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

Подготовка Visual Studio
Создадим новый проект в Visual Studio. Обратите внимание, что проект требует библиотеку SFML, поэтому если вы не настраивали окружение для нее, прочтите сначала небольшое руководство по настройке.
- Откройте Visual Studio и выберите File | New Project. В левом меню отметьте язык C++ и шаблон HelloSFML. Назовите проект Simple Game Engine.
- Выберите правой кнопкой мыши файл HelloSFML.cpp в окне Solution Explorer (под Source Files), далее – Rename и назовите файл Main. Это подходящее имя, так как файл будет содержать основную функцию.
- Откройте Main.cpp, удалите все содержимое файла.
- Переместите файл библиотеки SFML.dll из каталога Диск:\SFML\bin в Диск:\Visual Studio Stuff\Projects\Simple Game Engine\Simple Game Engine. Если названия в путях отличаются, вероятно, вы сделали отличную от руководства настройку.
Теперь можно приступить к коду. Исходный код и дополнительные ресурсы будут доступны на этой странице.
Проектируем собственный игровой движок
Самое важное – запуск движка, который будет происходить в файле 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 расширяется и адаптируется под другие объекты, так что дайте волю фантазии и попробуйте поэкспериментировать с окружением.
Game Engine своими руками #1
Сегодня мы поговорим об игровых движках, причем не о том, как и где их можно надыбать (об этом уже много говорилось в твоем любимом Х.), а о том, как самому написать и обустроить этот самый движок, чтобы потом не было стыдно ни тебе, ни мне. Дело это, скажу сразу, довольно сложное, причем сложное на столько, что на нем можно хорошо заработать и прославиться, чем многие программеры и пользуются. Я не буду пытаться рассказывать тебе о том, как сделать хорошую игру (может быть в следующий раз), не буду рассказывать и о том, как написать ту или иную часть движка, в этой статье ты вообще не увидишь ни одной строчки кода, а лучше расскажу о том, о чем следует хорошенько подумать прежде,
чем бросаться писать свой движок, и как в
общем можно организовать этот процесс, чтобы добиться хорошего результата малой кровью.
Если ты решил написать игровой движок, то значит ты уже хоть что-то знаешь, а как минимум неплохо владеешь тем или иным языком программирования. Если ты вынес со школьной скамьи основы Бейсика, то тебе, конечно, выбирать не приходится, но я, все же, не советовал бы тебе браться за игру, пока ты не овладеешь более совершенным инструментом. Если ты пишешь только на Delphi, и пишешь довольно долго, то у тебя есть хоть какие-то шансы выжить, но в идеале желательно овладеть языком Си, все-таки все солидные игры пишутся сегодня именно на нем. Лучше всего для нашего дела подойдет Visual C++ от Microsoft, желательно шестой версии, хотя это и не принципиально. Если ты долгое время общался с другими компиляторами, например от Borland, то тебе, конечно, имеет смысл оставаться в родной среде.
Прежде, чем приступать к бурным дискуссиям, я хочу убедиться, что твои представления о понятии «игровой движок» совпадают с моими. И так, игровой движок или game engine — это небольшая ОС в рамках игры, которая предоставляет набор базовых функций с помощью, которых игра устанавливает интерфейс с пользователем т.е. движком называется весь тот код, который отвечает за вывод графики, воспроизведение звуков, работу с сетью и т.д. , и который не касается правил самой игры. Надеюсь, с этим все ясно.
Прежде, чем писать движок, надо точно определиться с его специализацией и возможностями. Конечно, можно сделать игру и без предварительных планировок и раздумий, но поверь мне, тебе будет намного легче потом, если ты потратишь немного времени и обдумаешь все детали проекта сейчас. Первое, о чем стоит задуматься так это тип игры, для которой твой движок будет предназначен: будет ли это продвинутая 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» зажигать. Но я верю, что у тебя все получится, а если ты еще не почувствовал силы в своих руках, тогда не расстраивайся, мы сделаем все что сможем, чтобы помочь тебе в твоих девелоперских начинаниях. Главное не останавливайся перед трудностями и у тебя все получится!
Разработка собственного движка, напутствие
Это не тутор, а скорее моё виденье проблемы «для чайников». Ниже я лишь опишу самые вводные в тему, и наведу на мысли что и как может делаться. На полноту не претендую, поэтому самых умных жду в комментариях с пожеланиями и угрозами.
АТЕНШОН, МНОГА ТЕКСТА НЕТ ПИКЧ
Реклама своей игры как «написанная на собственном движке» это интересная затея, но явно не оправдывает себя, если твоя цель именно сделать игру, а не развлечься.
Разработка полноценного движка, о котором так мечтают люди начавшие свой путь в гейдеве, это время затратная вещь. Любой игровой движок состоит из компонентов основных на каких-то абстракциях, и разница между «сильным» и «слабым» инструментом — в количестве этих компонентов.
Поясню — что бы написать минимально рабочую игру, тебе нужно: обработка ввода, вывод графики и игровой цикл с логикой. Можно сказать — «всего 3 компоненты и ты уже можешь делать свои крестики-нолики или тетрисы!». Но для звания «великого игрового движка», при мысли о котором ты возбуждаешься, явно не хватает всего 3х компонент. Различные механизмы подгрузки данных, гибкие системы рендера, система скриптов, встроенные редакторы уровней — всё это требует времени на реализацию, и чем больше ты хочешь, тем больше времени у тебя уйдёт на разработку «движка с 0», а сколько времени потратится на написание самой игры?
Будь реалистом, и подумай над тем, что бы написать свою игру на уже имеющихся инструментах. Это сэкономит тебе кучу времени и добавит пару галочек в твоё портфолио. Не бойся тратить деньги на коммерческие движки, если от этого зависит как быстро ты сможешь выйти на рынок. (есть риск что не окупится)
Для начала нужно подвести черту между двумя кардинально разными подходами в написании этих ваших «движков». Я обозвал их так.
- «Динамические движи», это те в которых вся игровая логика содержится внутри объектов, которые можно передать в другие бинарные модули. (Прим — .dll библиотеки)
- «Статические движки», это те в которых вся логика содержится внутри самого движка и определяется на этапе компиляции.
Какие нюансы я обнаружил при такой классификации?
«Статические движки»
- Проще в написании и требуют минимального порога вхождения.
- Весь код собирается один раз, из-за чего компилятор может провести оптимизации, нужные конкретно для описанной логики.
- Имеются хорошие инструменты «отладки» для многих базовых библиотечных функций.
- Нет возможности модифицировать собранную игровую логику. Что решается понятием «скриптов», которые используя логику игрового движка, способны расширить уже описанную игру. (Самая геморная часть)
«Динамические движки»
- Требуют глубокого понимания матчасти, из-за виртуализации объектов.
- Код можно разбить на независимые модули и подключать буквально на лету. В некоторых играх есть моды которые реализуют «движок в движке», которые используют другие подключаемые модули.
- Большинство инструментов для отладки придётся писать самому, если это не встроено в сам язык. То есть, если ты не пишешь на .NET/JAVA/Lua?
- «Скрипты» могут быть реализованы в виде скомпилированных бинарников обёрнутого в объекты, от чего «потанцевал» при добавлении контента может быть больше (в плане производительности).
Большинство «индюшачьих» игровых движков описаны как «статические». Их гораздо проще писать и ещё проще поддерживать. Использование «динамического» подхода, даст тебе ненужную модульность. Возможно написание своего крутого интерфейса для аллокатора памяти хорошая затея, но ты бы мог потратить это время делая игру, а не натирать цепь разобранного велосипеда.
*** Рекомендация — пиши «статику»
*** Самый наивный способ сделать «динамику» — писать классы с виртуальными методами или их аналогами.
Это боль. (вся глава моё нытьё. )
Первая мысль которая должна посетить твою голову — как распространить игру на максимально широкую аудиторию.
Ты можешь использовать кроссплатформенные библиотеки, но их функционал крайне ограничен, так как они пытаются создать абстракцию которая «будет жить на любой платформе». Ты конечно можете написать свою библиотеку. Но вот в чомъ мем
Проблема в том, что платформы достаточно сильно отличаются даже в самых базовых вещах. Как пример — Windows при старте программы не имеет потоков ввода-вывода и для этого ей нужно создавать консоль, но консоль можно создать 2 различными способами и у каждого способа есть куча параметров, для лююююбых потребностей. В Linux же всё проще, у тебя со старта приложения есть потоки ввода-вывода и что бы «увидеть их» нужно просто перенаправить их в файл или создать окно-терминал. КАК ты даже ТАКУЮ простую проблему будешь решать, я не представляю. (я это уже сделал)
Чего уже говорить о создании графического контекста.
Вот ты пытался в OpenGL на Windows? Знаешь что на официальном сайте написано использовать GetDC() при связывании контекста окна и OpenGL? А ты знал что это DC на самом деле может быть принтером? Может быть целым монитором? Видеокартой? Просто окном? «Ресурсом менеджера окон»? А то что в Linux ты должен ручками выбрать нужный тебе монитор для запуска? Всякие драйверы-сервера запускать? И не один. И это надо как-то собрать в одну единую абстракцию и непринуждённо дёргать по воле случая.
Не страдай хернёй, я просто пытаясь понять как это всё работает не одну неделю убил —> используй готовые библиотеки. Их качество оставляет желать лучшего, но это избавит тебя от большой части головняка, который ты можешь себе вообразить, и позволит тебе сконцентрироваться на написании самой игры. (не исключается наличие багов в самих библиотеках)***
И всё же. Написать(наговнокодить) хотя бы простенькое приложение на API платформы, как по мне, важно. Так, ты будешь лучше понимать, что в конкретной системе значат абстракции которые дают фреймворки\языки.
В начале статьи я ввёл понятие «компонента». На самом деле я называю так любую штуку «которая делает что-то другое» )))
Любой код можно разбить на некое число компонент-модулей. Самое примитивное приложение состоит всего из 3 модулей
- Обработка ввода
- Вывод изображения
- Логика (зачастую на стейт машинах)
Уже это позволяет писать тебе довольно простые игры. Скажем крестики нолики или «давилку мух»? Давай вместе представим как оно может работать.
- Загружаем нужные ресурсы
- — В цикле.
- Рисуем: фон > картиночки
- Обрабатываем ввод
— Если нажата мышка, проверяем хитбоксы объектов
— Если прошли проверку, меняем «игровое состояние» - (для «давилки мух») Раз в Х циклов запускаем новую муху по некоторой траектории. И обрабатываем перемещение живых мух.
И это уже можно назвать игрой. «Погоди» — скажешь ты — «Но фактически тут 4 модуля, загрузка ресурсов!!». Хах, но если ты чуть-чуть разбираешься в линковке приложения, то ты можешь вшить данные в бинарник) И тебе не нужно будет загружать никаких данных. Поведение мух, также, вшито в движок и относится к логике, потому фактически тут именно 3 модуля.
Но это не будет игровым движком, а скорее просто игрой. Поэтому надо выделить чем именно должен заниматься движок.
Я бы сказал так — «движок занимается обработкой пользовательских данных и может иметь изменяемую логику»
Из такого определения давай же опишем некий «сферический движок для платформера».
Для начала, тебе потребуется описать больше чем один модуль, а логика самого движка будет гораздо сложнее.
- Конкретная загрузка ресурсов
- Обработка ввода, который конвертируется в абстракции
- Вывод изображения, который обходит множество объектов
- Игровая логика, которая уже разбита на несколько частей
— Обработка загрузки-переходов уровней
— Информация о сцене
— Нахождение коллизий и проверки на триггеры
— Обработка поведения ИИ?
— Обработка ввода от игрока
— Скриптовый процессор
Теперь, как прошлый раз, давай разберём поэтапно, что и как движок должен делать.
— Для начала опишем «минимальную» логику.
- Информация о сцене (уровне)
— Какой фон
— Набор тайлсетов (2D матрица)
— Предметы
— Юниты
*** Всё кроме фона должно иметь информацию о себе, типа — «текстура», «какие-то свойства», есть ли «триггер», «скрит» или «поведение» - Вшитые в движок «примитивы»
— Триггеры (какие бывают, что проверяют, что вызывают. прим — если хибоксы пересекаются — запускает смену уровня)
— Поведение (хардкодное поведение, что делает объект. прим — триггернутый предмет увеличивает свойство Х, у объекта который триггернул. Объект Х движется в одну сторону и случайное время меняет направление. Это можно оформить в виде «функций» для скриптового языка. )
— Свойства (Что за свойства, определяемые скриптами свойства)
— Процессор скриптов - Обработка передвижений-триггеров-физики?
Ну и сам игровой цикл будет состоять примерно из такой логики
- Загружаем информацию об уровне (описание уровня)
- Загружаем нужные ресурсы (текстуры и скрипты)
- — В цикле.
- Рисуем: фон > тайлест > предметы > юниты
- Обрабатываем ввод
— Абстрагируемся от кнопок и просто передаём «Идти влево» - Обрабатываем логику
— Проходимся по всем единицам со скриптами
— Проходимся по тем, у кого базовое поведение
— Что-то делаем с игроком?
— Проверяем коллизии-физику? (вызываем триггеры если что-то нашли)
— Двигаем юнитов-предметы
Тут нету анимаций. А первое время лучше не трогать скриптовый процессор, а задавать поведение явно. Не зря же мы добавили отдельный модуль который занимается именно этим?
И. Вот примерно в таком виде это уже можно назвать своим полноценным движком! Ахаха. Можно постоянно добавлять функционал, добавить графические эффекты, анимации или сделать меню с кнопками. В какой-то момент ты поймёшь, что хардкодить объекты не самая лучшая затея и перепишешь свой код, который будет использовать какие-то обобщённые абстракции. В целом, писать не так уж и много, не так ли?
Поясню за скрипты — для многих это будет открытием, но скрипты можно делать и в виде виртуальной машины-процессора. Твоя задача будет — написать парсер текста, описать виртуальную машину и придумать как именно получать информацию о сцене-объектах. В качестве примера, можно использовать многострадальные стековые процессоры, по типу forth. Они очень легко делаются, но к их логике нужно привыкать. По сути это единственный выход написать «быстро» ваш собственный «скриптовый процессор».
*** Обязательно сделай вывод логов при сборке скрипта, или при его работе.
*** Старайся избегать логики типа [INT a += «Lord»]. Писать нетипизрованный код опасно, но можно выделить отдельные команды для работы с конкретными типами.
*** ДА, ТЫ БУДЕШЬ ПИСАТЬ СКРИПТЫ НА АССЕМБЛЕРЕ.
*** Для написания простого ассемблера, хватит и знаний типа — Sting.indexof(«ADD»); и подобного говнокода. Но что бы написать нормально, или хотя бы простенький язык, вам нужны знания о «регулярных выражениях» или «парсерных комбинаторах».
*** Не надо упарываться в «полноценный язык», посмотрите как писались языки программирования в бородатых 80х, даже тот же Pascal. Они работают просто и честно, такие реализации займут у вас в разы меньше времени, чем описание «очередного» . C\Rust\Haskell.
В целом написать некий «движок в вакууме» не такая сложная задача, как кажется. На ранних этапах большинство поведений-абстракций можно вшивать в движок. Да и в целом, большинство вещей работают довольно просто, и требуют от вас лишь знаний и понимания. Но я напомню, написать свой движок для игры — плохо. Это отнимает огромное количество времени, которое вы можете потенциально потратить на разработку самой игры. А любая гордость проходит, после осознания того, что ты делал свою игру примерно 20% времени пока писал код.
Изначально, решился написать эту статью, ради привлечения инвестиций, на время разработки своей «базовой +18 новеллы». (инициатива друга)
Так что ты это, кнопочку то нажми, а?
В целом, если попрёт, у меня есть уже более конкретные истории с чем я сталкивался и как я решал какие-то проблемы. Я в основном концентрируюсь на низкоуровневых абстракциях, отдавая на откуп всю логику подключаемым модулям, которыми управляет специальный планировщик. Потому и проблемы у меня соответствующие. И в качестве примера.
Вот первое что пришло в голову, из самого простого.
- Как лучше работать с памятью-данными.
- Проблемы STL библиотек. (C++)
- Детали виртуализации объектов, микро рекомендации.
- Как можно делать «моды».
- Профилирование и Логирование, почему это важно.
- Что нужно знать о многопотоке, вводные-подводные.
- Асинхронно или параллельно? Как это работает?
- Работа с текстом-кодировками, и почему это настоящий ад.
На счёт моего кода — не будет опенсорса. Когда я релизнусь, я выложу лишь API к бинарнику. По сути, я использую «динамический» подход, который описывал выше. Потому запустить свою игру, можно будет просто собрав .dll и запустить бинарник запихнув либу в аргументы строки. Но вот когда это случится. Когда я перестану морить себя голодом? Кто знает
PS — Под конец получилось немного сумбурно, ибо я писал всё за один заход. В целом я описал лишь поверхностно многие вещи. Если будет спрос, могу углубится.
PPS — Мне тут говорят что бы я сделал бусти и публиковался впредь там, раз в месяцок публикуя «фри контент». Может в следующий раз? Я просто не знаю о чём там можно писать, лол.
PPPS — «Статья слишкам длинная устал читать, пиши кароче»
Как и зачем создавать собственный игровой движок
Игра с нуля — интересный челлендж для разработчика. Но если хотите пройти его на сложности Nightmare, можно еще и сделать собственный игровой движок, заточенный специально под проект. Подводных камней много, рассказываем, что важно знать при разработке такого гейм-дизайнерского софта, и что в него добавить.
Итак, вы задумались о создании собственного игрового движка. Отлично! У этого варианта множество плюсов в сравнении с использованием коммерческого — такого как Unity или Unreal. В этой статье разберемся, зачем разрабатывать свой движок, какие системы необходимо предусмотреть и как правильно подойти к процессу.
Зачем?
Начнем с главного вопроса, который стоит задать себе, если вы решили разработать собственный движок: зачем это вам?
Резонными причинами могут быть, например, такие:
Хотите создать игру с использованием новой технологии, которую не поддерживают другие движки. Или поддерживают, но реализация слишком сложна и костыльна. Это может быть масштабная симуляция (Factorio), нестандартный проект, который не вписывается в готовые шаблоны (Noita, Miegakure), и множество других идей. В таких случаях нет иного выхода кроме как писать собственный движок под проект.
Хотите оптимизировать рабочий процесс под игры, которые создаете. Если для проекта не нужен полный объем возможностей коммерческого движка, есть смысл создать кастомизированный вариант с подходящими для конкретной игры редакторами и функциями. Если движок не затачивается под конкретный проект, стоит задуматься, так ли нужно самописное решение или все-таки достаточно готовых вариантов?
Не хотите в долгосрочной перспективе зависеть от чужих технологий. Если вы хотите иметь полный контроль над проектом, готовы самостоятельно исправлять ошибки (а не сидеть в ожидании багфикса от создателей) и не бояться, что очередное обновление сломает игру, то собственный движок — подходящий вариант! И не придется зависеть от прихотей крупных корпораций
Вам интересно разобраться, как устроены и работают игровые движки. Это отличная причина — по правде говоря, самый веский повод заняться разработкой собственного движка.
Раз уж начали составлять списки, то вот несколько неудачных причин браться за разработку движка. Если найдете свою среди этих пунктов — притормозите и подумайте еще раз.
Вам кажется, что вы придумаете движок покруче, чем Unity или Unreal (или Godot, или GameMaker). Не выйдет. Разработать подходящий для специфических нужд софт можно (см. предыдущий список), но в одиночку или маленькой командой невозможно создать универсальный движок, который будет конкурировать с известным универсальным ПО. Особенно при первой попытке.
Думаете, что иначе вы «ненастоящий программист»? Использование готового движка не делает гейм-разработчика хуже. Для того они и придуманы! Это просто инструмент для создания игр. 99% проектов можно разработать при помощи уже существующего софта — в этом нет ничего постыдного. Ведь главное — это сама игра!
Если вы хотите таким образом сэкономить время или деньги — забудьте! Создавать движок с нуля долго, а время = деньги. Использовать готовый софт выгоднее, чем пытаться разработать собственный. В долгосрочной перспективе это может стать выигрышной стратегией, но только если движок будет основой для нескольких прибыльных проектов, и при этом значительно удобнее в работе, чем коммерческие. Такое ПО разработать сложно, особенно если это первый опыт (и почти невозможно, если речь о 3D).
При принятии решения учитывайте свой опыт и цели. Чем меньше практики в создании игр, тем сложнее окажется разработка движка — обязательно потренируйтесь прежде чем браться за игровое ПО.
Я начинал с флэш-игр в 90-00х, и ни один движок того времени не поддерживал импорт флэш-анимаций. Единственным выходом было создать собственный софт. Намного приятнее и быстрее закидывать swf-файлы в папку с ресурсами и сразу использовать анимации в игре без промежуточных шагов типа экспорта в списки спрайтов.
Конечно,целесообразность создания собственного движка во многом зависит от количества опыта как в геймдеве, так и в программировании. Приятно сделать кастомный софт, не гуглить постоянно туториалы, и самостоятельно дебажить возникающие ошибки. В то же время, достаточно допустить пару оплошностей, и проект развалится, а обратиться за советом будет некуда. Собственный движок — это полный контроль, но и полная ответственность за продукт.
Игровой движок — это рабочая среда, в которой создают игры. Он состоит из базы, на которой строится проект, и деталей, из которых, словно из деталей лего, состоит сама игра. Это золотая середина между «логикой игры» и «скучными техническими штуками»: благодаря движку в игровом коде не приходится вручную прописывать, как отобразить на экране условный треугольник, можно сразу заняться взаимодействием элементов.
Разные движки выполняют за вас разное количество работы. Некоторые просто отображают графику на экране (Flash, Pico-8). Другие сами по себе — целая игра с возможностью кастомизации или узко заточены под определенный жанр (RPGMaker, Ren’Py). А между ними — бесчисленное количество вариантов.
Игровые движки обычно основываются на простых фреймворках типа SDL и OpenGL, и включают в себя специализированные библиотеки для аудио, видео, физических и математических вычислений и чего угодно еще. При создании движка нет необходимости каждую мелочь прописывать вручную, практически на каждую потенциально полезную опцию доступна соответствующая библиотека.
Базовые функции движка.
Это основы, необходимые для того, чтобы начать создавать игры.
Инициализация системы.
Приводит программу в боевую готовность после запуска — открывает окно, загружает данные. С этим (и не только!) справится стандартная библиотека SDL, проще ее и использовать.
Контроль частоты кадров
Ограничивает частоту сменяемости кадров для плавного изображения и оптимизации работы.
Существует много способов реагирования на нажатия кнопок или движения джойстика, и обычно с этим справляется ранее упомянутый SDL. Так что если он используется для инициализации, больше ничего дополнительно ставить не надо. Поверх него можно построить мощную и гибкую систему ввода, но для начала хватит и дефолтной.
Рендеринг
Большинство (ну как минимум 75%) игр так или иначе используют графику, и за нее отвечает как раз движок. В 2D-игре минимальному рендеру достаточно отображать на экране текстурированные четырехугольники. Шейдеры, буферы вершин, однобуферная прорисовка, меши, материалы и так далее — это прекрасные опции, которые можно добавить позднее, если понадобится. Если хочется заморочиться с OpenGL или Vulkan и кастомизировать рендерер — на здоровье! Но помните, нет ничего постыдного в том, чтобы использовать для рендеринга готовые библиотеки типа Ogre3D. Выбор зависит от целей и потребностей разработчика, а также от того, какие задачи интереснее решать самостоятельно.
Математические и прочие утилиты
Желательно, чтобы к этим библиотекам имели доступ как игровой код, так и движок. Плюс — ко всем иным полезным функциям и формулам, которые вы найдете в процессе разработки. STB — отличный ресурс для поиска всевозможных утилит, которые могут пригодиться при создании движка.
Дополнительные функции
Еще несколько систем, которые лучше добавить в движок ближе к делу, когда они непосредственно понадобятся для игры:
Управление игровыми объектами и сценами
Можно кодить и вручную, но практичнее предусмотреть систему для обработки отдельных игровых объектов и коллекций. Это один из ключевых механизмов в движке, ведь он управляет логикой игры. Из каких компонентов состоят объекты, на какие типы событий реагируют, как происходит взаимодействие, что со структурой памяти, используется ECS? (Кстати, «чистый» неадаптированный ECS лучше применять только для специфических кейсов.) Эти и не только вопросы должна покрывать система управления объектами и сценами. Для таких задач доступны готовые библиотеки (особенно для чистого ECS), но, поскольку эта структура сильнее остальных влияет на игровой код, я склоняюсь к принципу «сделай сам». Использование существующего решения вынудит постоянно думать о том, как вписать логику игры в рамки. А надо наоборот — адаптировать движок под выражение задуманной игровой логики.
Аудио
Звуковые эффекты и музыка здесь разделены, хотя и прячутся под одним названием. Основные необходимые функции — это запуск и остановка звуковых циклов и воспроизведение звуковых эффектов от начала до конца. Этим аудио не ограничивается, но даже с двумя базовыми опциями можно далеко продвинуться. Минус в том, что стандартные звуковые фреймворки (FMod and Wwise) — коммерческие и с кучей лицензионных ограничений. Однако большинство ресурсов с открытым кодом раздражают неудобством (передаю привет OpenAL). Сам я использую FAudio — на мой вкус, простая и комфортная в использовании база для построения сложных звуковых механик.
Загрузка и управление файлами
Файлы загружают все игры. Вряд ли вам захочется вручную повторно загружать и декодировать уже добавленные файлы, так что понадобится система, которая этим займется. В будущем загрузчику файлов можно добавить и другие функции — например, поддержку модов или динамическую выгрузку. Это не срочно — поначалу можно использовать встроенный менеджер, но со временем файлов станет так много, что понадобится удобная система управления файлами и ресурсами.
Нетворкинг
Окей, нетворкинг (онлайн-мультиплеер) — это ОЧЕНЬ опционально. Если не планируется режим p2p, то и не заморачивайтесь. Однако эту систему чрезвычайно сложно встроить в движок, который разработан не с расчетом на многопользовательские игры. Поэтому, если вы планируете или допускаете добавление мультиплеера, подготовьте почву заранее, потому что иначе придется переделывать все системы.
Это базовый набор систем, которые входят в игровой движок. Другие варианты типа обнаружения столкновений, физики, сериализации, анимации и UI уже опциональны. Они распространены, поэтому входят в большинство движков, но для создания игр не обязательны. Например, предотвращение столкновений можно обеспечить при помощи математических утилит и прописать алгоритм в коде игры. Простейшую гравитацию и ускорение можно настроить без физических движков типа Box2D or Bullet. А полная сериализация вообще лишняя, если нужно попросту сохранить чекпойнт.
В самописном движке однозначно будет меньше систем и функций, чем в универсальном коммерческом. Такова цель! Unity и Unreal — огромные монолиты, и каждая отдельная игра использует лишь малую часть предложенных опций. Добавляйте только то, что нужно для вашего конкретного кейса и сосредоточьтесь на том, чтобы сделать инструменты разработки лучше и комфортнее в использовании.
Озанкомьтесь с тем, как работают другие игровые движки, прежде чем браться за собственный. Разберитесь, какие парадигмы и алгоритмы они используют, что реализовано классно, а что раздражает. Попробуйте создать мини-игру на нескольких движках, чтобы понять, как они устроены.
Итак, вы взвесили за и против, поняли, чего хотите, и решили все-таки взяться за создание движка. И как же это сделать?
Сразу к делу: делайте игру параллельно с разработкой движка. Это правило нельзя нарушать. Изучите основы как можно скорее и сразу же начинайте создавать на этой базе игру. Движок — ничто без игры.
Это необходимо, потому что функционал движка должен соответствовать потребностям сделанных на нем игр. Нельзя понять, как построить хорошую анимационную систему, если проект не требует сложной анимации. Слабые места движка проявляются в процессе написания игры. Может быть, нужна древовидная система, благодаря которой дальние объекты не будут рендериться, пока не приблизятся на определенное расстояние? Я не знаю, и вы не узнаете, пока не соберете игровой уровень, который будет страшно зависать. И даже тогда проблема может оказаться не в обновлении объектов — чтобы понять, надо проверить.
Не программируйте того, что не нужно. Если единственный UI в игре — кнопка Play в главном меню, поздравляю! Не придется создавать мудреный пользовательский интерфейс. В The End Is Nigh нет ни физического движка, ни детектора столкновений. Там даже нет камеры, потому что она там не нужна. Я использовал электронную таблицу .csv, чтобы собрать карту мира, вместо всяких сложных редакторов. Делается легко и нормально работает.
Не буду вдаваться в подробности реализации — способов слишком много, каждый подходит для определенных случаев. Нет «наилучшего варианта рендеринга» или «самого правильного способа управления объектами». Все зависит от игры. Начинайте с основ и расширяйтесь по мере необходимости.
Что касается языков программирования — выбирайте, каким лучше владеете. Разработка движка — сама по себе непроста, а если делать это параллельно с изучением С++, обе эти задачи станут в два раза сложнее. C# идеально подойдет для создания движка. Медленнее, чем на С++, но не критично. Более медленный язык типа Python может вызвать затруднения, если в игре много движущихся объектов… но для некоторых игр пойдет. Используйте, что удобно.
И еще — с первой попытки идеально не получится. Моей первой игрой на самописном движке стала Closure, и в ней полный бардак (забавно, что ее номинировали на награду «Техническое совершенство» на фестивале независимых игр в 2010 году). Системы рендеринга и обновления вдвоем обрабатывали всю игру. Добавлять новые объекты было крайне трудоемко, приходилось дописывать кучу кода и работать с кривыми редакторами анимации, так что в итоге осталось с дюжину интерактивных предметов. У некоторых из них было несколько вариаций, кардинально менявших поведение объекта — это было проще, чем добавлять новые. Так что прожекторы, зеркала и турели по сути один и тот же объект!
Но с ошибками приходит и опыт. Движок Closure написан кое-как, но оказался достаточно хорош, чтобы запустить игру на PS3. Идея переписать некоторые части движка была заманчивой, но это лишь отложило бы выход игры. Вместо этого я писал заметки о том, что получилось плохо, чтобы учесть ошибки в следующий раз. Особенно о том, что мешало непосредственно созданию игры. То же и с The End is Nigh. В ее движке (который, кстати, НАМНОГО лучше, чем в Closure) все равно была куча ошибок, которые я решал, стиснув зубы. Как только игра вышла, я сразу начал улучшать движок для следующего проекта, исправлять раздражающие баги и добавлять новые функции.
И так раз за разом: учишься, создаешь игру, запускаешь, и все по новой. До тех пор, пока движок не станет действительно хорош.
Не стал вдаваться в технические подробности, как внедрить в движок каждую отдельную систему. Это зависит от конкретных вариантов использования, есть сотни способов — и каждый из них правильный. Понять, что вам подходит — ВОТ в чем суть разработки движка, с таким настроем стоит браться за создание собственных проектов.
Вот и все, что я хотел рассказать в этой статье. Скорее всего, она вас либо мотивировала на разработку собственного движка, либо окончательно отпугнула от этой идеи. Оба варианта хороши, если вы поняли, чего хотите сами.