lenec ru

← все посты

Unity DOTS на практике: когда ECS оправдан, а когда оверкилл

15K

Я живу с DOTS уже четыре проекта. Один — мобильный F2P, два пет-проекта на ПК, один прототип сетевой стратегии. И в трёх случаях из четырёх через полгода я думал «зря я это начал». В одном случае — что без DOTS вообще не довёз бы. Вот разговор начистоту: когда Entities, Burst и Jobs стоят свечей, а когда ты усложняешь себе жизнь без причины.

Ориентируюсь на Entities 1.x в Unity 6 (6000.x). Если у тебя ещё 0.51 — там часть API другая, но философия та же.

Что на самом деле даёт DOTS

Когда люди говорят «DOTS быстрый», обычно подразумевают одну из трёх вещей:

  • Burst — компилятор LLVM-под-бэк, который превращает C# в SIMD-нативный код. Сам по себе. Работает и без ECS, на обычных Job'ах.
  • Job System — параллельные задачи на core'ах CPU без ручной возни с потоками. Тоже работает с обычными MonoBehaviour'ами через NativeArray.
  • Entities (ECS) — собственно архитектура: данные в чанках по компонентам, кэш-friendly доступ, автоматический параллелизм по queries.

Главный момент: Burst и Jobs можно использовать без ECS. Это часто упускают. Если у тебя bottleneck — тяжёлая математика на полторы тысячи объектов, не обязательно переписывать игру под Entities. Достаточно вынести горячий участок в Job + Burst.

[BurstCompile]
struct UpdatePositionsJob : IJobParallelFor
{
    public NativeArray<float3> positions;
    [ReadOnly] public NativeArray<float3> velocities;
    public float deltaTime;

    public void Execute(int index)
    {
        positions[index] += velocities[index] * deltaTime;
    }
}

Это всё ещё MonoBehaviour-сцена. Но горячий цикл уже работает на всех core'ах и со SIMD. На моём ноуте 100 000 объектов так обновляются за 0.4 мс. Без Burst и Jobs — около 35 мс на одном потоке.

Когда ECS реально окупается

ECS как архитектура нужна там, где у тебя много однородных объектов с одинаковой структурой данных, и ты хочешь перебирать их пачками. Условный сценарий, где DOTS превращает невозможное в возможное:

  • RTS на 10 000+ юнитов одновременно.
  • Симуляция flock'ов/частиц/толп с физикой и взаимодействиями (boids, ants, эпидемии).
  • Voxel-движок с миллионами блоков и chunk-системой.
  • Tower defense с тысячами врагов и сотнями башен.
  • Bullet hell с 50 000 пуль на экране.

Общее у всех: данных много, поведение одинаковое или сводится к нескольким архетипам. ECS позволяет процессить эти данные в кэш-friendly порядке, без виртуальных вызовов и без аллокаций. На мобиле это часто разница между 60 fps и слайдшоу.

Когда DOTS — оверкилл

Если у тебя single-player adventure про девочку с фонариком, не лезь в ECS. Серьёзно. Я слышал «надо же думать о масштабируемости», и каждый раз это заканчивалось одинаково: команда полгода переписывает gameplay на Entities, обнаруживает, что 80% игровой логики плохо ложится на ECS, и возвращается к гибриду, в котором половина систем ECS, половина MonoBehaviour, никто не понимает поток данных, баги — норма.

Конкретные признаки, что тебе DOTS не нужен:

  • Объектов в сцене — десятки или сотни, не тысячи.
  • Каждый объект уникальный или сильно отличается от соседей (босс, сюжетный NPC, интерактивный пропс).
  • Логика завязана на UI, диалоги, инвентарь, квесты.
  • Команда — три человека, и ты единственный, кто читал ECS-доку.
  • Деплой нужен через два месяца.

В этих условиях обычный Unity с MonoBehaviour и парой Job'ов на горячих участках работает быстрее, понятнее и с меньшим количеством багов.

Реальный кейс: куда DOTS встал отлично

В одной из мобильных F2P-игр у нас была механика «осада замка» — на экране одновременно бегало до 800 юнитов с поиском пути, атаками, эффектами. На MonoBehaviour мы держали 30 fps на iPhone 8 и страшно плавали по фреймтаймам.

Переписали юнитов на Entities, оставив всё остальное (UI, метаигра, экономика) на классическом стеке. Получилось:

  • 1500 юнитов в стабильных 60 fps на том же iPhone 8.
  • Память — минус 40%: вместо тысячи GameObject'ов с Transform'ами и Animator'ами — компактные чанки.
  • Поиск пути на Job + Burst, а не корутины.

Стек выглядел так: батч-система пути, ECS-системы для движения и боя, синхронизация позиций обратно в Hybrid-проксированные объекты для рендера через GPU instancing. Анимация — упрощённая, через скиннинг в шейдере на основе UV2 (без Animator'а).

Гибрид работал. Но критичен момент: мы вынесли в ECS только то, что реально не помещалось на CPU. UI, метаигра, мир-карта — всё на старом стеке. Если бы лезли везде — не довезли бы.

Что больно в DOTS прямо сейчас

Документация и учебные материалы запаздывают. Часть туториалов, которые ты найдёшь в Google, написана под Entities 0.x — там JobComponentSystem, IJobForEach, всё устарело. С 1.x всё через SystemBase или ISystem, IJobEntity, SystemAPI.Query<>.

Простой пример системы на 1.x:

public partial struct MoveSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var deltaTime = SystemAPI.Time.DeltaTime;
        foreach (var (transform, velocity) in
                 SystemAPI.Query<RefRW<LocalTransform>, RefRO<Velocity>>())
        {
            transform.ValueRW.Position += velocity.ValueRO.Value * deltaTime;
        }
    }
}

