Как сделать собственный игровой движок
Пишем собственный игровой движок с помощью C++
С нуля создадим собственный игровой движок с помощью библиотеки SFML и C++, чтобы разобраться, как происходит создание ядра.
В этом проекте мы создадим собственный игровой движок на C++. Движок будет очень простым, но готовым к расширению возможностей. Конечная игра с использованием этого кода тоже крайне проста: наш персонаж сможет перемещаться влево и право, а из графики – только бэкграунд и фигурка персонажа.
Подготовка Visual Studio
Создадим новый проект в Visual Studio. Обратите внимание, что проект требует библиотеку SFML, поэтому если вы не настраивали окружение для нее, прочтите сначала небольшое руководство по настройке.
Теперь можно приступить к коду. Исходный код и дополнительные ресурсы будут доступны на этой странице.
Проектируем собственный игровой движок
Самое важное – запуск движка, который будет происходить в файле Main.cpp, но им мы займемся немного позже.
Класс персонажа
Bob – простой класс для представления фигурки персонажа, управляемой игроком. Код класса будет легко расширяться, а что самое главное – его несложно переписать под любой другой игровой объект, который вы захотите добавить. Для этого потребуется заменить текстуру и описать поведение нового объекта в методе update().
Займемся заголовками класса. Выберите правой кнопкой Header Files в Solution Explorer и нажмите Add | New Item. В окне Add New Item выберите Header File (.h), затем в поле Name введите Bob. Нажмите Add и добавьте код заголовка класса:
Здесь мы объявили объекты типа Texture и Sprite. Дальше мы свяжем эти объекты и любое действие на экране с объектом Sprite будет сопровождаться изображением Боба:
Кликните правой кнопкой, чтобы сохранить
Bob.cpp
Теперь приступим к описанию методов.
Выберите правой кнопкой мыши Source Files в Solution Explorer и откройте Add | New Item. В окне Add New Item кликните по C++ File (.cpp), а в поле Name укажите Bob.cpp. Теперь добавьте в файл код:
В конструкторе мы установили значение переменной m_Speed на 400. Это значит, что Боб пересечет экран шириной в 1920 пикселей за 5 секунд. Также мы загрузили файл Bob.png в Texture и связали его с объектом Sprite. В переменных m_Position.x и m_Position.y установлено начальное положение Боба.
Функция update обрабатывает два If. Первое If проверяет, нажата ли правая кнопка (m_RightPressed), а второе следит за левой (m_LeftPressed). В каждом If скорость (m_Speed) умножается на elapsedTime. Переменная elapsedTime рассчитывается в функции Start движка (класс Engine). Им мы и займемся далее.
Пишем класс Engine
Класс Engine будет контролировать все остальное.
Engine.h
Добавим заголовок. Откройте окно Add New Item (так же, как для класса Bob), выберите Header File (.h) и в поле Name введите Engine.h. Добавьте в файл следующий код:
Класс библиотеки SFML, RenderWIndow, используется для рендера всего, что есть на экране. Переменные Sprite и Texture нужны для создания фона. Также в заголовке мы создали экземпляр класса Bob.
Engine.cpp
В Engine.cpp мы опишем конструктор и функцию start. Создайте файл класса так же, как для Bob.cpp, и добавьте в него код:
Функция конструктора получает разрешение экрана и разворачивает игру на весь экран с помощью m_Window.create. В конструкторе же загружается Texture и связывается с объектом Sprite.
Пример фонового изображения
Скачайте пример изображения или используйте любое другое на свое усмотрение. Переименуйте файл в background.jpg и поместите в каталог Simple Game Engine/Simple Game Engine.
Игровой цикл
Следующие три функции будут описаны каждая в своем файле, но при этом они должны быть частью Engine.h. Поэтому в начале каждого файла укажем директиву #include «Engine.h», так что Visual Studio будет знать, что мы делаем.
Обрабатываем пользовательский ввод
Создайте файл Input.cpp и добавьте в него код:
Функция input обрабатывает нажатия клавиш через константу Keyboard::isKeyPressed, предоставляемую SFML. При нажатии Escape m_Window будет закрыто. Для клавиш A и D вызывается соответствующая функция движения.
Обновляем игровые объекты
Теперь опишем простую функцию update. Каждый игровой объект будет иметь собственную функцию update.
Создайте файл Update.cpp и добавьте в него код:
Поскольку у нас пока только один объект «Боб», мы вызываем только функцию m_Bob.update.
Отрисовка сцены
Это последняя функция класса Engine. Создайте файл Draw.cpp и добавьте в него код:
Экран очищается методом clear, затем отрисовывается фон. Первым делом должен быть отрисован фон, чтобы потом поверх него можно было отрисовать Боба.
Запускаем движок в main()
Теперь вернемся к нашему Main.cpp. Время добавить в него немного кода:
Несколько слов в конце
Наш собственный игровой движок получился очень простым: он умеет только двигать главный объект и закрывать программу. Он не умеет обрабатывать столкновения, работать с интерфейсом и еще много чего. Однако он отлично описывает то, как строится ядро игрового проекта с нуля. К тому же, как мы уже выяснили, класс Bob расширяется и адаптируется под другие объекты, так что дайте волю фантазии и попробуйте поэкспериментировать с окружением.
Как написать свой игровой движок с нуля
Возможно, тема надоевшая, но я поискал в интернете на эту тему и понял что в интернете(youtube, yandex, habr) нет ничего полезного, вернее оно есть, но без объяснений.
Сразу скажу что, C# я знаю больше чем C++, просьба отталкивать от этого.
Что я подразумеваю под словом ‘с нуля’.
1. Нужно определиться с языком программирования. C++ или C#?
Если я напишу: «Мне нужен обоснованный ответ!», будет немного грубо.
По этому, желательно обосновать ответ и написать названия книг по выбранному языку.
Не нужно писать: вот C++ быстрее чем C#, пиши на C++ и т.д.
2. Какой фреймворк? (Если неправильно назвал поправьте)
OpenTK, GLFW и т.д. Источники, где можно поподробнее разобраться.
3. Что нужно знать в языке программирования что бы написать свой игровой движок?
Пример ответа: Нужно уметь пользоваться массивами, отладчиком, искать ошибки и т.д.
4. Ну и с чего стоит начать, после изучения языка.
Написать свой движок с нуля
Как написать свой движок с нуля при помощью C++. Заранее спасибо.
Можно ли в Visual Basic написать свой игровой движок
Здравствуйте господа программисты. Я хотел сделать свою игру, и для этого я искал разные движки.
Свой игровой движок
Я писал 3 раза игровой движок. Теперь предстоит четвертый. Подскажите, на чем лучшего его написать.
Как написать игровой движок
доброй ночи уважаемые, прошу помочь, я взялась прописывать движок на игру, и не очень понимаю что.
Ответ на твой вопрос зависит от того что ты хочешь получить на выхлопе?
По тому что если тебе нужен CryBiteUnrealUnity 10K5000 то запросы одни, а если тебе двиг для майнкрафта то запросы другие.
Добавлено через 1 час 0 минут
Я предположу, что ты делаешь CryBiteUnrealUnity 10K5000.
Те же фростбайты, анриалы, юнити и край энжины написаны на c/c++, сам сделай выводы.
У шарпа вообще плюсов нет :), помогите их найти? Единственный аргумент в таких спорах что я слышал, это дот нет и куча библиотек, так ведь у с++ тоже их море, один qt если вспомнить, а в контексте разработки с нуля с# не о чем
Опять же, если ты будешь писать свой серьезный движ то использовать сторонние библиотеки не будешь, по тому что все будешь писать под свои запросы. По тому openTK тебе не нужен, тебе нужно что-то типа glfw+gluw, которые только лишь создают окно и импортируют функции api opengl (ты же на opengl смотришь?), но можешь и по хардкору #include и самому импортировать функции и создавать окна.
Все что нужно знать об openGL есть тут.
Конкретно тебе нужно знать:
1.1) как создать окно
Первый самый простой этап.
2.1) ты должен узнать что такое шейдер, как его скомпилить на гпу, как его применить.
2.2) как создать vao (обязательно нужно разобраться как использовать несколько атрибутов), и как его выводить (рисовать), тут ты узнаешь что заходит на вход вершинному шейдеру и что выходит из фрагментного.
Тут ты уже поймешь как работает opengl, тебе станет очень радостно если ты добрался до сюда.
2.3) Ты должен загрузить изображение, создать текстуру, установить ее в семплер, внести изменения в код шейдера и любоваться своей текстурой.
Второй этап закончен. Ты уже можешь что-то рисовать посредством шейдеров.
Вот от сюда будет начинаться что-то серьезное.
3.1) Нужно будет разобраться с тем что такое fbo, как его создавать, как его применять/чистить и как в него рендерить.
3.2) Зная что такое fbo, нужно будет в первый раз что то отрендерить в текстуру (тобишь в fbo), сделаешь тени.
Вот на этом моменте кончается 90% работы. Можешь начать с этого после изучения языка.
Потом ты еще должен будешь выбрать аудио апи аля OpenAl.
Что нужно знать о языке?
Само собой нужно уметь работать с массивами, циклами (for, while), блоками ветвлений (if/else, switch/case), структуры. Потом ты должен знать как алоцировать память динамически, аля new и delete, указатели ( не умные, а обычные, умные таковыми только называются).
Не плохо бы знать ооп (классы, наследование), опять же, перегрузка и таблицы виртуальных функций для риалтайма зло.
Самые важные вопросы ты не спросил. Это что нужно знать о том что нужно знать о программировании.
Так вот, знать нужно алгоритмы и патерны проектирования. Если с первым все ясно, то второе нужно по тому что иначе твой вдиж станет лапшой с такой скоростью что ты не заметишь.
Game Engine своими руками #1
Сегодня мы поговорим об игровых движках, причем не о том, как и где их можно надыбать (об этом уже много говорилось в твоем любимом Х.), а о том, как самому написать и обустроить этот самый движок, чтобы потом не было стыдно ни тебе, ни мне. Дело это, скажу сразу, довольно сложное, причем сложное на столько, что на нем можно хорошо заработать и прославиться, чем многие программеры и пользуются. Я не буду пытаться рассказывать тебе о том, как сделать хорошую игру (может быть в следующий раз), не буду рассказывать и о том, как написать ту или иную часть движка, в этой статье ты вообще не увидишь ни одной строчки кода, а лучше расскажу о том, о чем следует хорошенько подумать прежде,
чем бросаться писать свой движок, и как в
общем можно организовать этот процесс, чтобы добиться хорошего результата малой кровью.
Если ты решил написать игровой движок, то значит ты уже хоть что-то знаешь, а как минимум неплохо владеешь тем или иным языком программирования. Если ты вынес со школьной скамьи основы Бейсика, то тебе, конечно, выбирать не приходится, но я, все же, не советовал бы тебе браться за игру, пока ты не овладеешь более совершенным инструментом. Если ты пишешь только на Delphi, и пишешь довольно долго, то у тебя есть хоть какие-то шансы выжить, но в идеале желательно овладеть языком Си, все-таки все солидные игры пишутся сегодня именно на нем. Лучше всего для нашего дела подойдет Visual C++ от Microsoft, желательно шестой версии, хотя это и не принципиально. Если ты долгое время общался с другими компиляторами, например от Borland, то тебе, конечно, имеет смысл оставаться в родной среде.
Прежде, чем писать движок, надо точно определиться с его специализацией и возможностями. Конечно, можно сделать игру и без предварительных планировок и раздумий, но поверь мне, тебе будет намного легче потом, если ты потратишь немного времени и обдумаешь все детали проекта сейчас. Первое, о чем стоит задуматься так это тип игры, для которой твой движок будет предназначен: будет ли это продвинутая RPG, либо гоночная аркада, либо это будет очередной шутер аля Quake. Будет ли твой движок поддерживать моды или он будет абсолютно закрытым. Стоит ли писать поддержку сети или можно обойтись сингл-плейером с глубоко проработанным сюжетом. Разберемся со всем этим по отдельности.
И так, давай разберемся в чем же кроется принципиальная разница при написании движка взависимости от его специализации.
Если твой движок будет предназначаться для простенькой двухмерной аркады или скроллера, то тебе имеет смысл оформить свой движок в виде статичной (LIB) или динамической (DLL) библиотеки, описать в ней несколько необходимых высокоуровневых функций для вывода спрайтов, анимации палитры и воспроизведения звуков. Этого тебе вполне хватит. Потом, когда ты будешь писать игры, ты просто подключишь эту библиотечку с движком и воспользуешься
необходимой функцией.
Если же ты захотел написать крутую RPG, с завернутым нелинейным сюжетом и высоко-интерактивной вселенной, то тебе стоит отвлечься от графической части движка и уделить больше внимания (намного больше) поддержке скриптов (если ты до сих пор не знаешь, что это такое, то жди моих следующих статей).
При создании 3D движка для шутеров вроде Анрыла и Квейка, очевидно, в первую очередь стоит позаботиться о рендере, т.е. той части движка, которая будет отвечать за прорисовку (рендеринг) окружающего мира. Ты должен заранее определиться с системой, которую ты планируешь использовать. Будут ли это BSP-деревья (Quake), портальный движок (Unreal) или quad-деревья (Tribes). Особо ярые энтузиасты могут заняться строганием воксельного движка.
Следует строго разделить вещи, которые просто необходимы движку, которые играют ключевую роль (например, поддержка мультиплейера) и вещи, которые хотелось бы видеть (например, продвинутая система частиц или объемный туман). И прежде всего следует браться за реализацию именно необходимых вещей, все остальное может вообще не потребоваться в будущем.
Уверен, что про моды ты слышал не раз. Может быть, даже сам принял участие в их создании и уж точно попробовал клепать уровни для любимой Кваки или Халф-лайфа. Сейчас инет просто расходится по швам от обилия модов для игрушек, есть люди, которые до сих пор пишут моды для первоквака. С помощью мода первоначальную игру можно просто-таки вывернуть наизнанку, поменять не только внешний вид, но и
кардинально перевернуть гейм-плей, а можно просто слегка изменить правила, например замедлить практически до нуля скорость снарядов от BFG и так вот резвиться. Всей этой романтики
не было бы вообще, если бы умные программеры, такие как Джон Кармак, не предусмотрели бы поддержку этих самых модов в своих движках.
Ты можешь спросить, почему большинство игр все же не поддерживают моды если это так круто? Ответить однозначно на этот вопрос довольно сложно, но можно привести несколько основополагающих доводов. Во-первых, поддержка модов дело довольно сложное, а порой совсем и не нужное, представь себе мод «Rocket Arena» для Сапера. Во-вторых, поддержка модов может нанести, не очевидные сначала, как моральные, так и финансовые убытки разработчику, если какой-нибудь Вася из Таганрога выложит в сеть мод, форматирующий винт при каждом запуске игры. Поэтому-то Кармак и пишет собственные компиляторы, чтобы еще на этапе создания мода присечь попытки деструктивной деятельности.
Существует вариант, когда движок поддерживает не моды, а внешние ресурсы в виде самодельных карт к игре, моделек, текстурок, звуков и т.п. Но в этом случае стоит серьезно задуматься о написании собственных утилит или о поддержке существующих бесплатных редакторов, потому что не у всех юзеров на компе стоят лицензионные 3D Studio MAX и Photoshop (ха-ха).
ОК, ты окончательно и бесповоротно решил писать поддержку модов, тогда давай обсудим как это все дело можно обустроить. Если ты хочешь разрешить пользователям модифицировать только ресурсы, то тебе не о чем беспокоиться, всего лишь нужно предусмотреть чтение небольшого файлика из каталога с модом, в котором будет сказано, как сам мод называется и какую карту нужно запустить первой, этого достаточно (в Half-Life этим файликом был ‘liblist.gam’). Если же ты хочешь разрешить программирование мода, то это более серьезно. Нужно подумать о безопасном месте в котором код мода будет храниться. Это не может быть самостоятельное приложение, так как моду необходимо, чтобы была запущена сама игра. Самым популярным решением является использование DLL (иногда разработчики меняют у файла расширение, чтобы смутить окружающих). DLL (Dynamic-Link Library) или динамические библиотеки это файлы в которых хранится набор некоторых функций, которые затем могут быть вызваны из любой другой программы при условии, что программа знает имя функций содержащихся в DLL. Когда моды организуются в виде динамических библиотек, заранее оговаривается какие функции по умолчанию должны содержаться в коде мода, а затем движок просто вызывает эти функции, не задумываясь над тем с каким именно модом он работает и где расположены функции, которые он вызывает. Причем код самой «родной» игры также можно выполнить в виде мода.
Наиболее распространенной является модель «Клиент-Сервер», когда один компьютер назначается сервером и начинает игру, а другие машины-клиенты потом к нему коннектятся, т.е. подсоеденяются. Сервер периодически опрашивает клиентов на предмет новых совершенных игроками действий, собирает инфу обрабатывает и раздает обратно, чтобы клиенты могли узнать последние новости друг о друге. На сервере хранится вся информация о положении и действиях игроков и других важных объектов. Если связь с клиентом была потеряна, то сервер либо делает его недоступным для других клиентов, либо просто оставляет его на растерзание врагам. Всякая мелочь типа разлетающихся гильз, облачков дыма от выстрелов, пятен крови и т.п. дело сугубо личное для каждого клиента и поэтому обрабатывается именно на клиентской машине. Сервер просто сообщает клиентам, что, например, игрока номер 2, только что офигенно разнесло на куски взрывом гранаты, а клиент уж сам решает рисовать эти куски или нет,
в зависимости от игровых настроек. Само собой игрок сидящий на сервере получает некоторые преимущества, так как вся информации крутится на его компе, особенно это заметно при плохом коннекте у клиентов. Что самое интересное, при такой организации сетевых взаимоотношений в движке можно не писать отдельно код для сингл-плейера и код для мултиплейера, просто при одиночной игре одна и та же машина выполняет роль как сервера, так и клиента. Все очень просто 🙂
Ну что, надеюсь ты понял, что написание движка дело довольно ответственное и это тебе не под «Ya mama» зажигать. Но я верю, что у тебя все получится, а если ты еще не почувствовал силы в своих руках, тогда не расстраивайся, мы сделаем все что сможем, чтобы помочь тебе в твоих девелоперских начинаниях. Главное не останавливайся перед трудностями и у тебя все получится!
Как написать собственный игровой движок на C++
Перевод статьи Джеффа Прешинга (Jeff Preshing) How to Write Your Own C++ Game Engine.
Как написать собственный игровой движок на C++
В последнее время я занят тем, что пишу игровой движок на C++. Я пользуюсь им для создания небольшой мобильной игры Hop Out. Вот ролик, записанный с моего iPhone 6. (Можете включить звук!)
Hop Out — та игра, в которую мне хочется играть самому: ретро-аркада с мультяшной 3D-графикой. Цель игры — перекрасить каждую из платформ, как в Q*Bert.
Hop Out всё ещё в разработке, но движок, который приводит её в действие, начинает принимать зрелые очертания, так что я решил поделиться здесь несколькими советами о разработке движка.
С чего бы кому-то хотеть написать игровой движок? Возможных причин много:
Игровые платформы в 2017-ом — мобильные, консоли и ПК — очень мощные и во многом похожи друг на друга. Разработка игрового движка перестала быть борьбой со слабым и редким железом, как это было в прошлом. По-моему, теперь это скорее борьба со сложностью вашего собственного произведения. Запросто можно сотворить монстра! Вот почему все советы в этой статье вращаются вокруг того, как сохранить код управляемым. Я объединил их в три группы:
Эти советы применимы к любому игровому движку. Я не собираюсь рассказывать, как написать шейдер, что такое октодерево или как добавить физику. Я полагаю, вы и так в курсе, что должны это знать — и во многом эти темы зависят от типа игры, которую вы хотите сделать. Вместо этого я сознательно выбрал темы, которые не освещаются широко — темы, которые я нахожу наиболее интересными, когда пытаюсь развеять завесу тайны над чем-либо.
Используйте итеративный подход
Мой первый совет — не задерживаясь заставьте что-нибудь (что угодно!) работать, затем повторите.
Вуаля! У меня появился замечательный вращающийся кубик, использующий OpenGL ES 2.0.
Моим следующим шагом было скачивание сделанной кем-то 3D-модели Марио. Я быстро написал черновой загрузчик OBJ-файлов — этот формат не так уж сложен — и подправил пример, чтобы он отрисовывал Марио вместо кубика. Ещё я интегрировал SDL_Image, чтобы загружать текстуры.
Затем я реализовал управление двумя стиками, чтобы перемещать Марио. (Поначалу я рассматривал идею создания dual-stick шутера. Впрочем, не с Марио).
Следующим делом я хотел познакомиться со скелетной анимацией, так что открыл Blender, создал модель щупальца и привязал к нему скелет из двух костей, которые колебались туда-сюда.
К тому моменту я отказался от формата OBJ и написал скрипт на Python для экспорта собственных JSON-файлов из Blender. Эти JSON-файлы описывали заскиненный меш, скелет и данные анимации. Я загружал эти файлы в игру с помощью библиотеки C++ JSON.
Как только всё заработало, я вернулся в Blender и создал более проработанного персонажа (Это был первый сделанный и зариганный мной трёхмерный человек. Я им весьма гордился.)
В течение следующих нескольких месяцев я сделал такие шаги:
Ключевой момент в следующем: я не планировал архитектуру движка до того как начал программировать. Это был осознанный выбор. Вместо этого я всего лишь писал максимально простой код, реализующий следующую часть функционала, затем смотрел на него, чтобы увидеть, какая архитектура возникла естественным образом. Под «архитектурой движка» я понимаю набор модулей, которые составляют игровой движок, зависимости между этими модулями и API для взаимодействия с каждым модулем.
Этот подход итеративен, потому что фокусируется на небольших практических результатах. Он хорошо работает при написании игрового движка, потому что на каждом шаге у вас есть работающая программа. Если что-то идёт не так, когда вы выделяете код в новый модуль, вы всегда можете сравнить изменения с кодом, который раньше работал. Разумеется, я предполагаю, что вы пользуетесь какой-нибудь системой контроля версий.
Готов поспорить, что больше времени тратится при противоположном подходе: пытаться заранее продумать архитектуру, которая будет делать всё, что вам понадобится. Две моих любимых статьи про опасности чрезмерной инженерии — The Vicious Circle of Generalization Томаша Дабровски и Don’t Let Architecture Astronauts Scare You Джоэла Спольски.
Я не говорю, что вы не должны решать проблемы на бумаге до того, как столкнётесь с ними в коде. Я также не утверждаю, что вам не следует заранее решить, какой функционал вам нужен. Например, я знал с самого начала, что хочу, чтобы движок загружал все ресурсы в фоновом потоке. Просто я не пытался спроектировать или реализовать этот функционал до тех пор, пока мой движок не начал загружать хоть какие-то ресурсы.
Итеративный подход дал мне куда более элегантную архитектуру, чем я мог бы вообразить, глядя на чистый лист бумаги. iOS-сборка моего движка сегодня на 100% состоит из оригинального кода, включая собственную математическую библиотеку, шаблоны контейнеров, систему рефлексии/сериализации, фреймворк рендеринга, физику и аудио микшер. У меня были причины писать каждый из этих модулей самостоятельно, но для вас это может быть необязательным. Вместо этого есть множество отличных библиотек с открытым исходным кодом и разрешительной лицензией, которые могут оказаться подходящими вашему движку. GLM, Bullet Physics и STB headers — лишь некоторые из интересных примеров.
Дважды подумайте, прежде чем слишком обобщать
Как программисты, мы стремимся избегать дублирования кода, и нам нравится, когда код следует единому стилю. Тем не менее, я думаю, что полезно не давать этим инстинктам управлять всеми решениями.
Время от времени нарушайте принцип DRY
95% времени повторное использование существующего кода — верный путь. Но если оно начинает вас сковывать или вы обнаруживаете, что усложняете что-то, однажды бывшее простым, спросите себя: не должна ли эта часть кодовой базы в действительности быть разделена надвое.
Использовать разные соглашения о вызове — это нормально
Одна из вещей, которая мне не нравится в Java — то, что она заставляет вас определять каждую функцию внутри класса. По-моему, это бессмысленно. Это может придать вашему коду более единообразный вид, но также поощряет переусложнение и не поддерживает итеративный подход, описанный мной ранее.
А ещё есть динамическая диспетчеризация, которая является формой полиморфизма. Часто нам нужно вызвать функцию объекта, не зная точного типа этого объекта. Первый порыв программиста на C++ — определить абстрактный базовый класс с виртуальными функциями, затем перегрузить эти функции в производном классе. Работает, но это лишь одна из техник. Существуют и другие методы динамической диспетчеризации, которые не привносят так много дополнительного кода, или имеют другие преимущества:
Осознайте, что сериализация — обширная тема
Сериализация — это преобразование объектов времени выполнения в последовательность байтов и обратно. Другими словами, сохранение и загрузка данных.
При подготовке такого пайплайна выбор форматов файлов на каждой из стадий остаётся за вами. Вы можете определить несколько собственных форматов, и они могут эволюционировать в процессе того как вы добавляете функциональность в движок. В то время как они эволюционируют, у вас может возникнуть необходимость сохранить совместимость некоторых программ с ранее сохранёнными файлами. Не важно в каком формате, в конце концов вам придётся сериализовать их в C++.
Можно писать более гибкий, менее подверженный ошибкам код сериализации, пользуясь преимуществом рефлексии — а именно, созданием данных времени выполнения, описывающих расположение ваших C++ типов. Чтобы получить краткое представление о том, как рефлексия может помочь с сериализацией, взглянем на то, как это делает Blender, проект с открытым исходным кодом.
Как и Blender, многие игровые движки — и связанные с ними инструменты — создают и используют собственные данные рефлексии. Есть много способов делать это: вы можете разбирать собственный исходный код на C/C++, чтобы извлечь информацию о типах, как это делает Blender. Можете создать отдельный язык описания данных и написать инструмент для генерации описаний типов и данных рефлексии C++ из этого языка. Можете использовать макросы препроцессора и шаблоны C++ для генерации данных рефлексии во время выполнения. И как только у вас под рукой появятся данные рефлексии, открываются бесчисленные способы написать общий сериализатор поверх всего этого.
Несомненно, я упускаю множество деталей. В этой статье я хотел только показать, что есть много способов сериализовать данные, некоторые из которых очень сложны. Программисты просто не обсуждают сериализацию столько же, сколько другие системы движка, даже несмотря на то, что большинство других систем зависят от неё. Например, из 96 программистских докладов GDC 2017, я насчитал 31 доклад о графике, 11 об онлайне, 10 об инструментах, 3 о физике, 2 об аудио — и только один, касающийся непосредственно сериализации.
Как минимум, постарайтесь представить, насколько сложными будут ваши требования. Если вы делаете маленькую игру вроде Flappy Bird, с несколькими ассетами, вам скорее всего не придётся много думать о сериализации. Вероятно, вы можете загружать текстуры напрямую из PNG и этого будет достаточно. Если вам нужен компактный бинарный формат с обратной совместимостью, но вы не хотите разрабатывать свой — взгляните на сторонние библиотеки, такие как Cereal или Boost.Serialization. Не думаю, что Google Protocol Buffers идеально подходят для сериализации игровых ресурсов, но они всё равно стоят изучения.
Написание игрового движка — даже маленького — большое предприятие. Я мог бы сказать намного больше, но, если честно, самый полезный совет, который я могу придумать для статьи такой длины: работайте итеративно, слегка сопротивляйтесь тяге к обобщению кода, и помните, что сериализация — обширная тема, так что понадобится выбрать подходящую стратегию. Мой опыт показывает, что каждый из этих пунктов может стать камнем преткновения, если его игнорировать.
Я люблю сравнивать наблюдения по этой теме, так что мне очень интересно услышать мнение других разработчиков. Если вы писали движок, привел ли ваш опыт к тем же выводам? А если не писали или ещё только собираетесь, ваши мысли мне тоже интересны. Что вы считаете хорошим ресурсом для обучения? Какие аспекты ещё кажутся вам загадочными? Не стесняйтесь оставлять комментарии ниже или свяжитесь со мной через Twitter.