Свой движок? Все «за» и «против»

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

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

Есть и категория людей, которые разрабатывают свой движок годами, постоянно переписывая и даже начиная с чистого листа. Я отношусь именно к этой категории – я задался целью создать свой движок в 2009 году, на тот момент имея весьма слабое представление о том, как он должен быть устроен. Страшно представить, через что я прошел за это время: чего стоит только эпическая смена языка (с C++ на D) или весьма болезненный переход с классического фиксированного конвейера OpenGL на современный шейдерный (что поделать – технологии не стоят на месте!). Техно-демка игры под кодовым названием Atrium, которая у меня получилась в итоге, стала довольно известной в кругах D-шников (и даже за их пределами) – а сейчас я работаю над новым графическим движком для Atrium, использующим OpenGL 4. Попутно я также написал собственный программный растеризатор, чем-то похожий на ранние движки id Software. Думаю, что 10-летний опыт работы с графическими API и низкоуровневым программированием графики позволяет мне дать несколько скромных советов начинающим. Я учился на собственных ошибках и ошибках многочисленных коллег по хобби – и теперь хочу рассказать о том, как не допустить их.

Для начала – типичные ошибки нуба:

  1. Постановка заведомо недостижимой цели. В свое время это называли созданием «второго Doom 3» или «убийцы Crysis» – многие, вдохновившись любимой AAA-игрой, загораются желанием создать что-то похожее. Естественно, что в одиночку это физически невозможно – поэтому ставьте реалистичные цели и постепенно повышайте планку по мере накопления опыта. На первых порах можно набросать краткий разумный список того, что должен уметь ваш движок, и постараться реализовать все это, после чего сделать какую-нибудь мини-игру или просто насыщенную графикой демку. Ваш движок не обязан быть мощным и сложным – но он должен быть продуманным и работающим, пусть и на уровне простых шариков-кубиков. Нет ничего хуже, чем попытаться прыгнуть выше головы и не суметь осуществить задуманное – именно на этом большинство и заваливаются.

  2. Разработка без определенной цели. Другая, противоположная крайность. Если вы не ставите перед собой конкретных задач, то ничего дельного в итоге и не получится. Если вы только начинаете изучать геймдев, то вам больше подойдут готовые движки – в них тоже можно неплохо баловаться. А разработка движка – все-таки не баловство, даже если вы и занимаетесь этим в свободное время для собственного удовольствия. Без четкого понимания, что вы делаете и зачем, у вас ничего не выйдет. Нужно сразу определиться – либо вы создаете конкретную игру/прототип/демку и движок для нее, либо универсальный движок для любых игр. Это две разные задачи, два пути, которые быстро расходятся и мало пересекаются. Создавать «движок ради движка» нет смысла – без конкретного применения от него не будет пользы ни вам самим, ни кому-либо еще. Такая разработка вам вскоре надоест, и вы просто потеряете на ней время и силы.

  3. Закрытая разработка. Вы, разумеется, вправе выкладывать в Интернет только скомпилированные версии своего движка – но не удивляйтесь, если ими никто не заинтересуется. Засекречивать исходники и беречь их от посторонних – удел коммерческих компаний. Для энтузиаста же хорошим тоном считается делиться своей работой с другими. Я, конечно, не призываю выкладывать сырой недописанный код, но, все же, чем раньше вы откроете свое детище общественности, тем лучше для вас самих – если вы создаете что-то действительно интересное, то вам обязательно помогут. Не факт, конечно, что люди будут писать за вас новые фичи, однако найдутся те, кто захочет протестировать ваши демки, скомпилировать ваши исходники под разные платформы. При этом будут всплывать баги, о которых вы даже не подозревали – а исправить их общими усилиями намного проще, чем в одиночку. Кто-то даст ценный совет или объяснит сложную вещь, кто-то сам поделится исходниками или другими полезными материалами. Люди будут появляться и исчезать, но их вклад останется – все лучшие OpenSource-проекты именно так и создаются. Не открывая код, вы заведомо лишаетесь ценной вещи – сотрудничества: очень многие любительские проекты с большим потенциалом сгинули именно из-за того, что были закрытыми. Не стоит пополнять их число – дайте сообществу возможность помочь вам!

  4. Желание поддерживать все технологии сразу. С таким императивом вы впадете в то, что называется over-engineering – то есть, сделаете движок более сложным, чем в действительности нужно. Не стоит добавлять в движок дополнительные технологии и возможности, если:

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

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

  1. Желание поддерживать все графические API. Это заблуждение проистекает из предыдущего, но оно настолько распространено, что я решил упомянуть его отдельным пунктом. Если вы не компания уровня Epic Games, то вам поддержка нескольких API просто не нужна. Принцип простой – если нужна кроссплатформенность, то используйте OpenGL, а если разрабатываете исключительно под Windows, берите Direct3D. Попытка поддерживать и то, и другое приведет к тому, что вместо игрового движка вы будете лепить монструозные абстракции и glue-код, писать транслятор шейдеров и решать прочие многочисленные проблемы, порожденные разницей в функциональности Direct3D и OpenGL. То же касается и поддержки разных версий внутри одного API – не стоит гнаться за всеми версиями сразу, выберите одну и делайте все на ней.

  2. Зацикленность на C++. Не спорю, многие современные игры написаны на нем. Если вы уже неплохо знаете C++, то, наверное, нет никаких причин не выбрать именно его. Но учить C++ с нуля только ради того, чтобы написать на нем игровой движок – увольте. На дворе уже не 90-е, сейчас есть масса более удобных и современных языков, которые сильно облегчат вам жизнь, не вынуждая жертвовать производительностью. Да, существуют узкие места и особые моменты, требующие ручной оптимизации, которую в высокоуровневых языках порой делать трудно. Но с повышением производительности компьютеров все эти хаки перестают быть необходимостью – основную нагрузку берет на себя GPU, а на каком языке написана CPU-сторона, уже не столь важно. Выберите язык, который вам понятен, которым вы можете овладеть в совершенстве, и пишите на нем. Лучше писать хороший код на Python, чем убогий код на C++.

