ECS паттерн в gamedev
Геймплей логику считают одной из самых сложных тем в gamedev. Если пытаться строить геймплей, полагаясь на организацию классов в ООП и на каскады условий if-else, то в какой-то момент можно потерять ориентацию. Код становится настолько запутанным и сложным для понимания, что даже самому разработчику становится трудно разобраться, что происходит. Постепенно увеличивающаяся сложность этих условий делает добавление новых фич дорогим и трудоёмким процессом.
С такой проблемой мы столкнулись, когда реализовывали геймплей для LandLords. Наш первый код игровых сценариев был именно таким. Порядок выполнения какой-то был, но он также был перенасыщен условиями, что делало его крайне хрупким и негибким.
Я расскажу как удалось сделать рефакторинг игровых сценариев с помощью ECS. Напомню, что игровой движок для игры LandLords это Phaser, но с его помощью только создаются префабы, там нет ничего о программирование игровых сценариев. Используя ECS мы значительно упростили разработку, сделав код более понятным, поддерживаемым и расширяемым.
ECS (Entity Component System) — это архитектурный шаблон, который позволяет разделить данные, поведение и логику управления в отдельные части, что делает код более модульным и гибким. ECS позволяет организовать код так, что сложные взаимосвязи становятся прозрачными и легко управляемыми.
Первая попытка написать геймплей
В нашу игру уже можно было играть. Большая часть игровых сценариев уже как-то работала. И если для игрока нет разницы, каким образом написаны сценарии (главное, чтобы работало), то как разработчик, я чувствовал непроходящий дискомфорт, работая с кодом геймплея. Он был связан с постоянно добавляющимися или меняющимися требованиями к игре, они же игровые ситуации. Я регулярно путался в очерёдности выполнения и в каскадах if. Часто было так, что исправлял одно место и ломал другое. И каждый раз было всё неприятней думать о добавлении чего-то нового в игру.
Конечно контроль над логикой был, но он был слабым. Не хватало такой гибкости, которая позволяла бы с ювелирной точностью разбирать игровые ситуации. Трудно было с этим мирится, срочно требовался рефакторинг. Я начал изучать проблему и искать решение. После просмотра нескольких докладов других разработчиков с похожими проблемами, стало очевидно, что ECS это именно то, что нужно для решения возникших сложностей. Хотя мне всё ещё не до конца было понятно, как именно.
Концепция ECS
ECS предлагает иной подход к разработке игр, чем то, к чему мы привыкли, полагаясь на ООП. Для игр больше не нужно создавать иерархии классов и настраивать связи между ними. Теперь игровые данные хранятся в глобальных реестрах-массивах, а управление данными осуществляется внутри отдельных систем-функций, выполняющихся в строгом порядке. Таким образом данные отделены от логики. Итого:
- Entity — это просто числовой идентификатор, связывающий вместе компоненты.
- Component — это данные, которые прикреплены к Entity, например, позиция, скорость или здоровье.
- System — это логика, которая обрабатывает компоненты сущностей.
ECS кажется более организованным подходом, но его суть, как шаблона проектирования, глубже. Это фундаментальный сдвиг в мышлении о разработке игр. Вместо того чтобы думать о том, как объекты взаимодействуют друг с другом, мы начинаем думать о том, что они делают и какие события происходят. И этот подход позволяет разработчикам создавать гораздо более гибкий и адаптивный код. Моделировать можно что угодно, необязательно думать при этом об объектах.
Конкретно для игры LandLords с помощью ECS я смоделировал пользовательские события и составление признаков игровых ситуаций. Я сделал небольшой демо проект, в котором продемонстрировал это минимальным примером. В демо показана обработка клика и составление fizz-buzz строки на основе счётчика. То же самое происходит в игре, но в гораздо большем масштабе.
github/13luck/ecs-pattern-demo
Рефакторинг
После тщательного поиска подходящей библиотеки для реализации ECS шаблона, я выбрал bitECS. На момент написания этой статьи библиотека в версии 0.3, но она уже предоставляет всю необходимую функциональность. Мне понравился её минималистичный API, который позволяет быстро и просто интегрировать ECS в проект. Это именно тот самый желаемый API с ювелирной точностью.
Для событий я использую сущность с прикреплённым маркером-компонентом. А для признаков игровых ситуаций я использую сущность с композицией маркер-компонентов. С помощью такого подхода получилось добиться максимального детерминизма в геймплее. Это дало мне, как разработчику, полную свободу для гибкой адаптации к меняющимся требованиям игры и упростило дальнейшее развитие кода.
Мне удалось переписать существующие сценарии за полтора месяца. Игроки особо разницы не заметили, но код сценариев стал значительно более структурированный, с чётким разделением ответственности между системами. Хотя не обошлось без «шероховатостей». Кроме того, unit-тестирование стало проще, а благодаря оптимизации кода и быстрее. Также уменьшилось потребление памяти.
В итоге ECS решил конкретные проблемы в проекте, такие как сложность иерархий и производительность. Всё это звучит как будто мы перешли от хаоса к порядку. ECS это действительно структурированная архитектура, которая делает геймплейные задачи предсказуемыми и, в теории, идеально подходящими для автоматизации ИИ. Код выглядит настолько шаблонным, что кажется ИИ может взять на себя часть работы, если дать ему чёткие инструкции.
Из минусов использования ECS это появление boilerplate-кода. Это не вызывает значительного дискомфорта, но может замедлять разработку. Как я заметил, ECS требует частого использования for-итерирования для перебора сущностей — это довольно типично для ECS решений. Скорее всего, существуют дополнительные абстракции или инструменты, которые помогают минимизировать этот недостаток.
Подводя итоги
Есть много разных подходов, какие-то из них экспериментальные, какие-то альтернативные, но есть такие, которые железобетонно работают. И подход ECS относится к последним, в чём я убедился сам. Если вы работаете только над частью системы, допустим на крупном проекте с большой командой, возможно вы не сразу оцените, насколько сильно такой подход оправдан. Однако если в ваши задачи входит архитектура или вы ведёте личный проект, эффективность ECS будет для вас очевидной. Обычно делая игровой проект мы рассчитываем на его развитие, что подразумевает рост команды, изменения внутри продукта и прочие факторы, так вот ECS оставляет возможность масштабирования без проклятия хаоса и неразберихи. Я теперь не представляю, как можно писать геймплей по-другому, ECS стал для меня естественным и эффективным способом организации кода.