Пожалуй нужно начать с небольшой предыстории: сижу я как-то на паре и решили мы с одногруппником поспорить о возможности создания простейших танчиков в консоли (по типу дендивских), но для игры по сети.
Так как компьютерных сетей у нас ещё не было, мне пришлось самой учить всё с нуля. Прочитав, пожалуй, страниц 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 за идеи и правки статьи.
Жду ваших пожеланий и идей, да и прибудет с вами сила!