Танчики в консоли, статья первая: «От спора к написанию кода»

в 11:40, , рубрики: .net, C#, денди, разработка игр

Пожалуй нужно начать с небольшой предыстории: сижу я как-то на паре и решили мы с одногруппником поспорить о возможности создания простейших танчиков в консоли (по типу дендивских), но для игры по сети.

Так как компьютерных сетей у нас ещё не было, мне пришлось самой учить всё с нуля. Прочитав, пожалуй, страниц 30 отборного текста и прослушав четыре лекции по этой теме, мне стало очень скучно и лениво слушать это дальше, и я наконец приступила к проекту.

Ну что, все готовы? Начинаем!

Эта статья будет короткой, но информативной (для новичков, как я).

На момент написания статьи я знала всего несколько языков и рассуждала о выборе каждого из них и насколько он подходит для разработки этих самых танчиков. Но опираясь на знания я решила распределить всё так:

C# — клиент (так как самый лёгкий)
Rust — сервер (так как самый безопасный)
Php/html/css/javascript — сайт (который мы ВОЗМОЖНО будем делать)

Часть первая: постановка задачи

Главное что я должна была сделать, дабы доказать правоту — это простые танчики, но я решила сделать универсальный клиент. Как это? — это когда сервер одинаково оптимизирован как и для WinForm, так и для консоли (потому что я хочу хорошие танчики в винформ).

Так что наша задача звучит так: Необходимо разработать три приложения, первое — для WinForm (стандартное окошко виндовс), второе — консольное (эмулятор денди) и третье — сам сервер.

Часть вторая: идеи и огрехи...

Что необходимо делать хорошему приложению? — этот вопрос я задала при проектировании и в моей голове прозвучал ответ:«Быть быстрым».

Что это значит? — то, что нам придётся работать с несколькими потоками приёма/передачи данных. Снаряд не может ждать, пока отрисуется танчик, танк не может ждать пока рисуется стена, чтобы пошевелится.

Мы все знаем как это обидно, когда у тебя падает фпс или не успевает что-то отрисоваться и тебя убивают. Таких ситуаций быть не должно!

Подумав, я решила распределить их именно так:

1-й поток (мэйн) — должен отправлять нажатую клавишу на сервер.
2-й поток должен принимать координаты танков.
3-й координаты стен.
4-й координаты снарядов.

Также желательно было создать чат для общения танкистов во время игры (вдруг в разных кабинетах будем), но это пока не реализовано.

Теперь немного конкретики с потоками, первый должен быть простым отправителем (то есть организовывать минимальны вычисления и отправлять их на сервер), остальные же должны вечно принимать и отрисовывать всё в нашем приложении. В виде кода всё будет примерно так:

static Thread thread_on_frame = new Thread(On_StartFrame);//поток для приёма координат 
//танков
static Thread thread_on_frame_wall = new Thread(On_StartFrame_wall);//поток для приёма 
//координат стен
static Thread thread_on_frame_puf = new Thread(On_StartFrame_puf);//поток для приёма 
//координат и всего прочего связанного с выстрелами

И собственно функции к ним:

 static void On_StartFrame() {            
            //Приём координат танков
}

 static void On_StartFrame_wall() {
            //Приём координат стен
}

  static void On_StartFrame_puf() {
            //Приём координат/силы/цвета снарядов
}

//и конечно же функция из нашего мэйна
static void To_key() {
            //Передача нажатой клавиши
/*
*
*
*   Сюда переводится управления из мэйна при нажатии клавиши.
*   Именно под этот метод мы и будем писать дальше код
*   Так как под остальные он будет идентичным (будем много копипастить)
*
*/
}

После всего этого у меня возникли несколько вариантов организации данных, для их отправки на сервер, и тут в бой пошли лекции. Выбор стоял великий: или всё организовать в интовских/стринговских переменных и рисовать через них, или создать структуру для данных. Спустя часик гугляния на форумах я остановилась на втором, так как организация данных в виде структуры гораздо легче, да и писать документацию будет удобнее (совет: если есть группы данных, которые схожи по назначению — лучше будет объединить их в структуру, ибо так гораздо легче читать код и искать переменные). Наша новая задача звучит так: создать структуру в которой будут поля отвечающие за нажатую клавишу, координату игрока, угол поворота танка и (для консоли) — позицию последнего символа и желательно какой-то регулятор отрисовки. Для чего нам последнее? — чтобы не использовались координаты с которыми мы работаем (увеличиваем/уменьшаем/отправляем на сервер)

В чём же были ошибки? — спросите меня вы.

Первоначально я начала лезть в дебри http модели (хотелось сделать на http), но спустя количество времени n мне стало ясно что лучше сделать на tcp (и проблем поменьше и с растом возится легче будет).

Мы определились с потоками и с идеей, что же дальше?

А дальше, друзья, будет самое интересное.

Часть третья: наполнение метода Main() и конец первого этапа разработки

Сразу кину код, чтобы нетерпеливые читатели сразу его скопипастили:


public struct player_coor {
            public static bool use = false;// показывает, юзаем ли мы сейчас эту стр или нет

            public static int x = 2;//пусть по дефолту будет так
            public static int y = 2;
            public static string dir;//направление (пока что просто по сторонам(left,right...))

            public static ConsoleKey key;//нажатая кнопочка

            public static int last_x;//последние координаты нашего танчика
            public static int last_y;//по ним делаем колайдеры 
            public static ArrayList colider_x = new ArrayList();//наши колайдеры - дело святое
            public static ArrayList colider_y = new ArrayList();//но, возможно, не обязательное
        }


public static bool f = true;//это для завершения основного потока

 static void Main() {
    thread_on_frame.Start();
    thread_on_frame_puf.Start();
    thread_on_frame_wall.Start();
    while (f) {
               player_coor.key = Console.ReadKey().Key;
                To_key();  
            }
            thread_on_frame.Abort();
            thread_on_frame_puf.Abort();
            thread_on_frame_wall.Abort();
            //thread_background.Abort();
            //work.Abort();
            f = false;  

Это самый простецкий код, но он делает огромную работу: он распоточивает наше приложение.

Небольшое описание потоков: любой поток создаётся через пространство System.Threading. Создаём мы его так же, как и экземпляр класса, но в аргументе потока указываем void функцию.
После создания потока его можно запустить методом .Start() и отключить (вызвать исключение) методом .Abort().

Вот мы написали свой первый «псевдокод» и основные положения/идеи нашего проекта. Первая стадия подошла к концу, а так же подошло к концу наше бездумство, далее мы будем разрабатывать функции и нам придётся попотеть.

Жду ваших пожеланий и идей, да и прибудет с вами сила!

Автор: Алиса Котикова

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js