Итак, вы поставили себе цель, составили список желаемых возможностей, выбрали язык и API. Как правильно начинать?

  1. Не пишите все с нуля, используйте готовые библиотеки. Например, при использовании OpenGL для создания окна и чтения ввода можно взять SDL. Для загрузки шрифтов есть Freetype, для 3D-моделей – Assimp, для игровой линейной алгербы – glm. Библиотеки есть практичеси для всех типовых задач в мультимедийном приложении. Мне лично пришлось написать большинство таких библиотек самостоятельно, поскольку я выбрал язык D, а библиотеки для C++ в нем использовать крайне затруднительно – тем не менее, я использую C-библиотеки, такие как SDL, Freetype и OpenAL.

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

  1. Не используйте как основу для движка код из уроков и примеров. Примеры создавались с особой целью – наглядно показать работу той или иной технологии. Авторы примеров вполне ожидаемо не реализуют «настоящий» игровой фреймворк, поскольку это затруднило бы чтение кода – вместо того, чтобы сразу вникнуть в технологию, читателю пришлось бы сначала изучать фреймворк. Лучше вообще свести бездумный копипаст к минимуму – возьмите за правило не копировать чужой код объемом больше 5-10 строк, а просто читать его и затем писать свой. Нет ничего хуже, когда программист не разбирается в собственном же коде – а копипаст обычно приводит именно к таким ситуациям.

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

  1. Очень часто движок на ранних стадиях пишется так, чтобы как можно быстрее получить картинку на экране. Это неправильно. Нужно с самого начала написать фреймворк, основу, которая в дальнейшем не будет меняться. Иначе вы будете раз за разом переписывать все «сверху вниз» (то есть, при добавлении высокоуровневых компонентов переписываются низкоуровневые), что является следствием плохого проектирования. О картинке можете не думать, пока у вас не будет пользовательского ввода, менеджера событий, управления памятью и ресурсами. Нужно постепенно наращивать функциональность – от общей и обязательной к частной и опциональной.

  2. В современном геймдеве есть несколько распространенных техник рендеринга, и вы должны изначально определиться с техникой. Будет ли ваш движок использовать PBR-материалы или классические Diffuse-Specular? HDR или LDR? Прямой рендеринг или отложенный? Можно, конечно, попытаться поддерживать все техники сразу, но спросите себя – нужно ли вам это? Излишне самоуверенных отсылаю к ошибке нуба №4.

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

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

  5. Помните, что графический движок – лишь один из компонентов, которые придется реализовать. В любой игре нужны средства вывода звука, большинству action-игр нужен физический движок. Часто физика в игровых движках – это «бедный родственник», которому уделяется куда меньше внимания, чем графике. А ведь какой бы красивой и навороченной ни была графика, без качественной физики и проверки столкновений она бесполезна – никто не захочет играть в игру, где персонажи застревают в стенах или проваливаются сквозь пол.

