Пожалуй нужно начать с небольшой предыстории: сижу я как-то на паре и решили мы с одногруппником поспорить о возможности создания простейших танчиков в консоли (по типу дендивских), но для игры по сети.
Так как компьютерных сетей у нас ещё не было, мне пришлось самой учить всё с нуля. Прочитав, пожалуй, страниц 30 отборного текста и прослушав четыре лекции по этой теме, мне стало очень скучно и лениво слушать это дальше, и я наконец приступила к проекту.
Эта статья будет короткой, но информативной (для новичков, как я).
На момент написания статьи я знала всего несколько языков и рассуждала о выборе каждого из них и насколько он подходит для разработки этих самых танчиков. Но опираясь на знания я решила распределить всё так:
C# — клиент (так как самый лёгкий в изучении язык)
Rust — сервер (так как самый безопасный и быстрый)
Php/html/css/javascript — сайт (который мы ВОЗМОЖНО будем делать)
Главное что я должна была сделать, дабы доказать правоту — это простые танчики, но я решила сделать универсальный клиент. Как это? — это когда сервер одинаково оптимизирован как и для WinForm, так и для консоли (потому что я хочу хорошие танчики в винформ).
Так что наша задача звучит так: Необходимо разработать три приложения, первое — для WinForm (стандартное окошко виндовс), второе — консольное (эмулятор денди) и третье — сам сервер.
Что необходимо делать хорошему приложению? — этот вопрос я задала при проектировании и в моей голове прозвучал ответ:«Быть быстрым».
Что это значит? — то, что нам придётся работать с несколькими потоками приёма/передачи данных. Снаряд не может ждать, пока отрисуется танчик, танк не может ждать пока рисуется стена, чтобы пошевелиться.
Мы все знаем как это обидно, когда у тебя падает фпс или не успевает что-то отрисоваться и тебя убивают. Таких ситуаций быть не должно!
Подумав, я решила распределить их именно так:
1-й поток (мэйн) — должен отправлять нажатую клавишу на сервер.
2-й поток должен принимать координаты танков.
3-й координаты стен.
4-й координаты снарядов.
Также желательно было создать чат для общения танкистов во время игры (вдруг в разных кабинетах будем), но это пока не реализовано.
Теперь немного конкретики с потоками, первый должен быть простым отправителем (то есть организовывать минимальны вычисления и отправлять их на сервер), остальные же должны вечно принимать и отрисовывать всё в нашем приложении. В виде кода всё будет примерно так:
После всего этого у меня возникли несколько вариантов организации данных, для их отправки на сервер, и тут в бой пошли лекции. Выбор стоял великий: или всё организовать в интовских/стринговских переменных и рисовать через них, или создать структуру для данных, объекты я не рассматривала т.к. не хотела возится с ссылками. Спустя часик гугляния на форумах я остановилась на втором, так как организация данных в виде структуры гораздо легче, да и писать документацию будет удобнее (совет: если есть группы данных, которые схожи по назначению — лучше будет объединить их в структуру, ибо так гораздо легче читать код и искать переменные). Наша новая задача звучит так: создать структуру в которой будут поля отвечающие за нажатую клавишу, координату игрока, угол поворота танка и (дл�� консоли) — позицию последнего символа и желательно какой-то регулятор отрисовки. Для чего нам последнее? — чтобы не использовались координаты с которыми мы работаем (увеличиваем/уменьшаем/отправляем на сервер)
В чём же были ошибки? — спросите меня вы.
Первоначально я начала лезть в дебри http модели (хотелось сделать на http), но спустя количество времени n мне стало ясно что лучше сделать на tcp (и проблем поменьше и с растом возится легче будет).
Мы определились с потоками и с идеей, что же дальше?
А дальше, друзья, будет самое интересное.
Сразу кину код, чтобы нетерпеливые читатели сразу его скопипастили:
Но это не самый удобный код, наилучший вариант был предложен: norver
и выглядит так:
Небольшая ремарка: в ходе написания продолжения этой статьи я выяснила что гораздо легче будет хранить числовую переменную, чем текстовую, именно поэтому в переменной dir (то есть позиция) мы будем хранить целочисленное значение.
Это самый простецкий код, но он делает огромную работу: он распоточивает наше будущее приложение.
Небольшое описание потоков: любой поток создаётся через пространство System.Threading. Создаём мы его так же, как и экземпляр класса, но в аргументе потока указываем void функцию.
После создания потока его можно запустить методом .Start() и отключить (вызвать исключение) методом .Abort(), но это есть синхронная модель (то есть едим и ножом и вилкой, но не можем резать, пока не возьмём вилкой), но есть асинхронная (мы едим и пылесосим, а ноги наши при этом делают жим лёжа по +100500 подходов), которую мы и взяли к использованию.
Вот мы написали свой первый «псевдокод» и основные положения/идеи нашего проекта. Первая стадия подошла к концу, а так же подошло к концу наше бездумство, далее мы будем разрабатывать функции и нам придётся попотеть.
lair, unsafePtr, vlreshet, domix32, vadimturkov, myxo, norver за идеи и правки статьи.
Жду ваших пожеланий и идей, да и прибудет с вами сила!
Так как компьютерных сетей у нас ещё не было, мне пришлось самой учить всё с нуля. Прочитав, пожалуй, страниц 30 отборного текста и прослушав четыре лекции по этой теме, мне стало очень скучно и лениво слушать это дальше, и я наконец приступила к проекту.
Ну что, все готовы? Начинаем!
Эта статья будет короткой, но информативной (для новичков, как я).
На момент написания статьи я знала всего несколько языков и рассуждала о выборе каждого из них и насколько он подходит для разработки этих самых танчиков. Но опираясь на знания я решила распределить всё так:
C# — клиент (так как самый лёгкий в изучении язык)
Rust — сервер (так как самый безопасный и быстрый)
Php/html/css/javascript — сайт (который мы ВОЗМОЖНО будем делать)
Часть первая: постановка задачи
Главное что я должна была сделать, дабы доказать правоту — это простые танчики, но я решила сделать универсальный клиент. Как это? — это когда сервер одинаково оптимизирован как и для WinForm, так и для консоли (потому что я хочу хорошие танчики в винформ).
Так что наша задача звучит так: Необходимо разработать три приложения, первое — для WinForm (стандартное окошко виндовс), второе — консольное (эмулятор денди) и третье — сам сервер.
Часть вторая: идеи и огрехи...
Что необходимо делать хорошему приложению? — этот вопрос я задала при проектировании и в моей голове прозвучал ответ:«Быть быстрым».
Что это значит? — то, что нам придётся работать с несколькими потоками приёма/передачи данных. Снаряд не может ждать, пока отрисуется танчик, танк не может ждать пока рисуется стена, чтобы пошевелиться.
Мы все знаем как это обидно, когда у тебя падает фпс или не успевает что-то отрисоваться и тебя убивают. Таких ситуаций быть не должно!
Подумав, я решила распределить их именно так:
1-й поток (мэйн) — должен отправлять нажатую клавишу на сервер.
2-й поток должен принимать координаты танков.
3-й координаты стен.
4-й координаты снарядов.
Также желательно было создать чат для общения танкистов во время игры (вдруг в разных кабинетах будем), но это пока не реализовано.
Теперь немного конкретики с потоками, первый должен быть простым отправителем (то есть организовывать минимальны вычисления и отправлять их на сервер), остальные же должны вечно принимать и отрисовывать всё в нашем приложении. В виде кода всё будет примерно так:
static async void Tank_coordinate() { //Приём координат танков await Task.Run( () => { }); } static async void Coordinate_wall() { //Приём координат стен await Task.Run( () => { }); } static async void Shot() { //Приём координат снарядов await Task.Run( () => { /* ДЛЯ СПРАВКИ: ТУТ МЫ ПИШЕМ СВОЙ КОД, КОТОРЫЙ БУДЕТ ВЫПОЛНЕН АСИНХРОННО */ }); } static void To_key() { //Приём нажатой клавиши }
После всего этого у меня возникли несколько вариантов организации данных, для их отправки на сервер, и тут в бой пошли лекции. Выбор стоял великий: или всё организовать в интовских/стринговских переменных и рисовать через них, или создать структуру для данных, объекты я не рассматривала т.к. не хотела возится с ссылками. Спустя часик гугляния на форумах я остановилась на втором, так как организация данных в виде структуры гораздо легче, да и писать документацию будет удобнее (совет: если есть группы данных, которые схожи по назначению — лучше будет объединить их в структуру, ибо так гораздо легче читать код и искать переменные). Наша новая задача звучит так: создать структуру в которой будут поля отвечающие за нажатую клавишу, координату игрока, угол поворота танка и (дл�� консоли) — позицию последнего символа и желательно какой-то регулятор отрисовки. Для чего нам последнее? — чтобы не использовались координаты с которыми мы работаем (увеличиваем/уменьшаем/отправляем на сервер)
В чём же были ошибки? — спросите меня вы.
Первоначально я начала лезть в дебри http модели (хотелось сделать на http), но спустя количество времени n мне стало ясно что лучше сделать на tcp (и проблем поменьше и с растом возится легче будет).
Мы определились с потоками и с идеей, что же дальше?
А дальше, друзья, будет самое интересное.
Часть третья: создание структуры и метода Main(). Конец первого этапа разработки
Сразу кину код, чтобы нетерпеливые читатели сразу его скопипастили:
//структура наша будет приватной //и через методы мы присваиваем ей значения /// <summary> /// Координаты игрока(численные значения) /// </summary> public struct player_coor { public static void new_player_coor (int x_, int y_, string dir_, ConsoleKey key_, int last_x_, int last_y_) { x = x_; y = y_; dir = dir_; key = key_; last_x = last_x_; last_y = last_y_; } static int x = 2;//стартовые координаты static int y = 2;// static string dir;//стартовое положене static ConsoleKey key;//нажатая клавиша static int last_x;// static int last_y;//последний 'y' и 'x' } static void Main(string[] args) { }
Но это не самый удобный код, наилучший вариант был предложен: norver
и выглядит так:
// Класс для удобного представления координат public class Position { // Публичные свойства класса public int X { get; set; } public int Y { get; set; } public Position(int x, int y) { X = x; Y = y; } } // Структура состояние игрока, бывшая "player_coor" public struct PlayerState { // Метод с названием идентичным названию класса или структуры // называется конструктор, он позволяет инициализировать свойства // и поля структуры или класса /// <summary> /// Конструктор структуры /// </summary> /// <param name="startPosition">Экземпляр позиции</param> /// <param name="dir_">Направление корпуса игрока (градусы)</param> /// <param name="key_">Нажатая клавиша</param> /// <param name="lastPosition">Предыдущая позиция игрока</param> public PlayerState(Position startPosition, int dir_, ConsoleKey key_, Position lastPosition) { StartPosition = startPosition; LastPosition = lastPosition; dir = dir_; key = key_; } private Position StartPosition { get; set; } private Position LastPosition { get; set; } //стартовое положение static int dir; //нажатая клавиша static ConsoleKey key; } static void Main(string[] args) { // Создаем экземпляр класса Position, с координатами 2, 2 var startPosition = new Position(2, 2); // Создаем экземпляр класса Position, с координатами 5, 10 var currentPosition = new Position(5, 10); // Создаем экземпляр структуры PlayerState var currentState = new PlayerState(startPosition, int, ConsoleKey.UpArrow, currentPosition); // А вот так можно получить доступ к свойствам класса Position Console.WriteLine("X={0}, Y={1}", startPosition.X, startPosition.Y); }
Небольшая ремарка: в ходе написания продолжения этой статьи я выяснила что гораздо легче будет хранить числовую переменную, чем текстовую, именно поэтому в переменной dir (то есть позиция) мы будем хранить целочисленное значение.
Это самый простецкий код, но он делает огромную работу: он распоточивает наше будущее приложение.
Небольшое описание потоков: любой поток создаётся через пространство System.Threading. Создаём мы его так же, как и экземпляр класса, но в аргументе потока указываем void функцию.
После создания потока его можно запустить методом .Start() и отключить (вызвать исключение) методом .Abort(), но это есть синхронная модель (то есть едим и ножом и вилкой, но не можем резать, пока не возьмём вилкой), но есть асинхронная (мы едим и пылесосим, а ноги наши при этом делают жим лёжа по +100500 подходов), которую мы и взяли к использованию.
Вот мы написали свой первый «псевдокод» и основные положения/идеи нашего проекта. Первая стадия подошла к концу, а так же подошло к концу наше бездумство, далее мы будем разрабатывать функции и нам придётся попотеть.
Огромное спасибо:
lair, unsafePtr, vlreshet, domix32, vadimturkov, myxo, norver за идеи и правки статьи.
Жду ваших пожеланий и идей, да и прибудет с вами сила!
