Глава 2. Паттерны реализации модульного подхода в программировании
Исходный размер 1140x1600

Глава 2. Паттерны реализации модульного подхода в программировании

Данный проект является учебной работой студента Школы дизайна или исследовательской работой преподавателя Школы дизайна. Данный проект не является коммерческим и служит образовательным целям

паттерны

На заре индустрии игровая логика представляла собой жёстко структурированный набор инструкций, неразрывно связанных с основным циклом программы, что было вызвано, в том числе, и ограничениями инструментария.

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

На сегодняшний день существует несколько паттернов, реализующих модульный подход. Между собой они отличаются архитектурной сложностью, степенью абстракции и производительностью кода, поэтому при разработке проекта следует внимательно подходить к выбору конкретной реализации. При этом, если модули являются независимыми «атомами» со своими «входами» и «выходами», то даже в рамках одного проекта внутренняя структура модулей может быть реализована на основе разных подходов [10].

объектно-ориентированное программирование

Наиболее привычный и устоявшийся в игровой разработке подход базируется на иерархическом наследовании объектно-ориентированного программирования (ООП). И хотя сами объектно-ориентированные языки программирования изначально не ориентированы на игровую разработку, их широкое распространение и популярность в других сферах доказали, что они могут подходить для решения различных задач.

В парадигме ООП модульность в играх может реализовываться путём разделения логики и графики на отдельные системы, а затем их связывание через промежуточный слой [11]. Это позволяет разрабатывать гибкие, взаимозаменяемые элементы игры, однако полноценную модульность реализовать не получится.

Примером перехода к разработке игр по принципам ООП является Doom 3 [12].

big
Исходный размер 1600x900

Doom 3. id Software. — Activision: 2004

проблема ООП

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

Например, если в игре есть противники-маги и противники-рыцари, то классическое наследование может выглядеть так:

Исходный размер 3439x999

Схема наследования акторов

Но если разработчику потребуется создать рыцаря-мага, то ему придётся либо создавать новый класс MageKnight и копировать в него логику из классов Mage и Knight, либо поднимать эту логику в базовый класс Enemy, что наделит его ненужной функциональностью.

композиция важнее наследования

Одним из решений проблемы является применение принципа «композиция важнее наследования» и его реализация в виде компонентной архитектуры (Component-Based Architecture) [13].

В рамках этой архитектуры сущность перестаёт быть носителем логики и превращается в пустой контейнер для компонентов.

Преимущества такого подхода:

  • Автономия. Каждый компонент (например, HealthComponent, MovementComponent) отвечает за одну узкую задачу.
  • Гибкость сборки. Геймдизайнер выступает в роли «сборщика». Чтобы создать нового уникального противника, ему не нужно просить программиста создать новый класс — достаточно добавить на базовый объект нужный набор модулей прямо в редакторе.
  • Скорость разработки. Подход позволяет снизить количество кода, который требуется для работы, и ускорить настройку игровой логики, что особенно важно на этапе прототипирования.
  • Масштабируемость. Продуманный набор компонентов позволяет реализовать большое количество комбинаций, не приводя при этом к резкому возрастанию сложности проекта.

Игровой движок Unity и создаваемые на нём игры, такие как Hollow Knight, используют компонентную архитектуру.

Исходный размер 1920x1080

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].

Исходный размер 1920x1080

Factorio. Wube Software. — Wube Software: 2016

entity–component–system

Наиболее комплексной и эффективной системой модульности в современном программировании является паттерн Entity–Component–System (ECS) [16, 17]. Он основан на трёх элементах, обеспечивающих модульность, производительность и разделение логики и данных:

  • Entity — сущность. Аналог игрового объекта, но по сути представляющий из себя простой числовой идентификатор. Сущность не содержит ни данных, ни логики.
  • Component — компонент. Контейнер, предназначенный для хранения простых наборов данных, например координат объекта. Компоненты не содержат логику, только данные.
  • System — система. Глобальный модуль, который находит сущности с интересующими данную систему компонентами и обновляет их. Например, система перемещения находит все сущности с компонентами позиции и скорости и обновляет их каждый кадр.

Однако такой подход требует значительного изменения мышления, так как программисту теперь нужно работать не с набором объектов и их компонентов, а с потоками данных, поэтому наибольшее распространение он получил в крупных проектах с большим количеством игровых объектов, где требуется максимальная производительность и сетевое взаимодействие [18].

В Overwatch паттерн ECS позволяет реализовывать большое количество разнообразных механик и обеспечивает стабильную сетевую игру для пользователей [19].

Исходный размер 1920x1080

Overwatch. Blizzard Entertainment. — Blizzard Entertainment: 2016

Глава 2. Паттерны реализации модульного подхода в программировании
Проект создан 10.03.2026
Глава:
1
2
3
4
5