
…или почему жизненный путь игровых движков всегда приводит к одним и тем же результатам.
Вы когда-нибудь задумывались, почему самый дорогостоящий движок индустрии спроектирован так, что проще снести всё до основания и написать заново, чем пытаться разобраться в его хитросплетениях? Или почему легендарные студии годами процветают на кодовой базе, которую сами же создатели метко характеризуют как «г…код»? При этом идеально выверенные с архитектурной точки зрения движки часто уходят в небытие просто из-за исчерпания финансирования. Если примеры не всплыли в памяти сразу — в финале статьи я дам подсказки.
Любой движок, «оживляющий» реальный проект, проходит через четыре закономерные стадии: «пусть работает хотя бы как-нибудь», «работает, но показывать стыдно», «работает хорошо, но никто не понимает как» и, наконец, «работает на собственном проприетарном языке». Затем цикл замыкается, и начинается новый виток эволюции, когда команда осознает неизбежность перемен.
Сразу отмечу: этот материал — синтез размышлений, докладов с GDC, лекций по паттернам и кулуарных бесед с коллегами из крупных студий. Отсылки к публикациям двадцатилетней давности здесь не ради ностальгии, а ради истины. В эпоху, когда блоги «нулевых» исчезают из сети, выводы пятнадцатилетней давности остаются пугающе актуальными.
Архитекторы и проектировщики: разные роли, разные цели
Первое, что нужно осознать: архитектура движка и дизайн систем — это далеко не одно и то же. Когда этими задачами занимаются разные специалисты, результат получается достойным. Но когда все функции берет на себя один «бородатый гуру», выходит как в анекдоте: пытаясь одновременно вести девушку и целовать машину, ты рискуешь провалить оба дела.
Архитектура — это выстраивание процессов и достижение целей. Дизайн систем — это ювелирная работа с решениями и связями. Истинный дизайн начинается там, где оба варианта по-своему хороши, но уводят разработку в разные стороны. Выбор вектора в этот момент предопределит будущее проекта на годы вперед. В прототипе, где всё держится на честном слове, дизайнер часто выглядит лишним звеном, мешающим процессу. Однако его главная задача — минимизировать «трение» при написании кода и создании контента. Это две стихии, которые постоянно конфликтуют.
Гранулярность: гибкость <───────●──────> простота
Связанность: несвязанность <──●───────> запутанность (направо не ходить)
Гибкость: приложение <─────●──────> библиотека
Связность: удобство <───────●──────> ортогональность
Векторы, влияющие на эти ползунки, чаще продиктованы экономикой и человеческим ресурсом: количеством разработчиков, бюджетами и приоритетами бизнеса. Хороший системный архитектор видит картину шире, концентрируясь на фундаменте всей системы.
Паттерны — это не про синтаксис
Небольшое лингвистическое отступление. Термин «паттерн» в русском языке часто отождествляют с «шаблоном», но шаблон — это жесткая форма. Английский язык дает нам куда более богатый инструментарий для описания инженерных решений.
Многогранность паттернов
pattern не задает жесткую форму, а описывает силы и способы их уравновешивания.
template фиксированный «скелет» с местами под вставку (как C++ templates).
idiom специфический для языка «речевой оборот» (например, RAII).
blueprint буквально инженерный чертеж, требующий буквального исполнения.
archetype прообраз, задающий вектор, от которого допустимы отклонения.
motif узнаваемый элемент, не претендующий на глобальное решение.
schema структура данных, форма организации информации.
convention ничем не подкрепленная договоренность внутри команды.
paradigm фундаментальный способ мышления (ООП, функциональный подход).
Пусть терминологическая каша уже стала нормой, но важно помнить: «паттерн проектирования» — это всегда контекст. В каноническом понимании GoF — это чаще про design for change, где шаблоны, идиомы и чертежи сливаются в единый процесс.
Как бы гениален ни был архитектор, масштаб проблем предсказать сложно. Большинство проектов стартует с «быстрого и грязного» кодинга, но рано или поздно любой самописный движок (Фаза-0) встает перед необходимостью эволюции в конструктор (Фаза-1).
Всё есть объект…