public struct Velocity : IComponentData
{
    public float3 Value;
}

Выглядит чисто, но первый раз ты пол-дня будешь искать, почему SystemAPI.Query ничего не возвращает (привет, baking authoring-компонента, который ты забыл положить в SubScene).

Подсистемы, которые в обычном Unity «работают из коробки»:

  • Анимация. Animator на ECS не работает напрямую. Либо GPU skinning через шейдер, либо проксирование на GameObject'е, либо собственное решение через Animation Rigging + IJob.
  • UI. UGUI и UI Toolkit живут вне ECS. Любой обмен данными — через bridge-компоненты или общие NativeContainer'ы.
  • Физика. Unity Physics (DOTS-овая) — отдельный пакет, не PhysX. Поведение похоже, но детали разные. Нет CharacterController «из коробки», continuous-коллизии другие.
  • Audio. Тоже не из ECS. Триггерится через события или managed-side.
  • Сериализация и save. Сложнее: чанки сохранять напрямую больно, нужен свой слой.

В сумме: всё, что в классическом Unity занимает час, в DOTS может занять день, если ты раньше с этим не работал.

Hybrid: разумный компромисс

Самая практичная архитектура для не-чисто-симуляционной игры — Hybrid. ECS делает то, что массовое и горячее. Остальное — MonoBehaviour.

Bridge между ECS и GameObject'ами в 1.x идёт через bake-процесс: у тебя есть Authoring-компонент (обычный MonoBehaviour), и его Baker превращает GameObject в Entity на этапе билда сцены или подсцены.

public class EnemyAuthoring : MonoBehaviour
{
    public float speed = 5f;
    public int hp = 100;
}

public class EnemyBaker : Baker<EnemyAuthoring>
{
    public override void Bake(EnemyAuthoring authoring)
    {
        var entity = GetEntity(TransformUsageFlags.Dynamic);
        AddComponent(entity, new EnemyData
        {
            speed = authoring.speed,
            hp = authoring.hp
        });
    }
}

Авторинг кладёшь в SubScene. Получаешь привычный workflow «прислонил префаб в иерархию», и при этом данные едут в ECS. Это правильный путь — ты не теряешь редактор и не вручную инстансируешь миллион сущностей в коде.

Профилирование: что смотреть

В DOTS-проектах профайлер показывает чуть другие штуки. Я в первую очередь смотрю:

  • Entities Hierarchy и Entities Hierarchy → Archetype Window. Если у тебя 200 архетипов вместо 5 — что-то не так с компонентами, лишние теги дробят чанки.
  • Profiler → CPU → Job timeline. Видно, как Job'ы расходятся по core'ам и где есть синхронные точки.
  • Burst Inspector. Можно посмотреть ассемблер сгенерированный для конкретного Job'а — иногда кажущиеся безобидные структуры мешают векторизации.
  • Memory Profiler. ECS-чанки видны отдельным разделом, легко поймать утечку (например, забытый EntityManager.CreateEntity без cleanup).

Если видишь, что Job выполняется на одном core хотя ты ожидал параллелизма — проверь зависимости и scheduling. Часто причина — какой-нибудь шаг с WithoutBurst() или managed-обращение, которое держит main thread.

Чек-лист «нужен ли мне DOTS»

Перед тем как затевать переход на Entities, ответь себе на вопросы:

  1. Я сейчас упёрся в производительность, и Profiler показывает, что виноват CPU?
  2. Boттлнэк в одной горячей системе, или у меня просто всё «среднее»?
  3. Я уже попробовал вынести горячее в Burst+Job без ECS?
  4. У меня тысячи однотипных объектов, или сотни уникальных?
  5. У меня есть свободные 4–8 недель на изучение и переписывание?
  6. В команде есть второй человек, который сможет это поддерживать?

Если на 1, 2, 3 ответы «да, и Burst+Jobs не хватило», на 4 — «тысячи однотипных», на 5–6 — «есть» — пробуй ECS. В остальных случаях не лезь.

Что почитать

Официальная документация Entities в Unity Manual — основное место. Сэмплы в репозитории Unity-Technologies/EntityComponentSystemSamples на GitHub — там и игровые примеры (Tank Tutorial, Boids), и шаблоны систем. На YouTube смотри Code Monkey и Tertle (он в Unity team по DOTS, делал live-стримы про реальные паттерны).

Не верь древним статьям с Medium 2019 года — велик шанс, что половина API в них уже не работает.

Если коротко итожить: DOTS — это инструмент для тех случаев, когда ты упёрся в производительность из-за количества однотипных объектов. Не паттерн архитектуры на все случаи. Не «правильный современный Unity». Просто ещё один молоток, и нужен он не для каждого гвоздя.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.