Как написать софт для игры

Содержание
  1. Создание собственного программного обеспечения Windows
  2. Создаем собственное программное обеспечение для Windows
  3. Способ 1: Программы для написания программ
  4. Способ 2: Язык программирования и среда разработки
  5. Как начать писать игры
  6. Выбор проекта
  7. Графика и обработка событий
  8. Синхронизация, движение, столкновения, анимация
  9. Переинженеринг
  10. Планирование, анализ столкновений, физика, уровни, искусственный интеллект
  11. Платформеры, Action/Adventure, RPG, RTS, движки
  12. 3D игры
  13. Как написать игру на C++
  14. О чем статья?
  15. Оглавление
  16. Мотивация
  17. Зачем так делать?
  18. Почему C++?
  19. Что нужно знать, чтобы понять статью?
  20. Обработка действий игрока в реальном времени
  21. Windows API
  22. conio.h + getch
  23. Запуск периодических событий по таймеру
  24. Получение текущего времени Windows API
  25. Получение текущего времени std::chrono
  26. Генератор случайных чисел
  27. Изменение размера консоли
  28. Ускорение вывода текста
  29. Перестановка курсора
  30. Вывод готовых фрагментов текста
  31. Настройка буферизации stdio.h
  32. Измерение производительности
  33. Заключение

Создание собственного программного обеспечения Windows

Ежедневно каждый активный пользователь компьютера сталкивается с работой в разных программах. Они призваны облегчить работу за ПК и выполняют определенный ряд функций. Например, калькулятор подсчитывает заданные примеры, в текстовом редакторе вы создаете документы любой сложности, а через плеер просматриваете любимые фильмы или слушаете музыку. Весь этот софт был создан с помощью языков программирования, начиная от основных элементов управления, и заканчивая графическим интерфейсом. Сегодня мы бы хотели обсудить два метода собственноручного написания простых приложений для операционной системы Windows.

Создаем собственное программное обеспечение для Windows

Сейчас разработать свою программу можно и без знания языков программирования, однако для этого существует совсем мало подходящих средств, позволяющих в полной мере реализовать задуманное. К тому же сейчас на просторах интернета бесплатно доступно множество курсов по ЯП, описывающих примеры написания софта с предоставлением исходного кода. Поэтому поставленная задача вполне реализуема, нужно лишь выбрать метод, что мы и предлагаем сделать далее.

Способ 1: Программы для написания программ

Если вы интересовались созданием игр, то знаете о специальных инструментах, куда уже встроено множество компонентов и записаны основные скрипты. Юзеру остается лишь создать из этого цельную картину, систематизируя имеющиеся данные. Примерно по такому же принципу работает и ПО, позволяющее создавать собственные приложения без знания языков программирования. За пример мы взяли HiAsm, поскольку это единственное оптимальное решение с полной локализацией на русский язык.

Возможностей рассмотренного инструмента хватит не только для того, чтобы создать простое графическое приложение. HiAsm вполне справляется и с гораздо сложными работами, например, созданием проигрывателя или загрузчика файлов из интернета. Конечно, здесь придется приложить намного больше усилий и выучить множество схем и встроенных скриптов. Все это намного проще освоить, если использовать официальные ресурсы, например, форум. Там пользователи не только делятся своими работами, но и объясняют начинающим азы конструирования ПО. Тем более при возникновении вопросов ничего не мешает вам создать отдельную тему, подробно описав сложившуюся трудность.

Способ 2: Язык программирования и среда разработки

Как уже было сказано ранее, абсолютно все программы пишутся на определенном языке программирования. В некоторых сложных проектах бывает задействовано сразу несколько ЯП. Такой способ написания софта самый сложный, но при освоении одного из языков вы получаете практически безграничные возможности в кодировании программного обеспечения, утилит или отдельных скриптов. Главная задача — определиться с языком программирования. На этот вопрос постарались дать ответ специалисты из известного обучающего сервиса GeekBrains. Всю необходимую информацию вы найдете по указанной ниже ссылке.

Теперь же давайте рассмотрим несколько вариантов обеспечения, написанного при помощи упомянутых в статье ЯП. В первую очередь затронем Python, который некоторые программисты считают самым простым языком. Чтобы на экране появилось простое графическое окно размером на весь экран, придется подключить стандартную библиотеку Tkinter и написать код такого формата:

from tkinter import *

