Базовая интеграция в редактор Unity для Entity Component System

Мой ECS фреймворк уже достаточно неплохо работает, но по сути нет никакой визуализации того, что происходит в коде. Что если мы хотим как-то следить за процессом и контролировать его?

Это возможно с помощью отдельного модуля - UnityEditor integration. Он позволяет визуализировать ECS-сущности и ECS-компоненты на них, делая отображение на стандартные GameObject и компоненты на них.

Тестовое окружение

Давайте создадим тестовое окружение. Пусть это будет сцена по умолчанию с новым GameObject-объектом (имя может быть любым, я использовал “_LOGIC”) и следующим MonoBehaviour-компонентом на нем:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sealed class UnityIntegrationTest : MonoBehaviour {
EcsWorld _world;

EcsSystems _systems;

void OnEnable () {
_world = new EcsWorld ();
#if UNITY_EDITOR
// observer should be created before any entity will be created.
UnityIntegration.EcsWorldObserver.Create (_world);
#endif
_systems = new EcsSystems (_world).Add (new TestSystem ());
_systems.Initialize ();
#if UNITY_EDITOR
UnityIntegration.EcsSystemsObserver.Create (_systems);
#endif
}

void Update () {
_systems.RunUpdate ();
}

void OnDisable () {
_systems.Destroy ();
}
}

Интеграция была выполнена следующими двумя строчками:

1
2
3
UnityIntegration.EcsWorldObserver.Create (_world);
...
UnityIntegration.EcsSystemsObserver.Create (_systems);

Обе строчки должны быть обернуты в директиву препроцессора #if UNITY_EDITOR, иначе проект не сможет быть собран как релизное приложение и сможет работать только в редакторе.

Оставшаяся часть ECS (cкомпоненты и система):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class UnityIntegrationComponent1 {
public Vector3 Position;

public Transform CameraTransform;
}

class UnityIntegrationComponent2 {
public int Id;

public string Name;

public float Time;
}

sealed class TestSystem : IEcsInitSystem, IEcsRunSystem {
[EcsWorld]
EcsWorld _world;

[EcsFilterInclude (typeof (UnityIntegrationComponent2))]
EcsFilter _filter;

void IEcsInitSystem.Initialize () {
var entity = _world.CreateEntity ();
var c1 = _world.AddComponent<UnityIntegrationComponent1> (entity);
c1.Position = new Vector3 (1f, 2f, 3f);
c1.CameraTransform = Camera.main.transform;

var c2 = _world.AddComponent<UnityIntegrationComponent2> (entity);
c2.Id = 456;
c2.Name = "Tester";
}

void IEcsInitSystem.Destroy () { }

EcsRunSystemType IEcsRunSystem.GetRunSystemType () {
return EcsRunSystemType.Update;
}

void IEcsRunSystem.Run () {
// test creating and removing entites in loop.
var entity = _world.CreateEntity ();
_world.AddComponent<UnityIntegrationComponent1> (entity);
_world.RemoveEntity (entity);

// change component field in loop.
foreach (var c2entity in _filter.Entities) {
_world.GetComponent<UnityIntegrationComponent2> (c2entity).Time = Time.time;
}
}
}

В системе TestSystem была создана одна сущность с компонентами UnityIntegrationComponent1 и UnityIntegrationComponent2 на ней. В Run-цикле системы можно увидеть как создается новая сущность, к ней добавляется компонент и вся сущность удаляется - это делается для проверки пулинга объектов.

Визуализация

После старта редактора сцена будет выглядеть следующим образом:

Вы можете увидеть новый GameObject с именем [ECS-WORLD], содержащий представления всех созданных (активных объектов) и удаленных (деактивированных объектов) сущностей.

Внутри [ECS-WORLD] можно найти статистику по количеству сущностей, компонентов, фильтров и т.п:

Если выделить любую ecs-entity-визуализацию, то можно увидеть список подключенных компонентов и данных внутри их полей:

При выделении [ECS-SYSTEMS]представления можно увидеть список использованных систем:

Как дополнительная особенность - вы можете временно выключить все IEcsRunSystem-ы, переключив состояние флага Run systems activated.

[Обновлено 28.11.2018] Теперь можно отключать каждую IEcsRunSystem индивидуально.

Все данные представления - доступны только по чтению by design, они не могут быть изменены из редактора Unity (связывание данных в одну сторону). Возможно позже будут добавлены опции вида “AddComponent” и “RemoveComponent”, но данные в ECS-компонентах не смогут быть отредактированы в любом случае.

В TestSystem-системы можно увидеть поле UnityIntegrationComponent2.Time, обновляемое каждый фрейм, но в инспекторе оно будет выглядеть статичным. Так происходит из-за особенностей работы редактора юнити - инспектор компонента будет перерисован только в случае возникновения одного и событий: клик, драг, изменение размеров виджетов и т.д. Поэтому, если хочется увидеть актуальное значение поля компонента - нужно покликать в инспекторе в любом месте. Такое поведение может быть изменено позже, но я пока не уверен в его целесообразности из-за падения производительности и аллокаций памяти.