паттерны
На заре индустрии игровая логика представляла собой жёстко структурированный набор инструкций, неразрывно связанных с основным циклом программы, что было вызвано, в том числе, и ограничениями инструментария.
Но по мере развития языков программирования и в связи с увеличением сложности проектов, стали появляться новые подходы, или паттерны, позволяющие работать с кодом более гибко.
На сегодняшний день существует несколько паттернов, реализующих модульный подход. Между собой они отличаются архитектурной сложностью, степенью абстракции и производительностью кода, поэтому при разработке проекта следует внимательно подходить к выбору конкретной реализации. При этом, если модули являются независимыми «атомами» со своими «входами» и «выходами», то даже в рамках одного проекта внутренняя структура модулей может быть реализована на основе разных подходов [10].
объектно-ориентированное программирование
Наиболее привычный и устоявшийся в игровой разработке подход базируется на иерархическом наследовании объектно-ориентированного программирования (ООП). И хотя сами объектно-ориентированные языки программирования изначально не ориентированы на игровую разработку, их широкое распространение и популярность в других сферах доказали, что они могут подходить для решения различных задач.
В парадигме ООП модульность в играх может реализовываться путём разделения логики и графики на отдельные системы, а затем их связывание через промежуточный слой [11]. Это позволяет разрабатывать гибкие, взаимозаменяемые элементы игры, однако полноценную модульность реализовать не получится.
Примером перехода к разработке игр по принципам ООП является Doom 3 [12].

Doom 3. id Software. — Activision: 2004
проблема ООП
Проблема в данном случае заключается в том, что традиционное наследование может приводить к так называемому комбинаторному взрыву — ситуации, при которой количество классов в архитектуре начинает расти в геометрической прогрессии из-за попыток выразить каждое сочетание свойств через наследование.
Например, если в игре есть противники-маги и противники-рыцари, то классическое наследование может выглядеть так:
Схема наследования акторов
Но если разработчику потребуется создать рыцаря-мага, то ему придётся либо создавать новый класс MageKnight и копировать в него логику из классов Mage и Knight, либо поднимать эту логику в базовый класс Enemy, что наделит его ненужной функциональностью.
композиция важнее наследования
Одним из решений проблемы является применение принципа «композиция важнее наследования» и его реализация в виде компонентной архитектуры (Component-Based Architecture) [13].
В рамках этой архитектуры сущность перестаёт быть носителем логики и превращается в пустой контейнер для компонентов.
Преимущества такого подхода:
- Автономия. Каждый компонент (например, HealthComponent, MovementComponent) отвечает за одну узкую задачу.
- Гибкость сборки. Геймдизайнер выступает в роли «сборщика». Чтобы создать нового уникального противника, ему не нужно просить программиста создать новый класс — достаточно добавить на базовый объект нужный набор модулей прямо в редакторе.
- Скорость разработки. Подход позволяет снизить количество кода, который требуется для работы, и ускорить настройку игровой логики, что особенно важно на этапе прототипирования.
- Масштабируемость. Продуманный набор компонентов позволяет реализовать большое количество комбинаций, не приводя при этом к резкому возрастанию сложности проекта.
Игровой движок Unity и создаваемые на нём игры, такие как Hollow Knight, используют компонентную архитектуру.
Hollow Knight. Team Cherry. — Team Cherry: 2017
интерфейсы
Для того чтобы независимые модули могли взаимодействовать, не имея прямых ссылок друг на друга, используются интерфейсы. Это позволяет достичь меньшей связности между отдельными системами [11].
Например, модуль снаряда не обязан знать, во что именно он попал — в персонажа, разрушаемую бочку или стену. Он лишь вызывает функцию TakeDamage через интерфейс IDamageable. Если объект поддерживает этот интерфейс, он реагирует на вызов согласно своей внутренней логике; если нет, то сообщение просто игнорируется.
То есть модули лишь сообщают друг другу о том, что произошло или что нужно сделать, но не знают о конкретных реализациях того, как это будет сделано, что позволяет менять одни модули на другие, не разрушая остальную логику.
отделение логики от данных
Следующий шаг к реализации модульного подхода заключается в отделении логики от данных. Основы этого подхода были озвучены Скоттом Биласом на GDC 2002 года в докладе «A Data-Driven Game Object System» [14].
Суть паттерна Data-Driven Design состоит в том, что переносит управление поведением системы из кода во внешние данные, такие как таблицы CSV, файлы JSON и другие.
В этом случае программист создаёт универсальный модуль, например компонент атаки. Модуль содержит лишь общую логику, но не включает конкретных значений урона, радиуса действия или типа атаки.
В свою очередь геймдизайнер, используя внешний файл или файл в редакторе движка, настраивает сами значения. Это даёт следующие преимущества:
- Быстрые итерации. Изменение баланса происходит без пересборки проекта и не требует участия программиста.
- Разделение ответственности. Геймдизайнер настраивает игровую логику и баланс, формирует опыт игрока, не рискуя повредить кодовую архитектуру проекта. Программист же работает только над архитектурой, не тратя время на подбор и тестирование значений.
Примером игры, в которой используется Data-Driven Design, является Factorio [15].
Factorio. Wube Software. — Wube Software: 2016
entity–component–system
Наиболее комплексной и эффективной системой модульности в современном программировании является паттерн Entity–Component–System (ECS) [16, 17]. Он основан на трёх элементах, обеспечивающих модульность, производительность и разделение логики и данных:
- Entity — сущность. Аналог игрового объекта, но по сути представляющий из себя простой числовой идентификатор. Сущность не содержит ни данных, ни логики.
- Component — компонент. Контейнер, предназначенный для хранения простых наборов данных, например координат объекта. Компоненты не содержат логику, только данные.
- System — система. Глобальный модуль, который находит сущности с интересующими данную систему компонентами и обновляет их. Например, система перемещения находит все сущности с компонентами позиции и скорости и обновляет их каждый кадр.
Однако такой подход требует значительного изменения мышления, так как программисту теперь нужно работать не с набором объектов и их компонентов, а с потоками данных, поэтому наибольшее распространение он получил в крупных проектах с большим количеством игровых объектов, где требуется максимальная производительность и сетевое взаимодействие [18].
В Overwatch паттерн ECS позволяет реализовывать большое количество разнообразных механик и обеспечивает стабильную сетевую игру для пользователей [19].
Overwatch. Blizzard Entertainment. — Blizzard Entertainment: 2016