Здесь код рождается в муках наследования. Появляется GameObject — абсолютный «божественный объект», знающий о вселенной всё. И есть GameLevel, который тащит на себе всю нагрузку. Связность становится запредельной: простой гоблин знает параметры небесных светил, потому что солнце — тоже объект.
class GoblinLeader : public Goblin {
/* данные и поведение в одной куче */
};
// Иерархия разрастается до небес:
// GoblinLeader -> Goblin -> Enemy -> AiObject -> MoveableObject -> GameObject
Программист знает всё: кто объект, какой он и что делает. Но «кто» и «что» намертво склеены, и это ловушка. Вспоминается байка про «Блицкриг», где нейтральные свиньи отнаследовались от боевых юнитов и начали искать оружие, которого не было. Движок падал из-за null pointer, пока «свиньям» не выдали «парабеллум» без патронов. Фаза-1 — это жизнь в попытках «долечить» структуру через еще более глубокое наследование. И, как показывает практика, в этой фазе можно жить годами, даже при огромных масштабах проекта.
…но не всё можно сделать объектом
Критическая масса ошибок заставляет команду переходить к модульности. Появляются менеджеры, классы становятся стройнее, и вместо жестких иерархий мы спрашиваем: «есть ли у объекта нужный компонент?».
GameObject* player = scene.findGameObject("Player");
if (!player->hasComponent("ControlComponent"))
player->addComponent(new ControlComponent());
player->update();
GameObject «худеет», становясь контейнером. Связность кода падает, но растет сложность отладки: повсюду приведения типов, умные указатели и системы обмена сообщениями. Самый мрачный хаос традиционно царит в GUI. Фаза-2 требует титанических усилий и времени — иногда годы, как в случае с Unreal Engine, где трансформация от C++ к конфигурационным подходам заняла около семи лет.
…можно сделать конфигом

Когда логика выносится из кода в данные, программисты теряют контроль над «картинкой». Классы исчезают, объекты собираются из файлов.
// Класса GoblinLeader больше нет. Только описание в данных:
{ "GoblinLeader": {
"EnemyComponent": { / ... / },
"MoveableComponent": { / ... / },
"BossComponent": { / ... / }
}}
Тут в студию приходит реальность: XML-конфигами начинают управлять «технические художники», а программист перестает понимать, что именно происходит на уровне. Плюс в том, что геймдизайнер может собрать чудо-монстра без правок C++ кода. Минус — в чудовищной сложности поддержки такой системы, сериализации и отладки того, что скрыто в данных.
…через собственный язык программирования

Движок становится виртуальной машиной, а вся игра — контентом. Цикл завершен.
ScriptEngine::ExecuteScript("GoblinLeader");
// Вся логика внутри скрипта, в ядре движка — лишь исполнитель.
Data-driven подход — это не панацея, а стратегия масштабирования, которая оправдана только для крупных команд. Для небольшого коллектива это может стать «Вавилонской башней», которую слишком дорого поддерживать.
Колесо вращается: за визуальным программированием неизбежно приходит жажда производительности, затем — потребность в четкой схеме данных, и всё начинается по новому кругу. В итоге всё сводится к деньгам и составу команды, а не только к инженерной моде.
Материалы
-
Scott Bilas, A Data-Driven Game Object System, GDC 2002. Фундамент, заложивший основы ECS. PDF
-
Mick West, Evolve Your Hierarchy, Cowboy Programming (2007). Переход от наследования к композиции. Ссылка
-
Adam Martin, Entity Systems Are the Future. Серия статей о чистом ECS. t-machine.org
-
Niklas Gray, Building a Data-Oriented Entity System. Практический разбор архитектуры данных. Gamedeveloper
-
Mike Acton, Data-Oriented Design and C++, CppCon 2014. Идеологическая оппозиция ООП. GitHub
-
Jason Gregory, Game Engine Architecture. Классический труд по сравнительному анализу подходов. Сайт книги
-
Tony Albrecht, Pitfalls of Object Oriented Programming. Критика ООП с позиций производительности. PDF
-
Борис Баткин/С. Макеев, Component-oriented design on consoles. Реальный пример WarThunder и языка DasLang. GitHub
P.S. Про обещанные пояснения: самый дорогой и «запутанный» движок — CryEngine (особенно в контексте бесконечного Star Citizen). Студия, живущая на «г…коде» — Mojang с их Java-базой Minecraft. А архитектурный шедевр, который закрылся из-за рыночной невостребованности — это Bitsquid (Autodesk Stingray).


