Создание игрового движка с нуля на C++

Создание игрового движка с нуля на C++

25 апреля 2022 г.


Как создать собственный игровой движок на C++


Итак, вы хотите узнать больше об игровых движках и написать их самостоятельно? Это потрясающе! Чтобы помочь вам в вашем путешествии, вот несколько рекомендаций по библиотекам C++ и зависимостям, которые помогут вам взяться за дело.


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


Один из моих наставников, доктор Сепи, однажды сказал:


«Некоторые думают, что игры — это детское развлечение, но разработка игр — одна из немногих областей, в которой используются почти все предметы стандартной учебной программы по CS». Сепиде Чакаве] (https://www.conted.ox.ac.uk/profiles/sepideh-chakaveh)


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


В разработке игры используются несколько предметов из стандартной учебной программы по CS


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


В этой статье будут рассмотрены некоторые основные строительные блоки, необходимые для создания простого игрового движка на C++. Я объясню основные элементы, необходимые для игрового движка, и дам несколько личных рекомендаций о том, как мне нравится подходить к написанию движка с нуля.


При этом это будет не учебник по программированию. Я не буду вдаваться в технические подробности или объяснять, как все эти элементы склеены вместе с помощью кода. Если вы ищете исчерпывающую видеокнигу о том, как написать игровой движок на C++, это отличная отправная точка: [Создание 2D-игрового движка с C++ и Lua] (https://pikuma.com/courses/cpp-2d). -игровой-движок-разработка).


Pikuma: Создайте игровой движок C++


Что такое игровой движок?


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


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


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


Чтобы действительно понять, как работает этот API, давайте рассмотрим его в контексте. Например, нередко API игрового движка предоставляет функцию под названием «IsColliding()», которую разработчики могут вызывать, чтобы проверить, сталкиваются ли два игровых объекта или нет. Программисту не нужно знать, как реализована эта функция или каков алгоритм, необходимый для правильного определения того, перекрываются ли две фигуры. Насколько нам известно, функция IsColliding представляет собой черный ящик, который творит чудеса и корректно возвращает true или false, если эти объекты сталкиваются друг с другом или нет. Это пример функции, которую большинство игровых движков предоставляют своим пользователям.


```нажмите


если (IsColliding(игрок, пуля)) {


жизни--;


если (живет == 0) {


Игра завершена();


![Большинство движков абстрагируются от обнаружения столкновений и просто отображают его как функцию true/false.]


Помимо API для программирования, еще одной большой обязанностью игрового движка является аппаратная абстракция. Например, 3D-движки обычно строятся на специальном графическом API, таком как [OpenGL] (https://www.opengl.org/), [Vulkan] (https://www.vulkan.org/) или [Direct3D]. (https://docs.microsoft.com/en-us/windows/win32/direct3d). Эти API обеспечивают программную абстракцию для графического процессора (GPU).


Говоря об аппаратной абстракции, существуют также низкоуровневые библиотеки (такие как DirectX, OpenAL и SDL), которые обеспечивают абстракцию и мультиплатформенный доступ ко многим другим аппаратным элементам. Эти библиотеки помогают нам получать доступ и обрабатывать события клавиатуры, движения мыши, сетевое подключение и даже звук.


Восстание игровых движков


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


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


Некоторые популярные классические движки: id Tech, Build и AGI . Эти движки были созданы для помощи в разработке конкретных игр, и они позволяли другим членам команды быстро разрабатывать новые уровни, добавлять пользовательские ресурсы и настраивать карты на лету. Эти пользовательские движки также использовались для [модификации] (https://en.wikipedia.org/wiki/Video_game_modding) или создания пакетов расширения для своих оригинальных игр.


Id Software разработала id Tech. id Tech — это набор различных движков, где каждая итерация связана с отдельной игрой. Часто можно услышать, как разработчики описывают id Tech 0 как «движок Wolfenstein3D», id Tech 1 как «движок Doom», а id Tech 2 как «движок Quake».


Сборка — еще один пример движка, который помог сформировать историю игр 90-х. Он был создан [Кеном Сильверманом] (http://advsys.net/ken/), чтобы облегчить настройку шутеров от первого лица. Подобно тому, что случилось с id Tech, Build развивался со временем, и его различные версии помогли программистам разрабатывать такие игры, как Duke Nukem 3D, Shadow Warrior и Кровь. Это, пожалуй, самые популярные игры, созданные с использованием движка Build, и их часто называют «большой тройкой».


Движок сборки, разработанный Кеном Сильверманом, редактирующий уровень в 2D-режиме.


Еще одним примером игрового движка из 90-х была «Утилита создания сценариев для Manic Mansion» (SCUMM). SCUMM — это движок, разработанный LucasArts, и он лежит в основе многих классических игр Point-and-Click, таких как Monkey Island и Full Дроссель.


Диалоги и действия Full Throttle управлялись с помощью языка сценариев SCUMM.


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


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


Зачем делать игровой движок?


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


Существует множество бесплатных, мощных и профессиональных [коммерческих движков] (https://www.incredibuild.com/blog/top-7-gaming-engines-you-should-consider-for-2020), которые разработчики могут использовать для создания и развертывать свои собственные игры. С таким большим выбором игровых движков, зачем кому-то создавать игровой движок с нуля?


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


  • Возможность обучения: низкоуровневое понимание того, как работают игровые движки внутри, может помочь вам вырасти как разработчику.

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

  • Настройка: вы сможете адаптировать решение под уникальные игровые требования.

  • Минимализм: меньшая кодовая база может уменьшить накладные расходы, связанные с большими игровыми движками.

  • Инновация: вам может понадобиться внедрить что-то совершенно новое или настроить таргетинг на нетрадиционное оборудование, которое не поддерживает ни один другой движок.

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


Как сделать игровой движок


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


1. Выбор языка программирования


Одним из первых решений, с которыми мы сталкиваемся, является выбор языка программирования, который мы будем использовать для разработки основного кода движка. Я видел, как движки разрабатывались на необработанном ассемблере, C, C++ и даже на таких высокоуровневых языках, как C#, Java, Lua и даже JavaScript!


Одним из самых популярных языков для написания игровых движков является C++. Язык программирования C++ сочетает в себе скорость с возможностью использования объектно-ориентированного программирования (ООП) и других парадигм программирования, которые помогают разработчикам организовывать и разрабатывать большие программные проекты.


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


![Разработчики могут получить доступ к контроллеру Xbox с помощью библиотек C++, предоставленных Microsoft.]


Говоря о производительности, лично я не рекомендую языки, использующие виртуальные машины, байт-код или любой другой промежуточный уровень. Помимо C++, некоторыми современными альтернативами, которые подходят для написания основного кода игрового движка, являются [Rust] (https://www.rust-lang.org/), [Odin] (https://odin-lang.org/), и [Zig] (https://ziglang.org/).


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


2. Доступ к оборудованию


В более старых операционных системах, таких как MS-DOS, мы обычно могли использовать адреса памяти и получать доступ к специальным ячейкам, которые были сопоставлены с различными аппаратными компонентами. Например, все, что мне нужно было сделать, чтобы «раскрасить» пиксель определенным цветом, — это загрузить в специальный адрес памяти число, представляющее правильный цвет моей палитры VGA, и драйвер дисплея преобразовал это изменение в физический пиксель в ЭЛТ-монитор.


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


Например, если вы используете Windows, macOS, Linux или *BSD, вам необходимо запросить у ОС правильные разрешения для рисования и рисования пикселей на экране или взаимодействия с любым другим аппаратным компонентом. Даже простая задача открытия окна на рабочем столе ОС должна выполняться через API операционной системы.


Следовательно, запуск процесса, открытие окна, рендеринг графики на экране, рисование пикселей внутри этого окна и даже чтение событий ввода с клавиатуры — все это задачи, специфичные для ОС.


Одной из очень популярных библиотек, которая помогает с абстракцией оборудования для разных платформ, является SDL. Лично мне нравится использовать SDL, когда я преподаю классы разработчиков игр, потому что с SDL мне не нужно создавать одну версию моего кода для Windows, другую версию для macOS и еще одну для студентов Linux. SDL работает как мост не только для разных операционных систем, но и для разных архитектур ЦП (Intel, ARM, Apple M1 и т. д.). Библиотека SDL абстрагирует низкоуровневый доступ к оборудованию и «переводит» наш код для правильной работы на этих разных платформах.


Вот минимальный фрагмент кода, который использует SDL для открытия окна в операционной системе. Я не обрабатываю ошибки для простоты, но приведенный ниже код будет одинаковым для Windows, macOS, Linux, BSD и даже RaspberryPi.


```нажмите


include


недействительным OpenNewWindow () {


SDL_Init(SDL_INIT_VIDEO);


SDL_Window* window = SDL_CreateWindow("Мое окно", 0, 0, 800, 600, 0);


SDL_Renderer* renderer = SDL_CreateRenderer(окно, -1, 0);


Но SDL — это всего лишь один пример библиотеки, которую мы можем использовать для достижения этого многоплатформенного доступа к оборудованию. SDL — популярный выбор для 2D-игр и переноса существующего кода на разные платформы и консоли. Другой популярный вариант многоплатформенной библиотеки, которая используется в основном с 3D-играми и 3D-движками, — GLFW. Библиотека GLFW очень хорошо взаимодействует с ускоренными 3D API, такими как OpenGL и Vulkan.


3. Игровой цикл


Когда у нас открыто окно ОС, нам нужно создать контролируемый [игровой цикл] (https://gameprogrammingpatterns.com/game-loop.html).


Проще говоря, мы обычно хотим, чтобы наши игры работали со скоростью 60 кадров в секунду. Частота кадров может различаться в зависимости от игры, но для сравнения: фильмы, снятые на пленку, воспроизводятся со скоростью 24 кадра в секунду (каждую секунду перед вашими глазами мелькают 24 изображения).


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


  • Обработка событий ввода без блокировки

  • Обновить все игровые объекты и их свойства для текущего кадра

  • Визуализировать все игровые объекты и другую важную информацию на экране

```javascript


пока (работает) {


Вход();


Обновлять();


Оказывать();


Это милый цикл while. Мы все? Точно нет!


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


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


4. Ввод


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


Для обработки пользовательского ввода мы должны запросить доступ к аппаратным событиям, и это должно быть выполнено через API операционной системы. Хорошая новость заключается в том, что мы можем использовать мультиплатформенную библиотеку аппаратных абстракций (SDL, GLFW, SFML и т. д.) для обработки пользовательского ввода.


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


нажмите лайк


недействительный ввод () {


событие SDL_Event;


в то время как (SDL_PollEvent(&event)) {


переключатель (событие.тип) {


случай SDL_KEYDOWN:


если (event.key.keysym.sym == SDLK_SPACE) {


Стрелять Ракетой();


перемена;


Опять же, если мы используем кросс-платформенную библиотеку, такую ​​как SDL, для обработки ввода, нам не нужно слишком беспокоиться о реализации для конкретной ОС. Наш код C++ должен быть одинаковым независимо от платформы, на которую мы ориентируемся.


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


5. Представление игровых объектов в памяти


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


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



Если одна из ваших целей — узнать больше об алгоритмах и структурах данных, я рекомендую вам попробовать реализовать эти структуры данных самостоятельно. Если вы используете C++, одним из вариантов является использование STL (стандартная библиотека шаблонов) и использование многих структур данных, которые поставляются с ней (векторы , списки, очереди, стеки, карты, наборы и т. д.). C++ STL сильно зависит от шаблонов, так что это может быть хорошей возможностью попрактиковаться в работе с шаблонами и увидеть их в действии в настоящий проект.


Когда вы начнете больше читать об архитектуре игрового движка, вы увидите, что один из самых популярных шаблонов проектирования, используемых в играх, основан на сущностях и компонентах. entity-component организует объекты нашей игровой сцены как сущности (то, что Unity называет "игровыми объектами", а Unreal называет "актерами") и компоненты (данные, которые мы можем добавить или прикрепить к нашим сущностям).


Чтобы понять, как сущности и компоненты работают вместе, представьте себе простую игровую сцену. Объекты будут нашим основным игроком, враги, пол, снаряды, а компоненты будут важными блоками данных, которые мы «прикрепляем» к нашим объектам, например, положение, скорость, коллайдер твердого тела и т. д.


![Популярным шаблоном проектирования игрового движка является организация игровых элементов в виде сущностей и компонентов.]


Некоторые примеры компонентов, которые мы можем прикрепить к нашим объектам:


  • Компонент положения: Отслеживает координаты положения нашего объекта в мире (или x-y-z в 3D).

  • Компонент скорости: отслеживает скорость движения объекта по оси x-y (или x-y-z в 3D).

  • Компонент Sprite: Обычно он хранит PNG-изображение, которое мы должны визуализировать для определенного объекта.

  • Компонент анимации: отслеживает скорость анимации объекта и то, как кадры анимации меняются с течением времени.

  • Компонент коллайдера: Обычно он связан с физическими характеристиками твердого тела и определяет сталкивающуюся форму объекта (ограничивающий прямоугольник, ограничивающий круг, сетчатый коллайдер и т. д.).

  • Компонент здоровья: сохраняет текущее значение здоровья объекта. Обычно это просто число или, в некоторых случаях, процентное значение (например, полоса здоровья).

  • Компонент сценария: Иногда к нашей сущности может быть прикреплен компонент сценария, который может быть внешним файлом сценария (Lua, Python и т. д.), который наши механизмы должны интерпретировать и выполнять за кулисами.

Это очень популярный способ представления игровых объектов и важных игровых данных. У нас есть сущности, и мы «подключаем» к нашим сущностям разные компоненты.


Существует множество книг и статей, в которых исследуется, как мы должны реализовывать дизайн сущностных компонентов, а также какие структуры данных мы должны использовать в этой реализации. Структуры данных, которые мы используем, и то, как мы получаем к ним доступ, напрямую влияют на производительность нашей игры, и вы наверняка слышали, как разработчики упоминают такие вещи, как [дизайн, ориентированный на данные] (https://en.wikipedia.org/wiki/Data-Oriented_design). ), Entity-Component-System (ECS), локальность данных и многие другие. другие идеи, которые полностью связаны с тем, как наши игровые данные хранятся в памяти и как мы можем эффективно получить доступ к этим данным.


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


Есть несколько популярных вариантов готовых к использованию библиотек ECS, которые мы можем включить в наш проект C++ и начать создавать сущности и присоединять компоненты, не беспокоясь о том, как они реализованы внутри. Некоторыми примерами библиотек C++ ECS являются [EnTT] (https://github.com/skypjack/entt/wiki) и [Flecs] (https://github.com/SanderMertens/flecs).


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


А теперь серьезный разговор! После того, как вы закончите свою специальную реализацию ECS, я бы посоветовал вам просто использовать некоторые из популярных сторонних библиотек ECS (EnTT, Flecs и т. д.). Это профессиональные библиотеки, которые разрабатывались и тестировались индустрией в течение нескольких лет. Они, вероятно, намного лучше, чем все, что мы могли бы придумать с нуля сами.


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


6. Рендеринг


Итак, похоже, сложность нашего игрового движка постепенно растет. Теперь, когда мы обсудили способы хранения и доступа к игровым объектам в памяти, нам, вероятно, нужно поговорить о том, как мы отображаем объекты на экране.


Первый шаг — рассмотреть характер игр, которые мы будем создавать с помощью нашего движка. Мы создаем игровой движок для разработки только 2D-игр? Если это так, нам нужно подумать о рендеринге спрайтов, текстур, управлении слоями и, возможно, воспользоваться ускорением видеокарты. Хорошая новость заключается в том, что 2D-игры обычно проще, чем 3D, а 2D-математика значительно проще, чем 3D-математика.



Если вашей целью является разработка 2D-движка, вы можете использовать SDL для облегчения многоплатформенного рендеринга. SDL абстрагирует аппаратное ускорение графического процессора, может декодировать и отображать изображения PNG, рисовать спрайты и отображать текстуры в нашем игровом окне.


Теперь, если вашей целью является разработка 3D-движка, нам нужно определить, как мы будем отправлять некоторую дополнительную 3D-информацию (вершины, текстуры, шейдеры и т. д.) на графический процессор. Вы, вероятно, захотите использовать программную абстракцию графического оборудования, и наиболее популярными вариантами являются [OpenGL] (https://www.opengl.org/), [Direct3D] (https://en.wikipedia.org). /wiki/Direct3D), Вулкан и Металл. Решение о том, какой API использовать, может зависеть от вашей целевой платформы. Например, Direct3D будет поддерживать приложения Microsoft, а Metal будет работать исключительно с продуктами Apple.


3D-приложения работают, обрабатывая 3D-данные через графический конвейер. Этот конвейер будет определять, как ваш движок должен отправлять графическую информацию на графический процессор (вершины, координаты текстуры, нормали и т. д.). Графический API и конвейер также определяют, как мы должны писать программируемые [шейдеры] (https://en.wikipedia.org/wiki/Shader) для преобразования и изменения вершин и пикселей нашей 3D-сцены.


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


У нас могут быть разные скрипты для каждой вершины и для каждого пикселя (фрагмента), и они управляют отражением, гладкостью, цветом, прозрачностью и т.д.


Говоря о трехмерных объектах и ​​вершинах, хорошей идеей будет делегировать библиотеке задачу чтения и декодирования различных форматов сетки. Существует множество популярных форматов 3D-моделей, о которых должны знать большинство сторонних 3D-движков. Некоторые примеры файлов: .OBJ, Collada, FBX и DAE. Я рекомендую начать с файлов .OBJ. Существуют хорошо протестированные и хорошо поддерживаемые библиотеки, которые обрабатывают загрузку OBJ с помощью C++. TinyOBJLoader и AssImp — отличные варианты, которые используются многими игровыми движками.


7. Физика


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


Здесь нам также необходимо рассмотреть, какой тип физики мы хотим смоделировать. 2D-физика обычно проще, чем 3D, но базовые части физического моделирования очень похожи на 2D- и 3D-движки.


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


Для 2D-физики я рекомендую посмотреть [Box2D] (https://box2d.org/) и [Chipmunk2D] (https://chipmunk-physics.net/). Для профессионального и стабильного трехмерного физического моделирования хорошими названиями являются такие библиотеки, как [PhysX] (https://developer.nvidia.com/physx-sdk) и [Bullet] (https://pybullet.org/). Использование стороннего физического движка всегда полезно, если стабильность физики и скорость разработки имеют решающее значение для вашего проекта.


![Box2D — это очень популярный вариант физической библиотеки, которую вы можете использовать с вашим игровым движком.]


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


Если вы хотите узнать больше о физических движках, вы можете воспользоваться несколькими хорошими книгами и онлайн-ресурсами. Для 2D-физики твердого тела вы можете посмотреть [исходный код Box2D] (https://github.com/erincatto/box2d) и [слайды] (https://box2d.org/publications/) от Эрин Катто. . Но если вы ищете исчерпывающий курс по игровой физике, то [2D Game Physics from Scratch] (https://pikuma.com/courses/game-physics-engine-programming), вероятно, будет хорошим началом.


Создайте 2D-физический движок с помощью C++ pikuma.com


Если вы хотите узнать о трехмерной физике и о том, как реализовать надежную симуляцию физики, другим отличным ресурсом является книга «[Игровая физика] (https://www.amazon.co.uk/Game-Physics-David-H-Eberly). /dp/0123749034)" Дэвида Эберли.


Игровая физика Дэвида Эберли


8. Пользовательский интерфейс


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


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


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


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


Если вашей целью является создание инструментов пользовательского интерфейса для вашего движка, я рекомендую использовать существующую стороннюю библиотеку пользовательского интерфейса. Быстрый поиск в Google покажет вам, что наиболее популярными вариантами являются [Уважаемый ImGui] (https://github.com/ocornut/imgui), [Qt] (https://github.com/qt) и [Nuklear]. (https://github.com/vurtun/nuklear).


![ImGui — это мощная библиотека пользовательского интерфейса, которая используется многими игровыми движками в качестве инструмента редактирования.]


Уважаемый ImGui — один из моих любимых, поскольку он позволяет нам быстро настраивать пользовательские интерфейсы для инструментов движка. В проекте ImGui используется шаблон проектирования под названием «[немедленный режим пользовательского интерфейса] (https://en.wikipedia.org/wiki/Immediate_mode_GUI)», и он широко используется с игровыми движками, поскольку он хорошо взаимодействует с 3D-приложениями, используя преимущества ускоренный рендеринг GPU.


Таким образом, если вы хотите добавить инструменты пользовательского интерфейса в свой игровой движок, я предлагаю просто использовать Dear ImGui.


9. Сценарии


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


Идея проста; мы встраиваем язык сценариев в наше родное приложение C++, и этот более простой язык сценариев может использоваться непрофессиональными программистами для описания поведения объектов, логики ИИ, анимации и других важных аспектов нашей игры.


Некоторые из популярных языков сценариев для игр: [Lua] (https://www.lua.org/), [Wren] (https://wren.io/), [C#] (https://en.wikipedia). .org/wiki/C_Sharp_(язык_программирования)), Python и JavaScript. Все эти языки работают на значительно более высоком уровне, чем наш родной код C++. Тот, кто пишет сценарии игрового поведения с использованием языка сценариев, не должен беспокоиться о таких вещах, как управление памятью или другие низкоуровневые детали работы основного движка. Все, что им нужно сделать, это запрограммировать уровни, а наш движок знает, как интерпретировать сценарии и выполнять сложные задачи за кулисами.


Lua — это быстрый и компактный язык сценариев, который можно легко интегрировать в проекты C и C++.


Мой любимый скриптовый язык — Lua. Lua небольшой, быстрый и очень легко интегрируется с собственным кодом C и C++. Кроме того, если я работаю с Lua и «современным» C++, мне нравится использовать библиотеку-оболочку под названием [Sol] (https://github.com/ThePhD/sol2). Библиотека Sol помогает мне начать работу с Lua и предлагает множество вспомогательных функций для улучшения традиционного Lua C-API.


Если мы включим скрипты, мы почти достигнем точки, когда сможем начать говорить о более сложных темах в нашем игровом движке. Скрипты помогают нам определять логику ИИ, настраивать кадры и движения анимации, а также другое игровое поведение, которое не должно жить внутри нашего собственного кода C++ и которым можно легко управлять с помощью внешних скриптов.


10. Аудио


Еще один элемент, который вы могли бы добавить в игровой движок, — это звук.


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


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


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


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


Вот несколько хороших библиотек и инструментов для аудио, которые вы можете интегрировать с игровым движком: SDL_Mixer, SoLoud и FMOD.


![Tiny Combat Arena использует библиотеку FMOD для звуковых эффектов, таких как допплер и сжатие.]


11. Искусственный интеллект


Последняя подсистема, которую я включу в наше обсуждение, — это ИИ. Мы могли бы достичь ИИ с помощью сценариев, что означает, что мы могли бы делегировать логику ИИ дизайнерам уровней в сценарий. Другим вариантом было бы встроить правильную систему искусственного интеллекта в нативный код нашего ядра игрового движка.


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


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


Подробная книга о теории и реализации искусственного интеллекта для игр называется [ИИ для игр] (https://www.amazon.co.uk/AI-Games-Third-Ian-Millington/dp/1138483974) Яна Миллингтона. .


Игровая физика Дэвида Эберли


Не пытайтесь сделать все сразу


Хорошо! Мы только что обсудили некоторые важные идеи, которые вы можете добавить в простой игровой движок на C++. Но прежде чем мы начнем склеивать все эти кусочки вместе, я просто хочу упомянуть кое-что очень важное.


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


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


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


Не торопитесь и сосредоточьтесь на основах


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


Большинство студентов очень взволнованы в начале проекта, и с течением времени начинает проявляться беспокойство. Если мы создаем игровой движок с нуля, особенно при использовании такого сложного языка, как C++, легко запутаться и потерять некоторый импульс.


Я хочу призвать вас бороться с чувством «бегства со временем». Сделайте глубокий вдох и наслаждайтесь маленькими победами. Например, когда вы узнаете, как успешно отображать текстуру PNG на экране, насладитесь этим моментом и убедитесь, что вы понимаете, что делаете. Если вам удалось успешно обнаружить столкновение двух тел, наслаждайтесь этим моментом и поразмыслите над только что полученными знаниями.


Сосредоточьтесь на основах и владейте этими знаниями. Неважно, насколько мала или проста концепция, владейте ею!!! Все остальное — эго.


Также опубликовано здесь



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE