Unity DOTS на практике: когда ECS оправдан, а когда оверкилл
Я живу с 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, ответь себе на вопросы:
- Я сейчас упёрся в производительность, и Profiler показывает, что виноват CPU?
- Boттлнэк в одной горячей системе, или у меня просто всё «среднее»?
- Я уже попробовал вынести горячее в Burst+Job без ECS?
- У меня тысячи однотипных объектов, или сотни уникальных?
- У меня есть свободные 4–8 недель на изучение и переписывание?
- В команде есть второй человек, который сможет это поддерживать?
Если на 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». Просто ещё один молоток, и нужен он не для каждого гвоздя.