28 KiB
Интеграция с Unity для DragonECS
| Readme Languages: | ||
|
Русский |
English(WIP) |
|
Этот пакет делает работу с DragonECS в Unity удобнее и нагляднее: встроенная визуальная отладка и профайлинг, редакторские шаблоны и инструменты для привязки сущностей к GameObject.
Warning
Проект в стадии разработки. API может меняться.
Readme еще не завершен, если есть не ясные моменты, вопросы можно задать тут Обратная связь
Warning
Встроенные реализации шаблонов
UnityComponent<T>были перенесены СЮДА Так как некоторые модули Unity отключаемы, и например отключение модуля физики приведет к тому что код реализацииUnityComponent<Rigidbody>илиUnityComponent<Collider>не будет компилироваться.
//https://gist.github.com/DCFApixels/c250f2561f09e09ab3e6a4bd4f3013cb#file-unitycomponenttemplates-cs
Оглавление
- Установка
- Debug
- Шаблоны
- Связь с GameObject
- World Provider
- Шаблон Пайплайна
- FixedUpdate LateUpdate
- Кастомизация инспектора
- Поддержка Jobs
- Документация проекта
- Окно настроек
- Reference Repairer
- FAQ
Установка
Семантика версионирования - Открыть
Окружение
Обязательные требования:
- Зависимость: DragonECS
- Минимальная версия C# 8.0;
- Минимальная версия Unity 2021.2.0;
Установка для Unity
-
Unity-модуль
Поддерживается установка в виде Unity-модуля при помощи добавления git-URL в PackageManager:
https://github.com/DCFApixels/DragonECS-Unity.git
Или ручного добавления этой строчки в Packages/manifest.json:
"com.dcfa_pixels.dragonecs-unity": "https://github.com/DCFApixels/DragonECS-Unity.git",
-
В виде исходников
Можно также напрямую скопировать исходники пакета в проект.
Debug
Debug Сервис
UnityDebugService - реализация Debug-сервиса для EcsDebug. В редакторе он инициализируется автоматически и обеспечивает интеграцию: например, вызовы EcsDebug.Print направляются в консоль Unity, а EcsProfilerMarker подключается к встроенному профайлеру и т.д.
//Ручная активация.
UnityDebugService.Activate();
//Выведет сообщение в консоли Unity.
EcsDebug.Print();
var someMarker = new EcsProfilerMarker("SomeMarker");
someMarker.Begin();
//время выполнения этого участка будет отражено в профайлере Unity.
someMarker.End();
//Остановка игрового режима.
EcsDebug.Break();
Визуальная отладка
Реализовано в виде объектов-мониторов, в которых отображается состояние разных частей фреймворка. Найти эти мониторы можно в Play Mode в разделе DontDestroyOnLoad.
_pipeline = EcsPipeline.New()
//...
// Инициализация отладки для пайплайна и миров
.AddUnityDebug(_world, _eventWorld)
//...
.BuildAndInit();
-
PipelineMonitor
Показывает состояние EcsPipeline. Системы отображаются в порядке их выполнения.
-
PipelineProcessMonitor
Отображает в виде матрицы процессы и системы. Системы отображаются в порядке их выполнения. Точка в пересечении системы и процесса означает что эта система является частью этого процесса.
-
WorldMonitor
Показывает состояние EcsWorld. На каждый мир, переданный в AddUnityDebug(...), создается отдельный монитор.
-
EntityMonitor
Показывает состояние сущности мира, позволяет добавлять/изменять/удалять компоненты по время Play Mode. На каждую сущность в мире создается отдельный монитор. Все мониторы сущностей помещаются в монитор мира.
Шаблоны
Интеграция содержит шаблоны, расширяющие ITemplateNode, предназначенные для настройки сущностей из редактора.
ScriptableEntityTemplate
Хранится как отдельный ассет. Наследуется от ScriptableObject.
Создать ассет: Asset > Create > DragonECS > ScriptableEntityTemplate.
Чтобы добавить компонент в меню Add Component Нужен Шаблон компонента.
MonoEntityTemplate
Крепится к GameObject. Наследуется от MonoBehaviour.
Повесить компонент: Add Component > DragonECS > MonoEntityTemplate.
Чтобы добавить компонент в меню Add Component Нужен Шаблон компонента.
Шаблон компонента
Чтобы компонент попал в меню Add Component требуется шаблон. Шаблоны компонента это типы реализующие IComponentTemplate или компоненты реализующие ITemplateNode вместе с IEcsComponentMember.
Реализация
- Упрощенная реализация:
// Обязательно добавить [Serializable] к типу компонента.
[Serializable]
struct SomeComponent : IEcsComponent { /* ... */ }
class SomeComponentTemplate : ComponentTemplate<SomeComponent> { }
// Тоже самое но для компонентов-тегов.
[Serializable]
struct SomeTagComponent : IEcsTagComponent { }
class SomeTagComponentTemplate : TagComponentTemplate<SomeComponent> { }
Другие способы
Реализация ITemplateNode у компонента
Такой способ может быть удобен тем что не требует создания отдельного класса шаблона, компонент сам выступает как шаблон, и он так же прост в реализации. Минус данного подхода, что проще случайно переименовать компонент и получить Missing Reference в местах с атрибутом [SerializeReference].
public struct Health : IEcsComponent, ITemplateNode
{
public float Points;
public void Apply(short worldID, int entityID)
{
EcsPool<Health>.Apply(worldID, entityID) = this;
}
}
Реализация кастомного шаблона
Если не подходят встроенные ComponentTemplate<T> или TagComponentTemplate<T>, можно создать свой шаблон реализующий IComponentTemplate. Например это может пригодиться для кастомного пула. В большинстве случаев достаточно использовать встроенные шаблоны.
[Serializable]
struct SomeComponent : IEcsComponent { /* ... */ }
class SomeComponentTemplate : IComponentTemplate
{
[SerializeField]
protected SomeComponent component;
public Type Type { get { return typeof(SomeComponent); } }
public bool IsUnique { get { return true; } }
public void Apply(int worldID, int entityID)
{
EcsPool<SomeComponent>.Apply(worldID, entityID) = component;
}
public object GetRaw() { return component; }
public void SetRaw(object raw) { component = (SomeComponent)raw; }
public void OnGizmos(Transform transform, IComponentTemplate.GizmosMode mode) { /*...*/ }
public void OnValidate(UnityEngine.Object obj) { /*...*/ }
}
Связь с GameObject
Связываются сущности и GameObject-ы с помощью коннектов. Коннекты со стороны GameObject - EcsEntityConnect, со стороны сущности - GameObjectConnect. EcsEntityConnect - управляющий коннект, GameObjectConnect - создается/удаляется автоматически.
EcsEntityConnect connect = /*...*/;
entlong entity = _world.NewEntityLong();
// Связывание сущности с GameObject.
// Автоматически добавляется GameObjectConnect в сущность
// и применяются шаблоны.
connect.ConnectWith(entity);
// Или создать без применения шаблонов.
connect.ConnectWith(entity, false);
// Отвязать.
// Автоматически удалится GameObjectConnect.
connect.Disconnect();
Повесить компонент: Add Component > DragonECS > EcsEntityConnect.
Просмотреть все компоненты связанной сущности можно развернув
RUNTIME COMPONENTS.
На панели внизу есть вспомогательные кнопки: 1) Отвязать сущность. 2) Удалить сущность. 3) Автоматическое заполнение массива шаблонов. 4) Каскадный вызов автозаполнения для всех дочерних коннектов в иерархии.
AutoEntityCreator автоматический создает сущность и связывает с GameObject. В инспекторе ему нужно указать EcsEntityConnect с которым связывать сущность и Провайдер мира в котором создать сущность.
Повесить компонент: Add Component > DragonECS > AutoEntityCreator.
На панели внизу есть вспомогательные кнопки: 1) Автоматическое заполнение ссылки на коннект. 2) Каскадный вызов автозаполнения для всех дочерних экземпляров в иерархии.
World Provider
EcsWorldProvider<TWorld> - это ScriptableObject обертка над TWorld, предназначенная для пробрасывания экземпляра мира и настройки через инспектор Unity. Для простых случаев достаточно будет использовать синглтон версию провайдера EcsDefaultWorldSingletonProvider.
// Синглтон провайдер создается автоматически в папке "Assets/Resource".
EcsDefaultWorldSingletonProvider provider = EcsDefaultWorldSingletonProvider.Instance;
// ...
EcsDefaultWorld world = new EcsDefaultWorld();
// Устанавливаем экземпляр мира в провайдер.
provider.Set(world);
// ...
//Получаем экземпляр мира, если провайдер был пуст, то он создаст новый мир.
EcsDefaultWorld world = provider.Get();
EcsPipeline pipeline = EcsPipeline.New()
//...
// Внедряем в системы полученный из провайдера мир.
.Inject(world)
//...
.BuildAndInit();
Пример реализации провайдера для своего типа мира
//Пример реализации своего провайдера для пробрасывания мира своего типа
[CreateAssetMenu(fileName = nameof(EcsMyWorldProvider), menuName = EcsConsts.FRAMEWORK_NAME + "/WorldProviders/" + nameof(EcsMyWorldProvider), order = 1)]
public class EcsMyWorldProvider : EcsWorldProvider<EcsMyWorld> { }
//Пример реализации синглтон версии для мира своего типа
public class EcsMyWorldSingletonProvider : EcsWorldProvider<EcsMyWorld>
{
private static EcsMyWorldSingletonProvider _instance;
public static EcsMyWorldSingletonProvider Instance
{
get
{
if (_instance == null) { _instance = FindOrCreateSingleton<EcsMyWorldSingletonProvider>("SingletonMyWorld"); }
return _instance;
}
}
}
Создать ассет провайдера: Asset > Create > DragonECS > WorldProviders > Выбрать тип мира.
Шаблон Пайплайна
Пайплайн как и сущности можно собирать из шаблонов. Шаблоны пайплайна это модули, реализующие интерфейс IEcsModule。
По умолчанию расширение содержит 2 вида шаблонов: ScriptablePipelineTemplate, MonoPipelineTemplate.
ScriptablePipelineTemplate
Хранится как отдельный ассет. Наследуется от ScriptableObject. Действия чтобы создать ScriptableEntityTemplate ассет: Asset > Create > DragonECS > ScriptablePipelineTemplate.
MonoPipelineTemplate
Крепится к GameObject. Наследуется от MonoBehaviour. Повесить компонент: Add Component > DragonECS > MonoPipelineTemplate.
EcsRootUnity
Упрощённая реализация Ecs Root для Unity; собирает пайплайн из шаблонов. Наследуется от MonoBehaviour. Чтобы добавить на GameObject: Add Component > DragonECS > EcsRootUnity.
FixedUpdate и LateUpdate
using DCFApixels.DragonECS;
using UnityEngine;
public class EcsRoot : MonoBehaviour
{
private EcsPipeline _pipeline;
//...
private void Update()
{
// Стандартный Run из фреймворка.
_pipeline.Run();
}
private void FixedUpdate()
{
// Специальный Run для трансляции FixedUpdate.
_pipeline.FixedRun();
}
private void LateUpdate()
{
// Специальный Run для трансляции LateUpdate.
_pipeline.LateRun();
}
// ...
}
Кастомизация инспектора
Атрибуты инспектора
- [ReferenceDropDown] -
Применяется к полю с
[SerializeReference]. Добавляет кнопку выбора типа из списка. Можно ограничить набор доступных типов, передав список в конструктор. - [ReferenceDropDownWithout] -
Используется вместе с
[ReferenceDropDown], чтобы исключить указанные типы (и их наследников) из списка выбора. - [DragonMetaBlock] -
Отображает значение в инспекторе так же, как компоненты в шаблонах сущности. Учитывает meta-атрибуты (
MetaGroup,MetaColor,MetaDescription,MetaIDи др.).
Поведение Meta-атрибутов
- Иерархическая группировка элементов в меню
Add Componentили[ReferenceDropDown]задаётся через[MetaGroup]. - Цвет компонента в инспекторе по умолчанию определяется именем типа. Режим окраски можно изменить в окне настроек. Явный цвет задаётся через
[MetaColor]. - При совпадении имени типа и файла (или при наличии
[MetaID]) рядом с кнопкой удаления появляется иконка файла: один клик — выделение скрипта в проекте, двойной клик — открытие. - Если указан
[MetaDescription], рядом отображается иконка подсказки с текстом описания. - При восстановлении Missing Reference с помощью Reference Repairer, инструмент ищет соответствие старого и нового имени типа по атрибуту
[MetaID(id)].
Примеры:
Атрибут DragonMetaBlock:
// Отображение поля настраиваемое мета-атрибутами.
// Аналогично компонентам в MonoEntityTemplate или ScriptableEntityTemplate.
[DragonMetaBlock]
public SomeComponent Component;
// Можно применять к любому полю любого типа.
[DragonMetaBlock]
public Foo Foo;
ReferenceDropDown и ReferenceDropDownWithout:
// Добавляет кнопку выбора реализации ITemplateNode из выпадающего списка.
[SerializeReference]
[ReferenceDropDown]
public ITemplateNode Template;
// Так же можно применять к любому полю любого типа.
// В списке будут только тип Foo и его наследники, исключая FooExc и его наследников.
[SerializeReference]
[ReferenceDropDown(typeof(Foo))]
[ReferenceDropDownWithout(typeof(FooExc))]
public object Template;
Комбинирование и другие варианты использования:
// Атрибуты можно комбинировать.
[DragonMetaBlock]
[ReferenceDropDown]
public ITemplateNode Template;
// Обертка над ITemplateNode, аналогично примеру выше.
public ComponentTemplateProperty Template;
// Атрибуты корректно работают с массивами и листами.
[DragonMetaBlock]
[ReferenceDropDown]
public ITemplateNode[] Template;
Поддержка Jobs
DragonECS по умолчанию совместим с Job системой Unity. Пример:
EcsWorld _world;
class Aspect : EcsAspect
{
// Пул для unmanaged компонентов.
public EcsValuePool<Cmp> Cmps = Inc;
}
public void Run()
{
var job = new Job()
{
// Идентично Where, но возвращает unmanaged список сущностей.
Entities = _world.WhereUnsafe(out Aspect a),
// Конвертация пула в unmanaged версию
Cmps = a.Cmps.AsNative(),
X = 10f,
};
JobHandle jobHandle = job.Schedule(job.Entities.Count, 64);
jobHandle.Complete();
}
// Unmanaged компонент.
public struct Cmp : IEcsValueComponent
{
public float A;
}
private struct Job : IJobParallelFor
{
public EcsUnsafeSpan Entities;
public NativeEcsValuePool<Cmp> Cmps;
public float X;
public Job(EcsUnsafeSpan entities, float x)
{
Entities = entities;
X = x;
}
public void Execute(int index)
{
var e = Entities[index];
Cmps[e].A += X;
}
}
Документация проекта
В интеграции также есть окно документации проекта на основе мета-атрибутов. Открыть документацию: Tools > DragonECS > Documentation. Документация формируется при первом открытии окна и при нажатии кнопки Update.
Окно настроек
В окне настроек доступно несколько опций, включая режимы отображения компонентов в инспекторе. Внизу находятся переключатели для define-переменных, используемых в фреймворке. Открыть окно настроек: Tools > DragonECS > Settings.
Reference Repairer
Инструмент для восстановления Missing Reference. Некоторые части интеграции активно задействует [SerializeReference], у которого есть известная проблема с потерей типов при переименовании. Reference Repairer упрощает процесс восстановления. Он может собирать все ассеты с потерянными типами, после этого предоставляет окно для указания новых имён потерянных типов и выполнит их восстановление в собранных ассетах. Открыть окно инструмента: Tools > DragonECS > Reference Repairer.
Если потерянные типы имеют атрибут
[MetaID(id)], инструмент попытается автоматически сопоставить новое имя типа.
FAQ
Не могу повесить EcsEntityConnect или другие компоненты
Иногда это происходит после обновления пакета. Решения: выполните Assets -> Reimport All или перезапустите Unity после удаления папки Library в корне проекта.