class Paint(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent

def main():
root = Tk()
root.geometry(«1920×1080+300+300»)
app = Paint(root)
root.mainloop()

if __name__ == «__main__»:
main()

Далее добавляется код, который вы видите на скриншоте ниже. Он реализует примерно такие же функции, как стандартная программа Paint.

После успешной компиляции запускается графическое окно с уже добавленными кнопками. Каждая из них отвечает за размер кисти и цвет.

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

В приведенной статье на GeekBrains отдельное внимание уделено и C#, который называют универсальным языком программирования для тех, кто еще не определился, в какой области хочет применять свои навыки. Разработка ПО для Windows ведется в официальной среде от Microsoft под названием Visual Studio. Код внешне выглядит так, как вы видите ниже:

namespace MyWinApp
<
using System;
using System.Windows.Forms;

public class MainForm : Form
<
// запускаем приложение
public static int Main(string[] args)
<
Application.Run(new MainForm());
return 0;
>
>
>

Как видите, существуют определенные различия с тем же Python. Если скопировать этот код, вставить его в IDE и скомпилировать, на экране появится простейшее графическое окно, куда уже в дальнейшем и будут прикрепляться кнопки, блоки и другие объекты.

Мы упомянули о Visual Studio как о среде разработки. Она понадобится в любом случае, если вы хотите писать собственный софт на ЯП, поскольку стандартный блокнот или текстовый редактор для этого практически не подходит. Ознакомиться с лучшими IDE, поддерживающими разные языки, мы советуем в отдельной нашей статье от другого автора далее.

В рамках этой статьи мы постарались максимально детально ознакомить вас с процессом написания программного обеспечения с помощью двух доступных методов. Как видите, дело это не совсем простое, ведь нужно получать специальные знания и учить многие аспекты, чтобы освоиться в этом деле. Приведенный выше материал был нацелен лишь на предоставление общей информации для ознакомления и не является полноценным уроком, освоив который, можно стать уверенным программистом. Если вас заинтересовал какой-либо ЯП или HiAsm, потребуется уделить много времени на изучение соответствующих обучающих материалов.

Помимо этой статьи, на сайте еще 11847 инструкций.
Добавьте сайт Lumpics.ru в закладки (CTRL+D) и мы точно еще пригодимся вам.

Отблагодарите автора, поделитесь статьей в социальных сетях.

Источник

Как начать писать игры

Путь в индустрию игровых разработок не близок. Эта статья призвана помочь понять с чего лучше начать это путешествие.

Вы только что закончили ваш первый курс по С++ и хотите начать делать игры. Кто-то указал вам на этот сайт и вы, возможно, поэкспериментировали немного с руководством. Вы изучили несколько лаконичных примеров, но не нашли руководства о том, как сделать целую игру. И на то есть причина.

Руководства хороши для обучения чему-то шаг за шагом, например тому, как перемещать изображение точки по экрану. Для того чтобы собрать игру воедино, вам нужны навыки решения возникающих проблем, приобретаемые лишь с опытом. Это не то, чему можно научиться из руководств. Лучший способ научиться делать игры — это начать их делать.

Выбор проекта

Итак, с чем же начать? Проще ответить с чего начинать не стоит, а именно с больших проектов, типа полноценной 3D FPS, MMO или даже длинного платформера 16-битной эпохи. Самая распространенная ошибка начинающих разработчиков это начать с большого проекта основанного на Крутой Идее или взять проект, который кажется простым, и закончить с полузаконченной кучей спагетти-кода. Поначалу следует создавать небольшие проекты.

В ранних проектах ваша основная цель учеба, а не реализация Крутых Идей. Поддерживая проект небольшим, вы можете сфокусироваться на изучении новых техник, а не тратить кучу времени на управление кодом и рефакторинг. Несмотря на то, что ваша Крутая Идея может быть офигительно офигенной, реальность индустрии разработки такова, что чем больше проект, тем больше вероятность совершить ошибку в архитектуре. И чем больше проект, тем дороже обходится эта ошибка. Помните историю Дедала и его сына Икара? Дедал создал крылья из воска и перьев для своего сына. Он предупредил Икара не подлетать на них слишком близко к солнцу. Но Икар проигнорировал предупреждение и крылья расплавились, и тогда-то гравитация и настигла его.

Поэтому помните: не подлетайте слишком близко к солнцу на ваших новых программистких крыльях.

Принимая во внимание все выше написанное, вот пара советов с чего начать.

Графика и обработка событий

Цель вашего первого проекта перейти от консольной разработки к разработки событийных графических приложений. Он так же научит вас фундаментальным основам игровой логики и архитектуры. Я рекомендую что-нибудь пошаговое, потому что игры с движением это совсем другой зверь.

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

Есть один момент, на который я хочу указать тем, кто будет делать крестики-нолики или четыре в ряд. Не стоит сейчас сильно беспокоиться об искусственном интеллекте. Сделать игру только для двух игроков или для игры с компьютером, который делает случайные ходы вполне достаточно для начала.

Если до этого вы имели дело с графикой и обработкой событий и чувствуете себя комфортно в этой области, можете приступить прямиком к следующему шагу.

Синхронизация, движение, столкновения, анимация

Здесь вы познакомитесь с движением, временем, анимацией, обнаружением столкновений, игровым циклом, вычислениями очков, побед и поражений и другими важными базовыми концепциями используемыми в каждой игре.

Duck Hunt и Pong — хорошие проекты для тех, кто уже имеет опыт в программировании графики и событий. В них есть простое обнаружение столкновений и все важные основы игр в реальном времени.

Читайте также:  Как пишется слово вода на английском языке

Space Invaders и Galaga — хороший выбор для второго/третьего проекта. В них есть уровни, поэтому вам нужно будет узнать как передвигаться от уровня к уровню, при помощи конечного автомата. Вы можете прочитать про конечные автоматы здесь(англ.). Игры в стиле «перестреляй их всех» так же требуют создать простые шаблоны поведения для врагов, что является шагом в сторону искусственного интеллекта.

Тетрис хорош для второго/третьего проекта. В нем совсем немного логики нужной для создания игры-головоломки. Это игра приличного размера, так что вам придется научиться разделять вашу программу на несколько исходных файлов, о чем вы можете больше прочитать здесь(англ.). Не недооценивайте Тетрис. Я недооценил и только посмотрите на это жуткое месиво в коде Lazy Blocks.

Переинженеринг

Типичная ошибка новичка это попытка сделать Самую Лучшую Игру Всех Времен, заканчивающаяся переинженерингом. То есть когда он пытается написать самую лучшую игру/движок и это все заканчивается тем, что используется только маленькая часть того что было понаписано.

Когда я был начинающим я переинженерил AI для крестиков-ноликов. Я хотел сделать игру с непобедимым AI. Мне удалось достигнуть этого, запрограммировав компьютер на знание всех возможных ловушек. Звучит круто не правда ли? Это заняло почти 40 000 тысяч строк в основном скопированного кода и месяц моего свободного времени.
Позже я выучил структуры данных и узнал про алгоритм Минимакс, который при меньшем размере кода не только делал нужное, но еще и делал это лучше.

Так что учитесь на моих ошибках и не будьте излишне амбициозны. Концентрируйтесь на обучении тому как делать игры, а не просто делайте их.

Планирование, анализ столкновений, физика, уровни, искусственный интеллект

Теперь, когда у вас за плечами две или три маленьких игры, пришло время сделать первый крупный проект.

До сих пор, вы, вероятно, программировали как придется. Это закончится на этом этапе. В реальном мире большинство процессов разработки завершается до того, как будет написана первая строчка кода. Ничто не может быть хуже, чем осознание того, что для того чтобы добавить в вашу игру то, что вы хотите, вам придется выкинуть весь написанный код, потому что вы не спланировали все заранее. Теперь, когда у вас есть опыт создания игр, вы знаете из чего состоит процесс разработки. Теперь вы можете планировать игры, перед тем как начинать их делать.

Теперь про вашу следующую игру. Break Out и Puzzle Bobble хороши для третьего проекта, потому что они включают в себя продвинутое распознавание столкновений и физику. Физика важна, поскольку дает игре реалистичное ощущение. Даже в Super Mario Brothers есть ощущение гравитации и инерции. Бильярд отличный проект для тех, кто хочет напрячь извилины физикой.

В играх типа бильярда вам нужно не только обнаруживать столкновения, но и обрабатывать их в определенном порядке. Обработка столкновений разительно отличается от их обнаружения. Хотя создание бильярда или 2D платформера может показаться простым делом, анализ столкновений в правильном порядке — запутанный процесс, и не должен быть недооценен.

Break out и Puzzle Bobble так же включают дизайн уровней и требуют загрузки и освобождения их ресурсов. Хорошим опытом будет создание редактора уровней для игры. Редакторы позволяют вам легко создавать уровни и не вынуждают впаивать их в приложение. У меня есть статья(англ.) про создание редактора уровней.

Так же вы возможно хотите попрактиковаться в написании искусственного интеллекта (AI). Один из вариантов — вернуться к крестикам-ноликам или четырем в ряд и написать непобедимый AI. Теперь вы уже должны знать структуры данных и сможете использовать знания о деревьях для использования алгоритма Минимакс. С этим алгоритмом вы можете просчитать все возможные исходы крестиков-ноликов и создать непобедимый AI. Забавно расстраивать им своих друзей. Так же вы возможно захотите сделать разные уровни сложности. Игра не приносит радости, если в нее нельзя выиграть.

Pac Man — отличный способ попрактиковаться в написании AI. Нужно будет знать структуры деревьев/графов и алгоритмы поиска, типа A*, для того чтобы призраки могли пройти через лабиринт. Так же нужно будет сделать чтобы призраки работали в команде. Все это пригодится когда вы будете делать игры со сложным AI, типа стратегий в реальном времени. Об основах AI можно прочитать тут(англ.).

Платформеры, Action/Adventure, RPG, RTS, движки

Теперь, когда вы получили опыт создания хорошо спланированной игры, вы готовы к созданию Action/Adventure/Платформера. Это будет кульминация графики, движения, анимации, анализа/обнаружения столкновений, физики, AI, программной архитектуры и всего остального, что вы изучите к этому моменту. Тем кто более амбициозен, можно предложить сделать стратегию в реальном времени(RTS) или ролевую игру(RPG). Будьте осторожны, потому что RPG и RTS действительно огромные проекты.

RTS также сложны архитектурно, а так же требуют много AI. Вам нужно будет делать поиск пути для юнитов, получение ими команд, разное поведение в зависимости от полученных команд. Если вы никогда до этого не делали AI, будет лучше начать с клона Pac Man’а для начала.

Вероятно вам впервые придется делать движок для вашей игры. Чего следует избегать, так это создания универсального движка. Создавая движок не пытайтесь сделать его подходящем для любой игры. Если ваша игра требует x, y и z, делайте движок который умеет x, y и z. Движки создают исходя из того что нужно для конкретной игры, а не из того что любой игре может потенциально понадобится.

Другая распространенная среди новичков ошибка — это попытка создать движок в качестве первого проекта. И обычно это универсальный движок. Вам не нужен движок с фантастической графикой для создания Pong’а или Space Invaders. Программируя, легко закопаться в деталях. Концентрируйтесь на общей картине и завершайте свои игры.

Кажется все хотят сделать следующую большую MMO. Создание онлайн игр не то, во что можно быстро вникнуть. Я понял это когда попытался сделать онлайн покер сразу после завершения крестиков-ноликов.

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

Вам следует полностью закончить хотя бы одну хорошо спланированную игру, перед тем как пробовать делать сетевую игру. В качестве первого сетевого проекта, попробуйте сделать что-нибудь, что не критично к скорости. Например простой чат-сервер/клиент будет хорошей практикой. Так же можно вернуться к крестикам-ноликам/четырем в ряд и добавить в них возможность играть в по сети. Как вариант попробуйте сделать сетевую карточную или настольную игру.

После того как ваш первый сетевой проект готов, попробуйте сделать что-нибудь в реальном времени. В вашем первом сетевом приложении вы, вероятно, использовали TCP, чтобы быть уверенным в том, что данные которые вы принимаете доходят в том порядке, в котором вы их посылали. Для игр в которых происходит много действий, задержки создаваемые TCP вероятно будут слишком велики, так что вам придется использовать UDP. UDP не гарантирует порядок доставки как и саму доставку вообще. Так как UDP не делает дополнительных проверок целостности он быстрее. Вам придется пожертвовать легкостью использования TCP, в обмен на скорость UDP и необходимость самостоятельной проверки целостности данных при создании игры.

3D игры

Перед тем как делать 3D игры, вам следует сделать хотя бы одну хорошо спланированную игру и иметь хорошее понимание трехмерной векторной математики, линейной и Ньютоновской физики. Тут вам придется иметь дело с вершинами, текстурами, освещением, тенями, опредением взаимодействия с объектами в трехмерном пространстве, загрузку моделей и прочими сложно звучащими вещами.

Хорошая новость в том, что если вы уже сделали 4 или 5 игр, вы уже знаете основы необходимые для создания игры. Вы уже хорошо знакомы с процессом разработки и знаете свои возможности как программиста. Неважно трехмерный шутер или двухмерный, он по прежнему шутер. 2D RPG или 3D RPG по прежнему RPG.

Не считайте это оправданием пропустить 2D и сразу перейти к 3D. Прежде чем научиться бегать, нужно научиться ходить.

Источник

Как написать игру на C++

О чем статья?

Оглавление

Что нужно знать, чтобы понять статью?

Обработка действий игрока в реальном времени

Запуск периодических событий по таймеру

Генератор случайных чисел

Изменение размера экрана консоли

Ускорение вывода текста

Вывод готовых фрагментов текста

Настройка буферизации stdio.h

Мотивация

Зачем так делать?

Почему C++?

Главная причина в том, что я сейчас веду индивидуальные занятия по C++ у одного талантливого студента. Его успехи вдохновили меня, а его вопросы показали о чем вообще нужно написать. C++ до сих пор рекомендуют как «язык для обучения». Это вселяет надежду, что статья будет полезна многим. Может быть когда-нибудь я напишу такую же статью и для других языков или для Linux, но не рассчитывайте на это. Если вы напишете сами подобный сборник советов для другого языка, то сообщите мне личным сообщением. Я добавлю ссылку на ваш труд.

Что нужно знать, чтобы понять статью?

Обработка действий игрока в реальном времени

Windows API

Поиск по ключевым словам «handle C++ event» выдало целую кучу бесполезной информации по рисованию окон в каком-то из фреймворков windows. Я все еще намерен писать простую игру в консоли, а не оконное приложение, поэтому игнорирую результаты.

Читайте также:  Не могу выучить немецкий язык что делать

Следующая попытка была «cpp event loop with console input» и мне попалась статья https://docs.microsoft.com/en-us/windows/console/reading-input-buffer-events из которой я и взял решение. Считаю это удачной находкой, потому что в процессе написания этой статьи пытался вспомнить свои запросы и даже слегка видоизмененный «event loop c++ with console application» уже не давал нужной информации. Пришлось смотреть историю поиска. Для будущих поколений я добавил сюда явный текст своих запросов, в надежде что будущие поиски будут более результативными.

Подстава в том, что даже эта статья на самом деле показывает пример блокирующего чтения «The function [ReadConsoleInput] does not return until at least one record is available to be read.» Поэтому я посмотрел на соседние статьи и нашел обзор низкоуровневых функций чтения https://docs.microsoft.com/en-us/windows/console/low-level-console-input-functions

В ней описана функция PeekConsoleInput, которая «If no records are available, the function returns immediately.», но которая при этом «Reads without removing the pending input records in an input buffer.». То есть если ее вызвать несколько раз подряд, то она получит информацию об одних и тех же событиях. К счастью там же еще описана функция FlushConsoleInputBuffer, которая удаляет все непрочитанные накопленные события. Сочетание этих двух функций позволит добиться нужного эффекта.

Я видоизменил код из найденной статьи https://docs.microsoft.com/en-us/windows/console/reading-input-buffer-events а именно:

Удалил обработку событий мыши. Если понадобится, спишите ее сами из оригинала.

Удалил колдовство над режимом консоли (функции GetConsoleMode/SetConsoleMode). Для обработки клавиатуры подходит и стандартный режим. Это позволило упростить обработку ошибок в функции ErrorExit.

Добавил сквозной счетчик итераций внешнего цикла. Чтобы было видно работу программы при отсутствии событий.

Получилось вот так:

Обработка событий клавиатуры в консоли Windows API

Результат запуска обработки событий клавиатуры в консоли Windows API

Допустим мы увидели как обрабатывать события клавиатуры не останавливая работу программы. Что можно с этим сделать?

Чтобы собрать больше информации, я поставил точку останова в функции обработки события и понажимал разные кнопки.

Анализ результатов в отладчике

Например вот так выглядит событие при нажатии «ж».

Обратите внимание, что во всех вариантах нажатия на кнопку «ж», «Ж», «;» в поле wVirtualKeyCode находится одно и то же число. Если использовать это поле, то управление не будет зависеть от раскладки и даже от зажатого шифта и капслока.

Еще важный момент, что wVirtualKeyCode для точки с запятой «;» на разных клавишах разный, но uChar.UnicodeChar у них одинаковый.

Итого с помощью Windows API мы можем:

Различать нажатые клавиши по коду клавиши независимо от раскладки

Различать нажатые клавиши по коду из Юникода независимо от их расположения на клавиатуре.

Обрабатывать нажатия на shift, ctrl, alt.

Понимать, нажат ли сейчас shift, ctrl, alt.

Отличать нажатие клавиши и ее отпускание (буду благодарен, если подберете слово получше).

Прикрутить обработку событий мыши

В обработчик нажатия клавиш добавил ветвление. В нем проверяется код клавиши и увеличивается соответствующая переменная.

Для проверки запустил и нажал вниз 1 раз, вправо 2 раза, вверх 3 раза, влево 4 раза. Получился вывод как на картинке. Чтобы сделать скриншот, пришлось подключить отладчик.

conio.h + getch

Мой студент параллельно мне нашел способ обрабатывать нажатия на кнопки с помощью комбинации функций kbhit и getch из conio.h. С помощью getch можно получить код нажатого символа, но эта функция ждет следующего нажатия. Чтобы программа при этом продолжала работать, нужно сначала вызвать kbhit. Эта функция вернет true, если нажата хотя бы одна клавиша, но при следующего нажатия ждать не будет. Если ничего не нажато, то kbhit возвращает false и программа работает дальше.

К сожалению, я сходу не разобрался, в какой кодировке этот код для кириллических символов. Я реализовал прототип с помощью conio и поэкспериментировал с нажатием клавиш. Эта библиотека игнорирует нажатие shift, ctrl, alt, caps lock. Точка с запятой «;» на клавише с «ж» вернет код «59», на клавише с «4» тоже получился код 59. У символов в разных регистрах, например «ж» и «Ж» будут разные коды. У меня получились 166 и 134 соответственно. Интересно, что стандартное преобразование «(char)key» превратило эти коды в совершенно другие символы.

Обработка событий клавиатуры conio.h Результат запуска обработки событий с помощью conio.h

Запуск периодических событий по таймеру

Как вы можете заметить, если просто отпустить программу в свободный полет, то она будет очень часто обращаться к списку событий. На моем компьютере получается несколько десятков раз за каждую миллисекунду. Само по себе это хорошо, т.к. позволяет оперативно реагировать на все действия игрока. Однако если мир будет меняться с такой же скоростью, то игрок никак не сможет поспеть за ним. Есть несколько способов контролировать период обновления мира. Я выбрал вариант без подключения дополнительных библиотек. Основная идея в том, чтобы постоянно смотреть на время, а обновление мира запускать каждые несколько секунд. Раз уж я докопал до ручной реализации event-loop, то изобрести велосипед с реализацией задержки в рамках выбранной архитектуры не составит труда.

Конкретная реализация нагуглилась с первого запроса «windows.h time milliseconds». https://stackoverflow.com/questions/17008026/windows-how-to-get-the-current-time-in-milliseconds-in-c Для очистки совести я еще попробовал поискать «cpp thread sleep» и «cpp sleep». Первый вариант получился слишком сложный, а второй недостаточно точный.

Существует несколько способов получить текущее время:

С помощью GetSystemTime из windows.h

С помощью std::chrono

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

Получение текущего времени Windows API

Сделать хитрое сравнение.

Первый вариант может решить проблему только если я воспроизведу вычисление настоящего unix-timestamp, а мне лень. Например там будет сложность с количеством дней в месяце. Поэтому я сделаю менее точную версию, где «начало эпохи» будет в начале текущего месяца.

Код получается вот таким

Получение текущей даты Windows API

При запуске получается примерно такой вывод:

Результат получения времени из Windows API

Теперь добавляю дополнительную переменную для хранения времени предыдущего обновления (prevTime) и периода между обновлениями (delay).

Каждую итерацию цикла текущее время сравнивается со временем последнего обновления. Если разница больше задержки, то выполняется обновление. Дополнительно реализована функция для предупреждения перехода через ноль.

При запуске получается примерно такой вывод.

Таким образом команда вывода слова «tick» будет выполняться раз в 300 миллисекунд. После нее нужно помещать логику будущей игры.

Получение текущего времени std::chrono

Реализация с помощью std::chrono получилась значительно проще. Но в ней используется непривычное для обычных людей запись времени. Там нет количества часов с начала дня, минут с начала часа и секунд с начала минуты. С помощью std::chrono можно получить количество миллисекунд с «начала эпохи». То есть с 1 января 1970 года. Это получается огромное целое число. Например 1610827417491. В отличие от «количества миллисекунд с начала секунды», это число всегда увеличивается. Поэтому не возникнет необходимости беспокоиться о переполнении часов.

В этом варианте я также использовал другой способ сравнения времени. На одно арифметическое действие в цикле меньше. Мелочь, а приятно.

Получение текущего времени std::chrono

Константа PRIu64 нужна для форматированного вывода значения типа uint64_t с помощью printf. Для её использования нужно подключить inttypes.h Хотелось бы, конечно, не подключать лишних библиотек, но времени на обход именно этой библиотеки у меня не было.

Вывод получился вот таким

Вывод от получения текущего времени std::chrono

Что с этим можно сделать? Навскидку мне в голову пришло:

Симуляция гравитации в платформере. Каждые Х миллисекунд персонаж должен падать на 1 символ.

Полет пуль. Каждые Х миллисекунд передвинь пулю по направлению выстрела.

Движение змейки. Каждые Х миллисекунд передвинь сегменты змейки на один символ от головы.

На картинке ниже вывод, который получается в такой программе время. Обратите внимание на чередование фраз «Each 225» и «Each 300».

Генератор случайных чисел

Если немного копнуть в алгоритм генераторов случайных чисел, то можно обнаружить, что они не такие уж случайные. Поэтому используют более точный термин Генератор псевдо-случайных чисел или ГПСЧ.

По запросу «с++ rand» и «cpp rand» можно найти довольно много материалов. Например обширную статью на русском https://en.cppreference.com/w/cpp/numeric/random/rand и чуть более сухую на английском https://en.cppreference.com/w/cpp/numeric/random/rand

Вкратце перескажу основные тезисы:

Функция rand возвращает случайное число от 0 до RAND_MAX. Значение RAND_MAX зависит от библиотеки, но должно быть не менее 32767.

Если просто вызывать функцию rand, то при разных запусках, последовательность случайных чисел будет одинаковой.

Чтобы последовательность была каждый раз разной, нужно применить функцию srand.

Я от себя добавлю, что аргумент функции srand еще называют зерном (или «сидом» от слова seed). Вы подобное видели во многих играх при создании мира.

Примеры начальных значений для ГПСЧ

Если зерно мира заполнять одним и тем же значением, то получатся одинаковые последовательности случайных значений. В играх получатся одинаковые миры. Вот пример использования ГСПЧ с постоянным значением зерна.

При запуске у меня появляется фраза «first is 440 second is 19053». Независимо от количества запусков получаются одни и те же числа. У вас могут получиться другие числа, но от запуска к запуску они должны быть одинаковы.

Чтобы последовательность менялась от запуска к запуску нужно передавать такое зерно, которое будет всегда разное. Что у нас не повторяется при запуске одной и той же программы раз за разом?

Инициализация зерна случайности с помощью std::chrono

Читайте также:  Молочное платье на свадьбу гостям

Я варварски сконвертировал uion64_t в int, но этого оказалось достаточно.

Следующая проблема в том, что числа получаются огромные. Повторюсь, что rand возвращает число от 0 до RAND_MAX, который у меня равен 0x7fff. В десятичной системе счисления диапазон случайных чисел получается от 0 до 32767.

Вы когда-нибудь задумывались, а почему остаток от деления нам действительно помогает? Не получится ли такого, что какое-то число будет встречаться чаще других?

Строгое математическое доказательство будет чрезмерным для этой статьи. Поэтому постараюсь объяснить «на пальцах».

Как вы можете заметить, диапазон от 0 до 14 разделился на несколько отрезков от 0 до 3. Числа в правом столбце возрастают так же равномерно, как и в левом. Точно такая же закономерность прослеживается и на диапазоне от 0 до 32767. Функция rand() может вернуть каждое число «из левого столбца» с равной вероятностью. После нахождения остатка от деления мы получим соответствующее число из правого столбца.

Обратите внимание, что последний диапазон «от 0 до 3» в правом столбце не успел закончиться. Если числа в левом диапазоне будут выбираться с равной вероятностью, то числа 0, 1, 2 из правого столбца будут появляться чаще, чем число 3. То же самое будет и на полном масштабе, если 32767 не будет делиться нацело на выбранное вами ограничение. Впрочем, исходный диапазон рандома достаточно велик, чтобы мы не заметили этот небольшой недостаток. В документации на cppreference в формуле выбора числа есть попытка компенсировать его делением на «((RAND_MAX + 1u)/6)».

Как уже говорилось выше, рандом возвращает числа от 0 до ограничения. Проблема в том, что при написании игры нужно использовать числа не от 0, а например от 10 до 20. В реализации std::rand приходится упражняться со сложением и вычитанием. К счастью, я нашел ссылку на статью с описанием синтаксиса, который был добавлен в стандарт C++ от 2011 года. https://en.cppreference.com/w/cpp/numeric/random/uniformintdistribution В нем есть красивый способ описать рандом в нужном диапазоне. Немножко оформления и можно просто получать случайные числа в нужном диапазоне.

Подробнее о Вихре Мерсенна можно почитать на Википедии https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%85%D1%80%D1%8C_%D0%9C%D0%B5%D1%80%D1%81%D0%B5%D0%BD%D0%BD%D0%B0

Меня такая находка очень порадовала. Пора переходить в туториалах для новичков на стандарт C++ хотя бы десятилетней давности. Этот фрагмент кода я специально для вас оформил текстом, который можно скопировать.

Изменение размера консоли

По умолчанию у меня в консоли помещается 80 символов в высоту и 50 в ширину. Это примерно треть моего экрана, поэтому захотелось увеличить количество символов в строке консоли хотя бы до 200.

Изменение размера окна консоли тоже оказалось задачкой с подвохом. Первый запрос был тривиальным «c++ change size of console window». Первый ответ на него подробно объяснял как сделать это с помощью настроек окна консоли на уровне операционной системы. То есть не из самой игры, а со стороны пользователя. Прикладывать эту инструкцию к игре я посчитал неправильным. Нужен способ сделать это из самой программы. Второй и последующие ответы описывали изменение размера окна консоли с помощью функции MoveWindow. Фактическое количество текста при этом не менялось. Если окно становилось слишком маленьким, то появлялись полосы прокрутки.

На тот момент я не знал точно, какие размеры стандартные и что я могу туда поставить. Поэтому указал 20 на 20. Для проверки размеров окна я также вывел прямоугольник из цифр от 0 до 9 шириной 20 на 20. Получился вот такой код:

Заполнение экрана символами и изменение размера консоли

Вывод получился вот таким

Поскольку это работа на уровне WinAPI, в результате получился код ошибки. Я в основном работаю с java стеком и обычно вижу стектрейсы и тексты исключений. Несмотря на это, принцип решения проблемы не изменился. Для расшифровки кода ошибки нужно воспользоваться официальной документацией. Она легко ищется запросом «getlasterror error codes». Кодов ошибок описано около девяти тысяч на нескольких страницах. Для моего случая подойдет первая страница https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes—0-499-

Ошибка гласит ERROR-INVALID-PARAMETER 87 (0x57) The parameter is incorrect.

Маловато объяснений. Тогда я проверил как другие пишут этот код. Запрос «SetConsoleScreenBufferSize incorrect argument» привел меня вот на этот вопрос на SO https://stackoverflow.com/questions/12900713/reducing-console-size

В ключевых аспектах код ответа был похож на мой. Но в нем содержалось важное дополнение «If you call SetConsoleScreenBufferSize with illegal value in COORDS (e.g. too little height/width) then you get an error, usually 87 ‘invalid argument’.»

Исходный код был видоизменен вот так:

Вывод при этом получился таким

Ускорение вывода текста

Запустив программу для заполнения большого экрана символами, я обнаружил, что текст пишется в консоль очень медленно. Заполнение символами экрана 200 на 80 заметно человеческому глазу. За одну секунду получится обновить экран лишь 1-2 раза. Это вряд ли связано с производительностью компьютера. Интуиция подсказывает, что это искусственное ограничение. При решении этой проблемы у меня было два направления поиска:

Как быстро написать много текста?

Как обновить только тот фрагмент экрана, который действительно менялся?

Сначала я поискал «cpp console reduce delay between screen updates». Практически все ссылки вели на советы по добавлению паузы, что мне совершенно не интересно. Только один ответ в выдаче говорил что-либо об ускорении вывода https://stackoverflow.com/questions/26376094/c-writing-to-console-without-delays. В нем предлагается подготовить большой буфер в памяти и вывести его одной командой.

Затем я поискал «windows.h write lot of text without animation» и нашел вот такой вопрос с очень любопытным ответом. https://stackoverflow.com/questions/34842526/update-console-without-flickering-c

Автор вопроса и автор ответа разговаривают в контексте создания консольной игры. Вместо полной очистки и полного заполнения экрана в ответе предлагается:

Переставить курсор и писать новый текст поверх старого без предварительной очистки экрана.

Переписывать фрагменты, а не отдельные буквы.

Переписывать отдельные буквы на экране.

Использовать два буфера и писать на экран только разницу.

Обратите внимание, в ответе на вопрос есть еще пример настройки цвета текста в консоли. У меня, к сожалению, не хватило времени воспроизвести этот прием.

Перестановка курсора

Простой перенос курсора в верхний левый угол экрана значительно улучшил ситуацию на windows 7. Я все еще видел процесс заполнения экрана, но текст на экране не исчезал два раза в секунду. У меня пропадали некоторые линии. У моего студента была windows 10 и без дополнительных ухищрений было видно только мигание самого курсора в разных частях экрана. Пропадания линий замечено не было.

За основу был взят код из главы «Изменение размера консоли».

Для своего удобства я реализовал функцию заполнения структуры нужными координатами.

Функция подготовки структуры с координатами

Для перестановки курсора достаточно вызвать функцию SetConsoleCursorPosition и передать ей в качестве аргументов поток вывода и структуру с координатами желаемой позиции курсора.

Вызов функции SetConsoleCursorPosition для перестановки курсора

Как я уже говорил, это улучшило ситуацию, но проблему полностью не решило. Поэтому я решил раскопать поглубже.

Вывод готовых фрагментов текста

Вывод текста фрагментами

В примерах еще часто использовался std::cout.flush(), который тащит за собой подключение iostream. Мне не хотелось использовать дополнительную библиотеку. Наверняка аналог есть и в stdio, который уже подключен. Мой запрос для поиска был «stdio flush output». Две ссылки на Stackoverflow указывают на fflush

Функция fflush вызывается с аргументом stdout. Я минут 10 искал как правильно заполнить переменную stdout, а оказалось она просто доступна из глобальной области видимости.

Настройка буферизации stdio.h

Настройка буфера вывода с помощью stdio.h

Измерение производительности

Тут я осознал, что не в состоянии сравнить производительность разных вариантов «на глаз». Поэтому вкрутил измерение времени до и после обновления экрана. Напишу тут порядок цифр, потому что значения с точностью до микросекунд не существенны для сравнения. Код практически полностью списал из ответа к этому вопросу https://stackoverflow.com/a/21856299

Функция получения времени с точностью до микросекунд

Измерил количество микросекунд без явного указания буфера. Три запуска, три числа: 8930000 8880000 9220000.

с размером буфера 1/16 от 740000 до 750000 микросекунд

с размером буфера 1/8 от 40000 до 39000 микросекунд

с размером буфера 1/4 от 18000 до 19000 микросекунд

с размером буфера 1/2 от 12000 до 13000 микросекунд

с размером буфера равным ширине консоли от 90000 до 10000.

Во всех этих случаях наблюдается мигание строк на экране размером с буфер. То есть при буфере 1/4, мигают фрагменты в четверть строки. С буфером равным ширине консоли получается очень большой разброс, причем без промежуточных значений. либо за 10000, либо за 90000. При этом мигает так, как будто буфер половина. redraw настройка буфера библиотеки.PNG

Велосипедный вариант с заполнением массива и выводом его на экран получился такой же, как с размером буфера равным ширине консоли. Были цифры 90000 и 10000 без промежуточных значений. При этом для создания буфера меньшего размера пришлось бы значительно усложнить реализацию. redraw велосипедный буфер.PNG

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

Заключение

К сожалению, новогодние праздники кончились и у меня сильно сократилось время на написание статьи. Вряд ли я когда либо еще плотно займусь этой темой. Поэтому я остановлюсь на достигнутом, чтобы не закопаться в перфекционизме.

P.S. Если вы нашли опечатки или ошибки в тексте, пожалуйста, сообщите мне. Это можно сделать выделив часть текста и нажав «Ctrl / ⌘ + Enter», если у вас есть Ctrl / ⌘, либо через личные сообщения. Если же оба варианта недоступны, напишите об ошибках в комментариях. Спасибо!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Источник

Простыми словами о самом интересном
Adblock
detector