Вы можете попробовать написать свой физический движок, либо взять готовый. Обычно советуют второе, но я, судя по собственному опыту, не буду столь категоричен. Да, во многих случаях функциональности существующих физических движков более чем достаточно, но бывают ситуации, когда они ведут себя не так, как нужно, а то и вовсе чего-то не умеют – типичным примером является контроллер персонажа. Часто в движках кинематика не пересекается с динамикой, что оборачивается невозможностью для контроллера персонажа нормально взаимодействовать с динамическими телами – эти две системы связываются с помощью «нефизичных» хаков, которых в готовых решениях обычно нет. При этом контроллер персонажа – еще довольно распространенная штука. А сколько может возникнуть проблем с готовыми движками в различных нетривиальных игровых механиках – и представить сложно! Взять хотя бы игры с открытым миром и динамической подгрузкой окружения. Реализация реалистичной физики в таких играх – архисложная задача, решения которой единичны. Именно поэтому вопрос выбора физического движка сугубо индивидуален, и давать общие советы здесь не представляется возможным.

  1. Совершенно необязательно включать в движок поддержку всех мыслимых форматов 3D-моделей – большинство из них малопопулярны, и их загрузчики все равно не будут использоваться. Достаточно поддерживать два-три распространенных (например, OBJ, COLLADA, FBX), либо придумать свой собственный формат с четко заданной спецификацией и сделать движок расширяемым, чтобы пользователь мог самостоятельно добавить поддержку произвольного формата. Хорошим тоном является создание отдельной утилиты, которая конвертирует модели в понятный движку формат, либо предоставление готовых экспортеров в этот формат для популярных 3D-пакетов. Также неплохим решением является реализация загрузки моделей через библиотеку Assimp. Инструменты для создания контента вообще очень важны: чем проще пользователю загрузить свою модель в ваш движок, тем скорее он в нем освоится и захочет создавать на нем игры.

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

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

  1. В обязательном порядке протестируйте работу движка на разных компьютерах с разными операционными системами. Если вы сами не можете, то найдите тестировщика, который может. Как вариант – выпустите публичную альфа-версию движка, чтобы ее могли протестировать все желающие. Чем раньше вы убедитесь в работоспособности вашей разработки на всех основных платформах (Windows разных версий, Linux, macOS), тем лучше. Конечно, вы можете и отказаться от поддержки платформ, отличных от вашей, но в таком случае вы сильно сужаете свою потенциальную аудиторию, что для любительского проекта не очень-то хорошо. К примеру, линуксоиды обычно очень дружественны к персональным проектам и могут здорово помочь, но они не будут специально ставить Windows, чтобы запустить ваши демки. Если вы откроете исходники, вам может повезти – кто-нибудь поможет вам портировать ваше творение на Linux.

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

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

Вот вам еще несколько вдохновляющих примеров:

  • Николаус Гебхардт (Nikolaus Gebhardt) написал Irrlicht, один из самых популярных свободных движков.
  • OGRE начался как персональный проект Стива Стритинга (Steve Streeting).
  • Майк Лишке (Mike Lischke) в одиночку разрабатывал GLScene до версии 0.5.

Конечно, если у вас нет опыта работы с готовыми движками, то вряд ли с первого раза получится что-то стоящее. На самом деле, с первого раза мало что получается и у гениев. Если вы способны к самокритике, то вам самим не понравится ваше первое творение. Однако вы получите ценный опыт, который поможет в дальнейшем. Главное – не разочаровываться и не бояться начинать заново.

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


Copyright © 2008-2021 Тимур Гафаров и соавторы. Доступно по СС BY-NC-SA 3.0.