Entity component system, основы

Entity Component System (ECS) - довольно популярный архитектурный паттерн у разработчиков, которые хотят избежать ада ситуации “проще переписать все с нуля” в случае добавления или изменения игровых механик. Этот паттерн позволяет уменьшить связность между различными частями проекта, что дает возможность добавлять / убирать / менять механики независимо друг от друга и без потерь в производительности. Итак, что же такое ECS?

ECS базируется на следующих 3 определениях: entity (сущность), component (компонент) и system (система). Собственно, акроним ECS и есть сокращение этих 3 слов.

Сущности (Entities)

Сущность - это любой объект в игре. Например, это может быть юнит игрока, кнопка в интерфейсе, событие с данными от одной системы к другой и т.п. Сущности сами по себе не имеют свойств и выступают контейнерами для компонентов. По аналогии с юнити - это GameObject.

Компоненты (Components)

Основная идея ECS - переход от стандартной ООП-идеи Inheritance (Наследование) к Composition over Inheritance (Композиция вместо Наследования). Больше никакой боли в попытках смешивания различных поведений с необходимостью ломания родительского класса в некоторых случаях - вместо иерархии наследования мы разделяем данные на изолированные блоки (Компоненты) и набираем из них нужную нам информацию об игровом объекте, добавляя компоненты на сущности:

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

Системы (Systems)

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

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

Сущности, компоненты, системы - зачем такие сложности? Потому что с таким подходом (разделение логики и данных) мы может комбинировать любое сложное поведение набором простых систем, каждая из которых будет обрабатывать только нужные данные на сущностях. В результате мы можем добавлять и удалять системы обработки данных с минимальными изменениями в других системах, либо вообще без них.

Но стоп, разве мы уже не имеем то же самое в Unity? В чем отличие между GameObject / MonoBehaviour и ECS? Отличие есть: Unity - это не ECS, а EC:

  • GameObject => ECS Entity.
  • MonoBehaviour => ECS Component + ECS System.

Если мы захотим создать 10000 объектов в игре с простым поведением (реализованным внутри MonoBehaviour) - производительность будет не очень хорошей, об этом была статья от самих разработчиков Unity. Если мы отделим логику от данных и перенесем ее в Менеджеры, как предложено в статье - мы по сути получим тот самый ECS, но с рядом ограничений. Т.е если рассматривать движение от простого к сложному, это можно описать следующей цепочкой: MonoBehaviours -> Managers -> ECS.

Решения, готовые к применению

Самое популярное решение для Unity - это Entitas (существуют порты под другие движки и языки). очень большой и сложный проект, пытающийся упростить жизнь разработчика путем автоматической генерации большого количества кода различных оберток и вспомогательных классов для более простого управления данными. Это главная как позитивная, так и негативная особенность этого проекта. В Runtime будет добавлено порядка 0.5Mb, генерация кода и интеграция в редактор - порядка 3Mb.

Разработчики Unity сейчас активно разрабатывают штатную ECS, которую обещают выпустить в каком-то виде в этом году. Ну… как обычно, никаких четких сроков, да и юнитехи в принципе не способны выпустить ни одну подсистему и сказать, что она стабильная и готова к использованию. Мы должны подождать еще год или два после официального релиза, пока они пофиксят все критические баги. Это значит, что встроенного решения можно не ждать слишком скоро.

Постоянная генерация кода и дополнительный размер в 3.5Mb - меня это не устроило. К тому же я слишком ленив, чтобы подробно изучить как Entitas работает внутри в деталях - я просто взял и написал свою собственную ECS с нуля - минимальный размер кода, минимальное потребление памяти (нет постоянных аллокаций и сборок мусора) в Runtime. Возможно, я еще напишу несколько постов об этой ECS в будущем.