diff --git a/README-RU.md b/README-RU.md index 7bbcbf3..d728764 100644 --- a/README-RU.md +++ b/README-RU.md @@ -9,9 +9,9 @@ QQ

-# DragonECS - C# Entity Component System Framework -| Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) | -| :--- | :--- | :--- | +# DragonECS - C# Entity Component System Фреймворк +| Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) | [中文](https://github.com/DCFApixels/DragonECS/blob/main/README-ZN.md) | +| :--- | :--- | :--- | :--- | DragonECS - это [ECS](https://en.wikipedia.org/wiki/Entity_component_system) фреймворк нацеленный на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Разработан на чистом C#, без зависимостей и генерации кода. Вдохновлен [LeoEcs](https://github.com/Leopotam/ecslite). @@ -148,7 +148,7 @@ class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy // Будет вызван один раз в момент работы EcsPipeline.Run(). public void Run () { } - // Будет вызван один раз в момент работы EcsPipeline.Destroy() + // Будет вызван один раз в момент работы EcsPipeline.Destroy(). public void Destroy () { } } ``` @@ -162,16 +162,16 @@ class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy ### Построение За построение пайплайна отвечает Builder. В Builder добавляются системы, а в конце строится пайплайн. Пример: ```c# -EcsPipelone pipeline = EcsPipeline.New() //Создает Builder пайплайна - // Добавляет систему System1 в очередь систем +EcsPipeline pipeline = EcsPipeline.New() // Создает Builder пайплайна. + // Добавляет систему System1 в очередь систем. .Add(new System1()) - // Добавляет System2 в очередь после System1 + // Добавляет System2 в очередь после System1. .Add(new System2()) - // Добавляет System3 в очередь после System2, но в единичном экземпляре + // Добавляет System3 в очередь после System2, но в единичном экземпляре. .AddUnique(new System3()) - // Завершает построение пайплайна и возвращает его экземпляр + // Завершает построение пайплайна и возвращает его экземпляр. .Build(); -pipeline.Init(); // Инициализация пайплайна +pipeline.Init(); // Инициализация пайплайна. ``` ```c# @@ -188,27 +188,29 @@ class SomeSystem : IEcsRun, IEcsPipelineMember Фреймворк реализует внедрение зависимостей для систем. это процесс который запускается вместе с инициализацией пайплайна и внедряет данные переданные в Builder. > Использование встроенного внедрения зависимостей опционально. ``` c# -class SomeDataA { /*...*/ } -class SomeDataB : SomeDataA { /*...*/ } +class SomeDataA { /* ... */ } +class SomeDataB : SomeDataA { /* ... */ } -//... +// ... SomeDataB _someDataB = new SomeDataB(); -EcsPipelone pipeline = EcsPipeline.New() - //... +EcsPipeline pipeline = EcsPipeline.New() + // ... // Внедрит _someDataB в системы реализующие IEcsInject. .Inject(_someDataB) - // Добавит системы реализующие IEcsInject в дерево инъекции + // Добавит системы реализующие IEcsInject в дерево инъекции, // теперь эти системы так же получат _someDataB. - .Injector.AddNode() // - //... + .Injector.AddNode() + // ... + .Add(new SomeSystem()) + // ... .BuildAndInit(); -//... +// ... // Для внедрения используется интерфейс IEcsInject и его метод Inject(T obj) class SomeSystem : IEcsInject, IEcsRun { SomeDataA _someDataA - //obj будет экземпляром типа SomeDataB + // obj будет экземпляром типа SomeDataB. public void Inject(SomeDataA obj) => _someDataA = obj; public void Run () @@ -222,21 +224,22 @@ class SomeSystem : IEcsInject, IEcsRun Группы систем реализующие общую фичу можно объединять в модули, и просто добавлять модули в Pipeline. ``` c# using DCFApixels.DragonECS; -class Module : IEcsModule +class Module1 : IEcsModule { public void Import(EcsPipeline.Builder b) { b.Add(new System1()); - b.Add(new System2(), EcsConsts.END_LAYER); // данная система будет добавлена в слой EcsConsts.END_LAYER - b.Add(new System3()); + b.Add(new System2()); + b.AddModule(new Module2()); + // ... } } ``` ``` c# -EcsPipelone pipeline = EcsPipeline.New() - //... - .AddModule(new Module()) - //... +EcsPipeline pipeline = EcsPipeline.New() + // ... + .AddModule(new Module1()) + // ... .BuildAndInit(); ``` @@ -244,11 +247,13 @@ EcsPipelone pipeline = EcsPipeline.New() Очередь систем можно разбить на слои. Слой определяет место в очереди для вставки систем. Если необходимо чтобы какая-то система была вставлена в конце очереди, вне зависимости от места добавления, эту систему можно добавить в слой EcsConsts.END_LAYER. ``` c# const string SOME_LAYER = nameof(SOME_LAYER); -EcsPipelone pipeline = EcsPipeline.New() - //... - .Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER) // Вставляет новый слой перед конечным слоем EcsConsts.END_LAYER - .Add(New SomeSystem(), SOME_LAYER) // Система SomeSystem будет вставлена в слой SOME_LAYER - //... +EcsPipeline pipeline = EcsPipeline.New() + // ... + // Вставляет новый слой перед конечным слоем EcsConsts.END_LAYER + .Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER) + // Система SomeSystem будет вставлена в слой SOME_LAYER + .Add(New SomeSystem(), SOME_LAYER) + // ... .BuildAndInit(); ``` Встроенные слои расположены в следующем порядке: @@ -275,12 +280,12 @@ EcsPipelone pipeline = EcsPipeline.New() Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от `EcsRunner`. Пример: ``` c# -//Интерфейс. +// Интерфейс. interface IDoSomethingProcess : IEcsProcess { void Do(); } -//Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах +// Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах sealed class DoSomethingProcessRunner : EcsRunner, IDoSomethingProcess { public void Do() @@ -288,19 +293,19 @@ sealed class DoSomethingProcessRunner : EcsRunner, IDoSomet foreach (var item in Process) item.Do(); } } -//... +// ... -//Добавление раннера при создании пайплайна. +// Добавление раннера при создании пайплайна. _pipeline = EcsPipeline.New() //... .AddRunner() //... .BuildAndInit(); -//Запуск раннера если раннер был добавлен. +// Запуск раннера если раннер был добавлен. _pipeline.GetRunner.Do() -//Или если раннер не был добавлен(Вызов GetRunnerInstance так же добавит раннер в пайплайн). +// Или если раннер не был добавлен(Вызов GetRunnerInstance так же добавит раннер в пайплайн). _pipeline.GetRunnerInstance.Do() ``` > Раннеры имеют ряд требований к реализации: @@ -314,31 +319,30 @@ _pipeline.GetRunnerInstance.Do() ## Мир Является контейнером для сущностей и компонентов. ``` c# -//Создание экземпляра мира +// Создание экземпляра мира. _world = new EcsDefaultWorld(); -//Пример из раздела Сущности +// Создание и удаление сущности по примеру из раздела Сущности. var e = _world.NewEntity(); _world.DelEntity(e); ``` > **NOTICE:** Необходимо вызывать EcsWorld.Destroy() у экземпляра мира если он больше не используется, иначе он будет висеть в памяти. ### Конфигурация мира -При создании мира, в конструктор можно передать экземпляр `EcsWorldConfig`. +Для инициализации мира сразу необходимого размера и сокращения времени прогрева, в конструктор можно передать экземпляр `EcsWorldConfig`. ``` c# EcsWorldConfig config = new EcsWorldConfig( - //предварительно инициализирует вместимость мира для 2000 сущностей + // Предварительно инициализирует вместимость мира для 2000 сущностей. entitiesCapacity: 2000, - //предварительно инициализирует вместимость пулов для 2000 компонентов + // Предварительно инициализирует вместимость пулов для 2000 компонентов. poolComponentsCapacity: 2000); _world = new EcsDefaultWorld(config); ``` -> Компоненты и конфиги можно применять для создания расширений в связке с методами расширений. ## Пул -Является контейнером для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей: +Является хранилищем для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей: * `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс `IEcsComponent`; -* `EcsTagPool` - подходит для хранения пустых компонентов-тегов, в сравнении с `EcsPool` имеет лучше оптимизацию памяти и скорости, хранит struct-компоненты с `IEcsTagComponent`; +* `EcsTagPool` - специальный пул для пустых компонентов-тегов, хранит struct-компоненты с `IEcsTagComponent` как bool значения, что в сравнении с реализацией `EcsPool` имеет лучше оптимизацию памяти и скорости; Пулы имеют 5 основных метода и их разновидности: ``` c# @@ -360,10 +364,9 @@ if (poses.Has(entityID)) { /* ... */ } // Удалит компонент у сущности, бросит исключение если у сущности нет этого компонента. poses.Del(entityID); ``` -> Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try` +> Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try`. -Имеется возможность реализации пользовательского пула -> эта функция будет описана в ближайшее время +> Имеется возможность реализации пользовательского пула. Эта функция будет описана в ближайшее время. ## Аспект Это пользовательские классы наследуемые от `EcsAspect` и используемые для взаимодействия с сущностями. Аспекты одновременно являются кешем пулов и маской компонентов для фильтрации сущностей. Можно рассматривать аспекты как описание того с какими сущностями работает система. @@ -371,7 +374,7 @@ poses.Del(entityID); Упрощенный синтаксис: ``` c# using DCFApixels.DragonECS; -... +// ... class Aspect : EcsAspect { // Кешируется пул и Pose добавляется во включающее ограничение. @@ -390,12 +393,11 @@ class Aspect : EcsAspect Явный синтаксис (результат идентичен примеру выше): ``` c# using DCFApixels.DragonECS; -... +// ... class Aspect : EcsAspect { public EcsPool poses; public EcsPool velocities; - // вместо виртуальной функции, можно использовать конструктор Aspect(Builder b) protected override void Init(Builder b) { poses = b.Include(); @@ -411,21 +413,21 @@ class Aspect : EcsAspect В аспекты можно добавлять другие аспекты, тем самым комбинируя их. Ограничения так же будут скомбинированы. ``` c# using DCFApixels.DragonECS; -... +// ... class Aspect : EcsAspect { public OtherAspect1 otherAspect1; public OtherAspect2 otherAspect2; public EcsPool poses; - // функция Init аналогична конструктору Aspect(Builder b) + // Функция Init аналогична конструктору Aspect(Builder b). protected override void Init(Builder b) { - // комбинирует с SomeAspect1. + // Комбинирует с SomeAspect1. otherAspect1 = b.Combine(1); - // хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0. + // Хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0. otherAspect2 = b.Combine(); - // если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude() тут оно будет заменено на b.Include(). + // Если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude() тут оно будет заменено на b.Include(). poses = b.Include(); } } @@ -441,7 +443,7 @@ class Aspect : EcsAspect ## Запросы -Что бы получить необходимый набор сущностей используется метод-запрос `EcsWorld.Where(out TAspcet aspect)`. В качестве `TAspcet` указывается аспект, сущности будут отфильтрованны по маске указанного аспекта. Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чемто похож на аналогичный из Linq). +Что бы получить необходимый набор сущностей используется метод-запрос `EcsWorld.Where(out TAspect aspect)`. В качестве `TAspect` указывается аспект, сущности будут отфильтрованны по маске указанного аспекта. Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чем-то похож на аналогичный из Linq). Пример: ``` c# public class SomeDamageSystem : IEcsRun, IEcsInject @@ -459,7 +461,7 @@ public class SomeDamageSystem : IEcsRun, IEcsInject { foreach (var e in _world.Where(out Aspect a)) { - // Сюда попадают сущности с компонентами Health, DamageSignal и без IsInvulnerable + // Сюда попадают сущности с компонентами Health, DamageSignal и без IsInvulnerable. a.healths.Get(e).points -= a.damageSignals.Get(e).points; } } @@ -471,9 +473,9 @@ public class SomeDamageSystem : IEcsRun, IEcsInject ### EcsSpan Коллекция сущностей, доступная только для чтения и выделяемая только в стеке. Состоит из ссылки на массив, длинны и идентификатора мира. Аналог `ReadOnlySpan`. ``` c# -//Запрос Where возвращает сущности в виде EcsSpan +// Запрос Where возвращает сущности в виде EcsSpan. EcsSpan es = _world.Where(out Aspect a); -//Итерироваться можно по foreach и for +// Итерироваться можно по foreach и for. foreach (var e in es) { // ... @@ -481,68 +483,68 @@ foreach (var e in es) for (int i = 0; i < es.Count; i++) { int e = es[i]; - //... + // ... } ``` > Хотя `EcsSpan` является просто массивом, в нем не допускается дублирование сущностей. ### EcsGroup -Вспомогательная коллекция основаная на spase set для хранения множества сущностей с O(1) операциями добавления/удаления/проверки и т.д. +Вспомогательная коллекция основанная на Sparse Set для хранения множества сущностей с O(1) операциями добавления/удаления/проверки и т.д. ``` c# -//Получем новую группу. EcsWorld содержит в себе пул групп, -//поэтому будет создана новая или переиспользована свободная. +// Получаем новую группу. EcsWorld содержит в себе пул групп, +// поэтому будет создана новая или переиспользована свободная. EcsGroup group = EcsGroup.New(_world); -//Освобождаем группу. +// Освобождаем группу. group.Dispose(); ``` ``` c# -//Добвялем сущность entityID. +// Добавляем сущность entityID. group.Add(entityID); -//Проверяем наличие сущности entityID. +// Проверяем наличие сущности entityID. group.Has(entityID); -//Удялем сущность entityID. +// Удаляем сущность entityID. group.Remove(entityID); ``` ``` c# -//Запрос WhereToGroup возвращает сущности в виде группы только для чтения EcsReadonlyGroup +// Запрос WhereToGroup возвращает сущности в виде группы только для чтения EcsReadonlyGroup. EcsReadonlyGroup group = _world.WhereToGroup(out Aspect a); -//Итерироваться можно по foreach и for +// Итерироваться можно по foreach и for. foreach (var e in group) { - //... + // ... } for (int i = 0; i < group.Count; i++) { int e = group[i]; - //... + // ... } ``` Так как группы это множества, они содержат методы аналогичные `ISet`. Редактирующие методы имеет 2 варианта, с записью результата в groupA, либо с возвращением новой группы: ``` c# -// Объединение groupA и groupB +// Объединение groupA и groupB. groupA.UnionWith(groupB); EcsGroup newGroup = EcsGroup.Union(groupA, groupB); -// Пересечение groupA и groupB +// Пересечение groupA и groupB. groupA.IntersectWith(groupB); EcsGroup newGroup = EcsGroup.Intersect(groupA, groupB); -// Разность groupA и groupB +// Разность groupA и groupB. groupA.ExceptWith(groupB); EcsGroup newGroup = EcsGroup.Except(groupA, groupB); -// Симметрическая разность groupA и groupB +// Симметрическая разность groupA и groupB. groupA.SymmetricExceptWith(groupB); EcsGroup newGroup = EcsGroup.SymmetricExcept(groupA, groupB); -//Разница всех сущностей в мире и groupA +// Разница всех сущностей в мире и groupA. groupA.Inverse(); EcsGroup newGroup = EcsGroup.Inverse(groupA); ``` ## Корень ECS -Это пользовательский класс который является точкой входа для ECS. Основное назначение инициализация, запуск систем на каждый Update движка и очистка по окончанию использования. +Это пользовательский класс который является точкой входа для ECS. Основное назначение инициализация, запуск систем на каждый Update движка и освобождение ресурсов по окончанию использования. ### Пример для Unity ``` c# using DCFApixels.DragonECS; @@ -553,9 +555,9 @@ public class EcsRoot : MonoBehaviour private EcsDefaultWorld _world; private void Start() { - //Создание мира для сущностей и компонентов + // Создание мира для сущностей и компонентов. _world = new EcsDefaultWorld(); - //Создание пайплайна длясистем + // Создание пайплайна для систем. _pipeline = EcsPipeline.New() // Добавление систем. // .Add(new SomeSystem1()) @@ -569,21 +571,21 @@ public class EcsRoot : MonoBehaviour // Завершение построения пайплайна. .Build(); - //Инициализация пайплайна и запуск IEcsPreInit.PreInit - //и IEcsInit.Init у всех добавленных систем + // Инициализация пайплайна и запуск IEcsPreInit.PreInit() + // и IEcsInit.Init() у всех добавленных систем. _pipeline.Init(); } private void Update() { - //Запуск IEcsRun.Run у всех добавленных систем + // Запуск IEcsRun.Run() у всех добавленных систем. _pipeline.Run(); } private void OnDestroy() { - //Запускает IEcsDestroyInit.Destroy у всех добавленных систем + // Запускает IEcsDestroy.Destroy() у всех добавленных систем. _pipeline.Destroy(); _pipeline = null; - //Обязательно удалять миры которые больше не будут использованы + // Обязательно удалять миры которые больше не будут использованы. _world.Destroy(); _world = null; } @@ -615,22 +617,22 @@ public class EcsRoot // Завершение построения пайплайна. .Build(); - // Инициализация пайплайна и запуск IEcsPreInit.PreInit - // и IEcsInit.Init у всех добавленных систем. + // Инициализация пайплайна и запуск IEcsPreInit.PreInit() + // и IEcsInit.Init() у всех добавленных систем. _pipeline.Init(); } // Update-цикл движка. public void Update() { - // Запуск IEcsRun.Run у всех добавленных систем. + // Запуск IEcsRun.Run() у всех добавленных систем. _pipeline.Run(); } // Очистка окружения. public void Destroy() { - // Запускает IEcsDestroyInit.Destroy у всех добавленных систем. + // Запускает IEcsDestroy.Destroy() у всех добавленных систем. _pipeline.Destroy(); _pipeline = null; // Обязательно удалять миры которые больше не будут использованы. @@ -645,7 +647,7 @@ public class EcsRoot # Debug Фреймворк предоставляет дополнительные инструменты для отладки и логирования, не зависящие от среды. Так же многие типы имеют свой DebuggerProxy для более информативного отображения в IDE. ## Мета-Атрибуты -В чистом виде мета-атрибуты не имеют применения, но могут быть использованы для генерации автоматической документации и используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах. +По умолчанию мета-атрибуты не имеют применения, но используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах. А так же могут быть использованы для генерации автоматической документации. ``` c# using DCFApixels.DragonECS; @@ -655,7 +657,7 @@ using DCFApixels.DragonECS; // Используется для группировки типов. [MetaGroup("Abilities/Passive/")] // или [MetaGroup("Abilities", "Passive")] -// Задает цвет типа в системе rgb, где каждый канал принимает значение от 0 до 255, по умолчанию белый. +// Задает цвет типа в RGB кодировке, где каждый канал принимает значение от 0 до 255, по умолчанию белый. [MetaColor(MetaColor.Red)] // или [MetaColor(255, 0, 0)] // Добавляет описание типу. @@ -663,9 +665,9 @@ using DCFApixels.DragonECS; // Добавляет строковые теги. [MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе -public struct Component { } +public struct Component : IEcsComponent { /* ... */ } ``` -Получение мета-информации. С помощью TypeMeta: +Получение мета-информации: ``` c# TypeMeta typeMeta = someComponent.GetMeta(); // или @@ -677,24 +679,17 @@ var description = typeMeta.Description; var group = typeMeta.Group; var tags = typeMeta.Tags; ``` -С помощью EcsDebugUtility: -``` c# -EcsDebugUtility.GetMetaName(someComponent); -EcsDebugUtility.GetColor(someComponent); -EcsDebugUtility.GetDescription(someComponent); -EcsDebugUtility.GetGroup(someComponent); -EcsDebugUtility.GetTags(someComponent); -``` + ## EcsDebug -Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить проект на другие движки, достаточно только реализовать специальный Debug-сервис. +Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить проект на другие движки, достаточно только реализовать соответствующий Debug-сервис. По умолчанию используется `DefaultDebugService` который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от `DebugService` и реализуйте абстрактные члены класса. ``` c# -// Логирование. +// Вывод лога. EcsDebug.Print("Message"); -// Логирование с тегом. +// Вывод лога с тегом. EcsDebug.Print("Tag", "Message"); // Прерывание игры. @@ -748,7 +743,7 @@ var configs = new ConfigContainer() .Set(new SomeDataA(/* ... */)) .Set(new SomeDataB(/* ... */))); EcsDefaultWorld _world = new EcsDefaultWorld(configs); -//... +// ... var _someDataA = _world.Configs.Get(); var _someDataB = _world.Configs.Get(); ``` @@ -757,9 +752,9 @@ var _someDataB = _world.Configs.Get(); _pipeline = EcsPipeline.New()// аналогично _pipeline = EcsPipeline.New(new ConfigContainer()) .Configs.Set(new SomeDataA(/* ... */)) .Configs.Set(new SomeDataB(/* ... */)) - //... + // ... .BuildAndInit(); -//... +// ... var _someDataA = _pipeline.Configs.Get(); var _someDataB = _pipeline.Configs.Get(); ``` @@ -803,15 +798,15 @@ public struct WorldComponent : IEcsWorldComponent public struct WorldComponent : IEcsWorldComponent { private SomeClass _object; // Объект который будет утилизироваться. - private SomeReusedClass _resusedObject; // Объект который будет переиспользоваться. + private SomeReusedClass _reusedObject; // Объект который будет переиспользоваться. public SomeClass Object => _object; - public SomeReusedClass ResusedObject => _resusedObject; + public SomeReusedClass ReusedObject => _reusedObject; void IEcsWorldComponent.Init(ref WorldComponent component, EcsWorld world) { - if (component._resusedObject == null) - component._resusedObject = new SomeReusedClass(); + if (component._reusedObject == null) + component._reusedObject = new SomeReusedClass(); component._object = new SomeClass(); - // Теперь при получении компонента через EcsWorld.Get, _resusedObject и _object уже будут созданы. + // Теперь при получении компонента через EcsWorld.Get, _reusedObject и _object уже будут созданы. } void IEcsWorldComponent.OnDestroy(ref WorldComponent component, EcsWorld world) { @@ -820,15 +815,17 @@ public struct WorldComponent : IEcsWorldComponent component._object = null; // Как вариант тут можно сделать сброс значений у переиспользуемого объекта. - //component._resusedObject.Reset(); + //component._reusedObject.Reset(); - //Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна. - //component = default; + // Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна. + // component = default; } } ``` +> Компоненты и конфиги можно применять для создания расширений в связке с методами расширений. +
# Проекты на DragonECS @@ -845,7 +842,7 @@ public struct WorldComponent : IEcsWorldComponent * [One-Frame Components](https://gist.github.com/DCFApixels/46d512dbcf96c115b94c3af502461f60) * [Шаблоны кода IDE](https://gist.github.com/ctzcs/0ba948b0e53aa41fe1c87796a401660b) и [для Unity](https://gist.github.com/ctzcs/d4c7730cf6cd984fe6f9e0e3f108a0f1) * Графы (Work in progress) - +> *Твое расширение? Если разрабатываешь расширение для DragonECS, пиши [сюда](#обратная-связь).
diff --git a/README-ZN.md b/README-ZN.md new file mode 100644 index 0000000..aa0f9cd --- /dev/null +++ b/README-ZN.md @@ -0,0 +1,873 @@ +

+ +

+ +

+Version +License +Discord +QQ +

+ +# DragonECS - C# 实体组件系统框架 +| Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) | [中文](https://github.com/DCFApixels/DragonECS/blob/main/README-ZN.md) | +| :--- | :--- | :--- | :--- | + + +DragonECS 是一个[实体组件系统](https://en.wikipedia.org/wiki/Entity_component_system)框架。专注于提升便利性、模块性、可扩展性和动态实体修改性能。 用纯C#开发的,没有依赖和代码生成。灵感来自于[LeoEcs](https://github.com/Leopotam/ecslite)。 + +> [!WARNING] +> 该框架是预发布版本,因此 API 可能会有变化。在 `main` 分支中是当前的工作版本。
+> Readme还没完成,如果有不清楚的地方,可以在这里提问 [反馈](#反馈) + +## 目录 +- [安装](#安装) +- [基础概念](#基础概念) + - [实体](#实体) + - [组件](#组件) + - [系统](#系统) +- [框架概念](#框架概念) + - [系统管线](#系统管线) + - [初始化](#初始化) + - [依赖注入](#依赖注入) + - [模块](#模块) + - [层级](#层级) + - [流程](#流程) + - [世界](#世界) + - [池子](#池子) + - [方面](#方面) + - [查询](#查询) + - [集合](#集合) + - [根本](#根本) +- [Debug](#debug) + - [元属性](#元属性) + - [EcsDebug](#ecsdebug) + - [性能分析](#性能分析) +- [Define Symbols](#define-symbols) +- [扩展的功能](#扩展的功能) + - [世界组件](#世界组件) + - [配置](#配置) +- [使用DragonECS的项目](#使用dragonecs的项目) +- [扩展](#扩展) +- [FAQ](#faq) +- [反馈](#反馈) + +
+ +# 安装 +版本的语义 [[打开]](https://gist.github.com/DCFApixels/e53281d4628b19fe5278f3e77a7da9e8#file-dcfapixels_versioning_ru-md) +## 环境 +必备要求: ++ C# 7.3 的最低版本; + +可选要求: ++ 支持NativeAOT; ++ 使用 C# 的游戏引擎:Unity、Godot、MonoGame等。 + +已测试: ++ **Unity:** 最低版本 2020.1.0; + +## 为Unity安装 +> 还建议安装[Unity引擎集成](https://github.com/DCFApixels/DragonECS-Unity)扩展。 +* ### Unity-软件包 +支持以Unity软件包的形式安装。可以通过[git-url添加到PackageManager](https://docs.unity3d.com/cn/2023.2/Manual/upm-ui-giturl.html)或手动添加到`Packages/manifest.json`: +``` +https://github.com/DCFApixels/DragonECS.git +``` + +* ### 作为源代码 +框架也可以通过复制源代码添加到项目中。 +
+ +# 基础概念 +## 实体 +**实体**是附加数据的基础。它们以标识符的形式实现,有两种类型: +* `int` - 是在单个更新中使用的一次性标识符。不建议存储`int`标识符,而应使用 `entlong`; +* `entlong` - 是一个长期标识符,包含一整套用于明确识别的信息; +``` c# +// 在世界中创建一个新实体。 +int entityID = _world.NewEntity(); + +// 删除实体。 +_world.DelEntity(entityID); + +// 一个实体的组件复制到另一个实体。 +_world.CopyEntity(entityID, otherEntityID); + +// 克隆实体。 +int newEntityID = _world.CloneEntity(entityID); +``` + +
+entlong使用 + +``` c# +// int 转换为 entlong。 +entlong entity = _world.GetEntityLong(entityID); +// 或者 +entlong entity = (_world, entityID); + +// 检查实体是否还活着。 +if (entity.IsAlive) { } + +// entlong 转换为 int。如果实体不存在,则会出现异常。 +int entityID = entity.ID; +// 或者 +var (entityID, world) = entity; + +// entlong 转换为int。如果实体仍然存在,则返回 true 及其 int 标识符。 +if (entity.TryGetID(out int entityID)) { } +``` + +
+ +> **NOTICE:** 没有组件的实体不能存在,空实体会在最后一个组件被删除。 + +## 组件 +**组件**是实体的数据。必须实现`IEcsComponent`接口或其他指定类型的组件。 +```c# +struct Health : IEcsComponent +{ + public float health; + public int armor; +} +struct PlayerTag : IEcsTagComponent {} +``` +内置组件类型: +* `IEcsComponent` - 包含数据的组件。 通用类型的组件。 +* `IEcsTagComponent` - 标签组件。 没有数据。 + +## 系统 +**系统**这是基本逻辑,这里定义了实体的行为。系统以用户类的形式实现,用户类至少要实现一个流程接口。基本流程: +```c# +class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy +{ + // 它将在 EcsPipeline.Init() 运行时和 Init 被调用之前被调用一次。 + public void PreInit () { } + + // 它将在 EcsPipeline.Init() 运行时和 PreInit 被调用之后被调用一次。 + public void Init () { } + + // 它将在 EcsPipeline.Run() 运行时调用一次。 + public void Run () { } + + // 它将在 EcsPipeline.Destroy() 运行时调用一次。 + public void Destroy () { } +} +``` +> 如何实现附加流程在[流程部分](#流程)中描述。 + +
+ +# 框架概念 +## 管线 +系统的容器和引擎. 负责设置系统调用队列,提供系统间消息和依赖注入功能。管线以 `EcsPipeline` 类的形式实现。 +### 初始化 +Builder负责初始化管线。系统被添加到Builder中,然后生成管线。 例子: +```c# +EcsPipeline pipeline = EcsPipeline.New() //创建管线的 Builder。 + // 将 System1 添加到系统队列。 + .Add(new System1()) + // 在 System1 之后将 System2 添加到队列中。 + .Add(new System2()) + // 在 System2 之后将 System3 添加到队列中,但只在一个实例中添加。 + .AddUnique(new System3()) + // 完成管线构造并返回其实例。 + .Build(); +pipeline.Init(); // 管线初始化。 +``` + +```c# +class SomeSystem : IEcsRun, IEcsPipelineMember +{ + // 获取系统所属管线的实例。 + public EcsPipeline Pipeline { get ; set; } + + public void Run () { } +} +``` +> 有一种同时构造和初始化的方法`Builder.BuildAndInit();` +### 依赖注入 +框架具有向系统注入依赖的功能。这是一个与管线初始化一起运行的流程,并注入传递给Builder的数据。 +> 内置依赖注入的使用是可选的。 +``` c# +class SomeDataA { /* ... */ } +class SomeDataB : SomeDataA { /* ... */ } + +// ... +SomeDataB _someDataB = new SomeDataB(); +EcsPipeline pipeline = EcsPipeline.New() + //... + // 将 _someDataB 的实例注入到实现 IEcsInject 的系统中。 + .Inject(_someDataB) + // 将实现 IEcsInject 的系统添加到注入树中, + // 这样这些系统也会得到_someDataB。 + .Injector.AddNode() // + // ... + .BuildAndInit(); + +// ... +// IEcsInject 接口及其方法 Inject(T obj) 用于注入。 +class SomeSystem : IEcsInject, IEcsRun +{ + SomeDataA _someDataA + // 在本例中,obj 将是 SomeDataB 类型的实例 + public void Inject(SomeDataA obj) => _someDataA = obj; + + public void Run () + { + _someDataA.DoSomething(); + } +} +``` + +### 模块 +实现一个共同特性的系统组可以组合成模块,模块也可以简单地添加到管线中。 +``` c# +using DCFApixels.DragonECS; +class Module1 : IEcsModule +{ + public void Import(EcsPipeline.Builder b) + { + b.Add(new System1()); + b.Add(new System2()); + b.AddModule(new Module2()); + // ... + } +} +``` +``` c# +EcsPipeline pipeline = EcsPipeline.New() + // ... + .AddModule(new Module1()) + // ... + .BuildAndInit(); +``` + +### 层级 +系统的队列可以分为层。层定义了队列中插入系统的位置。如果要在队列末尾插入一个系统,无论添加的地方如,可以把这个系统添加到 `EcsConsts.END_LAYER` 层级. +``` c# +const string SOME_LAYER = nameof(SOME_LAYER); +EcsPipeline pipeline = EcsPipeline.New() + // ... + // 在最终的 EcsConsts.END_LAYER 层前面插入一个新 SOME_LAYER 层。 + .Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER) + // SomeSystem 系统将插入 SAME_LAYER 层。 + .Add(New SomeSystem(), SOME_LAYER) + // ... + .BuildAndInit(); +``` +嵌入层按以下顺序排列: +* `EcsConst.PRE_BEGIN_LAYER` +* `EcsConst.BEGIN_LAYER` +* `EcsConst.BASIC_LAYER`(如果在添加系统时没有指定层级,则会在这里添加) +* `EcsConst.END_LAYER` +* `EcsConst.POST_END_LAYER` + +## 流程 +流程是实现共同接口的系统队列,例如`IcsRun`接口。用于启动这些流程的是启动器。内置流程会自动启动。还可以实现用户流程。 + +
+内置流程 + +* `IEcsPreInit`, `IEcsInit`, `IEcsRun`, `IEcsDestroy` - 生命周期流程`EcsPipeline`. +* `IEcsInject` - [依赖注入](#依赖注入)的流程. +* `IOnInitInjectionComplete` - 也是[依赖注入](#依赖注入)的流程,而是表示初始化注入的完成。 + +
+ +
+用户流程 + +Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от `EcsRunner`. Пример: +``` c# +//Интерфейс. +interface IDoSomethingProcess : IEcsProcess +{ + void Do(); +} +//Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах +sealed class DoSomethingProcessRunner : EcsRunner, IDoSomethingProcess +{ + public void Do() + { + foreach (var item in Process) item.Do(); + } +} +//... + +//Добавление раннера при создании пайплайна. +_pipeline = EcsPipeline.New() + //... + .AddRunner() + //... + .BuildAndInit(); + +//Запуск раннера если раннер был добавлен. +_pipeline.GetRunner.Do() + +//Или если раннер не был добавлен(Вызов GetRunnerInstance так же добавит раннер в пайплайн). +_pipeline.GetRunnerInstance.Do() +``` +> Раннеры имеют ряд требований к реализации: +> * Наследоваться от `EcsRunner` можно только напрямую; +> * Раннер может содержать только один интерфейс(за исключением `IEcsProcess`); +> * Наследуемый класс `EcsRunner,` должен так же реализовать интерфейс `T`; + +> Не рекомендуется в цикле вызывать `GetRunner`, иначе кешируйте полученный раннер. +
+ +## 世界 +是实体和组件的容器。 +``` c# +// 创建世界实例。 +_world = new EcsDefaultWorld(); +// 创建和删除实体,本例来自实体部分。 +var e = _world.NewEntity(); +_world.DelEntity(e); +``` +> **NOTICE:** 如果实例化的 `EcsWorld` 不再使用,需要调用 `EcsWorld.Destroy()` 来释放它,否则它将继续占用内存。 + +### 世界配置 +为了初始化所需大小的世界并缩短预热时间,可以在构造函数中传递 EcsWorldConfig 的实例。 + +``` c# +EcsWorldConfig config = new EcsWorldConfig( + // 预先初始化世界的容量为2000个实体。 + entitiesCapacity: 2000, + // 预先初始化池子的容量为2000个组件。 + poolComponentsCapacity: 2000); +_world = new EcsDefaultWorld(config); +``` + +## 池子 +是组件的存储库,池子有添加/读取/编辑/删除实体上组件的方法。有几种类型的池,用于不同的目的: +* `EcsPool` - 通用池,存储实现`IEcsComponent`接口的 struct 组件; +* `EcsTagPool` - 专门用于存储实现`IEcsTagComponent`接口的空标签 struct 组件的池。存储的组件仅作为bool值存储,因此与EcsPool相比,具有更好的内存和速度优化; + +池有5种主要方法及其品种: +``` c# +// 从世界中获取组件池的一种方法。 +EcsPool poses = _world.GetPool(); + +// 向实体添加一个组件,如果实体已经拥有该组件,则抛出异常。 +ref var addedPose = ref poses.Add(entityID); + +// 返回一个组件,如果实体没有该组件,则抛出异常。 +ref var gettedPose = ref poses.Get(entityID); + +// 返回一个只读组件,如果实体没有该组件,则抛出异常。 +ref readonly var readonlyPose = ref poses.Read(entityID); + +// 如果实体具有组件,则返回true,否则返回false。 +if (poses.Has(entityID)) { /* ... */ } + +// 从实体中删除组件,如果实体没有此组件,则抛发异常。 +poses.Del(entityID); +``` +> 有一些 “安全 ”方法会首先检查组件是否存在,这些方法的名称以 “Try ”开头。 + +> 可以实现用户池。稍后将介绍这一功能。 + +## 方面 +这些是继承自 EcsAspect 的用户类,用于与实体进行交互。方面同时充当池的缓存和实体组件的过滤掩码。可以把方面视为系统处理哪些实体的描述。 + +简化语法: +``` c# +using DCFApixels.DragonECS; +// ... +class Aspect : EcsAspect +{ + // 缓存池,并将 Pose 添加到包含限制中。 + public EcsPool poses = Inc; + // 缓存池,并将 Velocity 添加到包含限制中。 + public EcsPool velocities = Inc; + // 缓存池,并将 FreezedTag 添加到排除限制中。 + public EcsTagPool freezedTags = Exc; + + // 在查询时将检查包含限制掩码中的组件存在性, + // 同时确保排除限制中的组件不存在。 + // 还有Opt模式,它只缓存池,不影响掩码。 +} +``` + +显式语法(结果与上面的示例相同): +``` c# +using DCFApixels.DragonECS; +// ... +class Aspect : EcsAspect +{ + public EcsPool poses; + public EcsPool velocities; + protected override void Init(Builder b) + { + poses = b.Include(); + velocities = b.Include(); + b.Exclude(); + } +} +``` + +
+结合方面 + +В аспекты можно добавлять другие аспекты, тем самым комбинируя их. Ограничения так же будут скомбинированы. +``` c# +using DCFApixels.DragonECS; +... +class Aspect : EcsAspect +{ + public OtherAspect1 otherAspect1; + public OtherAspect2 otherAspect2; + public EcsPool poses; + + // функция Init аналогична конструктору Aspect(Builder b) + protected override void Init(Builder b) + { + // комбинирует с SomeAspect1. + otherAspect1 = b.Combine(1); + // хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0. + otherAspect2 = b.Combine(); + // если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude() тут оно будет заменено на b.Include(). + poses = b.Include(); + } +} +``` +Если будут конфликтующие ограничения у комбинируемых аспектов, то новые ограничения будут заменять добавленные ранее. Ограничения корневого аспекта всегда заменяют ограничения из добавленных аспектов. Визуальный пример комбинации ограничений: +| | cmp1 | cmp2 | cmp3 | cmp4 | cmp5 | разрешение конфликтных ограничений| +| :--- | :--- | :--- | :--- | :--- | :--- |:--- | +| OtherAspect2 | :heavy_check_mark: | :x: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | | +| OtherAspect1 | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :x: | :heavy_minus_sign: | Для `cmp2` будет выбрано :heavy_check_mark: | +| Aspect | :x: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | Для `cmp1` будет выбрано :x: | +| Итоговые ограничения | :x: | :heavy_check_mark: | :heavy_minus_sign: | :x: | :heavy_check_mark: | | + +
+ +## 查询 +要获取所需的实体集,需要使用 `EcsWorld.Where(out TAspect aspect)` 查询方法。在 TAspect 参数中指定的是一个方面,实体将按照指定方面的掩码进行过滤。`Where`查询既适用于`EcsWorld`也适用于框架的集合(在这方面,Where与Linq中的类似查询方式有些相似)。 +示例: +``` c# +public class SomeDamageSystem : IEcsRun, IEcsInject +{ + class Aspect : EcsAspect + { + public EcsPool healths = Inc; + public EcsPool damageSignals = Inc; + public EcsTagPool isInvulnerables = Exc; + } + EcsDefaultWorld _world; + public void Inject(EcsDefaultWorld world) => _world = world; + + public void Run() + { + foreach (var e in _world.Where(out Aspect a)) + { + // 在这里处理具有 Health 和 DamageSignal,但没有 IsInvulnerable 组件的实体。 + a.healths.Get(e).points -= a.damageSignals.Get(e).points; + } + } +} +``` + +## 集合 + +### EcsSpan +只读且仅在堆栈上分配的实体的集合。包含对数组的引用、长度和世界标识符。类似于 `ReadOnlySpan`. +``` c# +// Where 查询返回 EcsSpan 类型的实体集合。 +EcsSpan es = _world.Where(out Aspect a); +// 可以使用 foreach 和 for 进行迭代。 +foreach (var e in es) +{ + // ... +} +for (int i = 0; i < es.Count; i++) +{ + int e = es[i]; + // ... +} +``` +> 虽然`EcsSpan`只是数组,但它不允许重复实体。 + +### EcsGroup +基于稀疏集(Sparse Set)的辅助集合,用于存储实体集合,支持 O(1) 的添加、删除、检查等操作。 +``` c# +// 获取新的组。EcsWorld 包含组池, +// 因此将创建一个新的组或重新使用空闲的组。 +EcsGroup group = EcsGroup.New(_world); +// 将组返回到组池。 +group.Dispose(); +``` +``` c# +// 添加 entityID 实体。 +group.Add(entityID); +// 检查 entityID 实体的存在. +group.Has(entityID); +// 删除 entityID 实体。 +group.Remove(entityID); +``` +``` c# +// WhereToGroup 查询返回 EcsReadonlyGroup 类型的实体集合。 +EcsReadonlyGroup es = _world.WhereToGroup(out Aspect a); +// 可以使用 foreach 和 for 进行迭代。 +foreach (var e in es) +{ + // ... +} +for (int i = 0; i < es.Count; i++) +{ + int e = es[i]; + // ... +} +``` +由于组是没有重复元素的集合,因此组支持集合运算,并包含类似于`Iset`的方法。编辑方法有两种方式:一种是将结果写入到 groupA 中,另一种是返回一个新的群组: + +``` c# +// 合集 groupA 和 groupB。 +groupA.UnionWith(groupB); +EcsGroup newGroup = EcsGroup.Union(groupA, groupB); + +// 交集 groupA 和 groupB。 +groupA.IntersectWith(groupB); +EcsGroup newGroup = EcsGroup.Intersect(groupA, groupB); + +// 差集 groupA 和 groupB。 +groupA.ExceptWith(groupB); +EcsGroup newGroup = EcsGroup.Except(groupA, groupB); + +// 对称差集 groupA 和 groupB。 +groupA.SymmetricExceptWith(groupB); +EcsGroup newGroup = EcsGroup.SymmetricExcept(groupA, groupB); + +// 全部实体与 groupA 的差集。 +groupA.Inverse(); +EcsGroup newGroup = EcsGroup.Inverse(groupA); +``` + +## 根本 +这是一个用户定义的类,作为 ECS 的入口点。其主要目的是初始化和启动每个 Update 引擎上的系统,并在使用结束后释放资源。 +### Пример для Unity +``` c# +using DCFApixels.DragonECS; +using UnityEngine; +public class EcsRoot : MonoBehaviour +{ + private EcsPipeline _pipeline; + private EcsDefaultWorld _world; + private void Start() + { + // 创建实体和组件的世界。 + _world = new EcsDefaultWorld(); + // 创建系统的管线。 + _pipeline = EcsPipeline.New() + // 添加系统。 + // .Add(new SomeSystem1()) + // .Add(new SomeSystem2()) + // .Add(new SomeSystem3()) + + // 将世界注入系统。 + .Inject(_world) + // 其他注入。 + // .Inject(SomeData) + + // 完成管线构造。 + .Build(); + // 初始化管线并运行每个添加系统的 IEcsPreInit.PreInit() + // 和 IEcsInit.Init()。 + _pipeline.Init(); + } + private void Update() + { + // 运行每个添加系统的 IEcsRun.Run() 方法。 + _pipeline.Run(); + } + private void OnDestroy() + { + // 运行每个添加系统的 IEcsDestroy.Destroy() 方法。 + _pipeline.Destroy(); + _pipeline = null; + // 必须销毁不再使用的世界。 + _world.Destroy(); + _world = null; + } +} +``` +### Общий пример +``` c# +using DCFApixels.DragonECS; +public class EcsRoot +{ + private EcsPipeline _pipeline; + private EcsDefaultWorld _world; + // 环境的初始化。 + public void Init() + { + // 创建实体和组件的世界。 + _world = new EcsDefaultWorld(); + // 创建系统的管线。 + _pipeline = EcsPipeline.New() + // 添加系统。 + // .Add(new SomeSystem1()) + // .Add(new SomeSystem2()) + // .Add(new SomeSystem3()) + + // 将世界注入系统。 + .Inject(_world) + // 其他注入。 + // .Inject(SomeData) + + // 完成管线构造。 + .Build(); + // 初始化管线并运行每个添加系统的 IEcsPreInit.PreInit() + // 和 IEcsInit.Init()。 + _pipeline.Init(); + } + + // 引擎的 Update 循环。 + public void Update() + { + // 运行每个添加系统的 IEcsRun.Run() 方法。 + _pipeline.Run(); + } + + // 环境的清理。 + public void Destroy() + { + // 运行每个添加系统的 IEcsDestroy.Destroy() 方法。 + _pipeline.Destroy(); + _pipeline = null; + // 必须销毁不再使用的世界。 + _world.Destroy(); + _world = null; + } +} +``` + +
+ +# Debug +该框架提供了额外的调试和日志记录工具,不依赖于环境此外,许多类型都有自己的 DebuggerProxy,以便在 IDE 中更详细地显示信息。 +## 元属性 +默认情况下,元属性没有用处,在与引擎集成时用于指定在调试工具和编辑器中的显示方式。还可以用于生成自动文档。 +``` c# +using DCFApixels.DragonECS; + +// 设置用户自定义类型名称,默认情况下使用类型名称。 +[MetaName("SomeComponent")] + +// 用于对类型进行分组。 +[MetaGroup("Abilities/Passive/")] // 或者 [MetaGroup("Abilities", "Passive")] + +// 使用 RGB 编码设置显示颜色,每个通道的值范围从0到255,默认为白色。 +[MetaColor(MetaColor.Red)] // 或者 [MetaColor(255, 0, 0)] + +// 为类型添加描述。 +[MetaDescription("The quick brown fox jumps over the lazy dog")] + +// 添加字符串标签。 +[MetaTags("Tag1", "Tag2", ...)] // 使用 [MetaTags(MetaTags.HIDDEN))] 可隐藏在编辑器中。 +public struct Component : IEcsComponent { /* ... */ } +``` +获取元信息: +``` c# +TypeMeta typeMeta = someComponent.GetMeta(); +// 或者 +TypeMeta typeMeta = pool.ComponentType.ToMeta(); + +var name = typeMeta.Name; +var color = typeMeta.Color; +var description = typeMeta.Description; +var group = typeMeta.Group; +var tags = typeMeta.Tags; +``` + +## EcsDebug +具有调试和日志记录方法集. 实现为一个静态类,调用 DebugService 的方法. DebugService 是环境调试系统与 EcsDebug 之间的中介. 这使得可以将项目移植到其他引擎上,而无需修改项目的调试代码,只需要实现特定的 DebugService 即可。 + +默认情况下使用 `DefaultDebugService` 会将日志输出到控制台. 要实现自定义的,可以创建继承自`DebugService'的类并实现抽象类成员。 + +``` c# +// 输出日志。 +EcsDebug.Print("Message"); + +// 输出带标签的日志。 +EcsDebug.Print("Tag", "Message"); + +// 中断游戏。 +EcsDebug.Break(); + +// 设置其他 DebugService。 +EcsDebug.Set(); +``` + +## 性能分析 +``` c# +// 创建名为 SomeMarker 的标记器。 +private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker"); + +// ... + +marker.Begin(); +// 要测量速度的代码。 +marker.End(); + +// 或者 + +using (marker.Auto()) +{ + // 要测量速度的代码。 +} +``` + +
+ +# Define Symbols ++ `DISABLE_POOLS_EVENTS` - 禁用池子事件的响应行为。 ++ `ENABLE_DRAGONECS_DEBUGGER` - 在发布版中启用 EcsDebug 的工作。 ++ `ENABLE_DRAGONECS_ASSERT_CHECKS` - 在发布版中启用可忽略的检查和异常。 ++ `REFLECTION_DISABLED` - 完全限制框架内部代码中的 Reflection 使用。 ++ `DISABLE_DEBUG` - 用于不支持手动禁用 DEBUG 的环境,例如 Unity。 ++ `ENABLE_DUMMY_SPAN` - 如果环境不支持 Span 类型,则启用它的替代。 ++ `DISABLE_CATH_EXCEPTIONS` - 禁用默认的异常处理行为。默认情况下,框架将捕获异常并通过 EcsDebug 输出异常信息,然后继续执行。 + +
+ +# 扩展的功能 +为了增强框架的可扩展性,提供了其他工具。 + +## 配置 +`EcsWorld` 和 `EcsPipeline` 类的构造函数可以接受实现 `IConfigContainer` 或 `IConfigContainerWriter` 接口的配置容器。使用这些容器可以传递数据和依赖关系。内置的容器实现是 `ConfigContainer`,但也可以使用自定义的实现。
+为世界使用配置容器的示例: +``` c# +var configs = new ConfigContainer() + .Set(new EcsWorldConfig(entitiesCapacity: 2000, poolsCapacity: 2000) + .Set(new SomeDataA(/* ... */)) + .Set(new SomeDataB(/* ... */))); +EcsDefaultWorld _world = new EcsDefaultWorld(configs); +// ... +var _someDataA = _world.Configs.Get(); +var _someDataB = _world.Configs.Get(); +``` +为管线使用配置容器的示例: +``` c# +_pipeline = EcsPipeline.New()// 相当于 _pipeline = EcsPipeline.New(new ConfigContainer())。 + .Configs.Set(new SomeDataA(/* ... */)) + .Configs.Set(new SomeDataB(/* ... */)) + // ... + .BuildAndInit(); +// ... +var _someDataA = _pipeline.Configs.Get(); +var _someDataB = _pipeline.Configs.Get(); +``` + +## 世界组件 +使用世界组件可以将额外的数据附加到世界上. 世界组件使用 `struct` 类型来实现。访问组件的 `Get` 方法经过了速度优化,速度几乎与访问类字段相同。 + +``` c# +// 获取组件。 +ref WorldComponent component = ref _world.Get(); +``` +世界组件实现: +``` c# +public struct WorldComponent +{ + // 数据。 +} +``` +或者: +``` c# +public struct WorldComponent : IEcsWorldComponent +{ + // 数据。 + + // 接口的初始化方法。 + void IEcsWorldComponent.Init(ref WorldComponent component, EcsWorld world) + { + // 初始化组件时执行的操作。在从 EcsWorld.Get 返回之前调用。 + } + // 接口的销毁方法。 + void IEcsWorldComponent.OnDestroy(ref WorldComponent component, EcsWorld world) + { + // 在调用 EcsWorld.Destroy 时执行的操作。 + // 调用 OnDestroy 要求用户手动将组件重置为默认状态,如果需要的话。 + component = default; + } +} +``` + +
+使用示例 + +`IEcsWorldComponent` 接口的事件可用于自动初始化组件字段和释放资源。 +``` c# +public struct WorldComponent : IEcsWorldComponent +{ + private SomeClass _object; // 被回收的对象。 + private SomeReusedClass _reusedObject; // 被重复使用的对象。 + public SomeClass Object => _object; + public SomeReusedClass ReusedObject => _reusedObject; + void IEcsWorldComponent.Init(ref WorldComponent component, EcsWorld world) + { + if (component._reusedObject == null) + component._reusedObject = new SomeReusedClass(); + component._object = new SomeClass(); + // 当通过 EcsWorld.Get 获取组件时,_reusedObject 和 _object 已经被创建。 + } + void IEcsWorldComponent.OnDestroy(ref WorldComponent component, EcsWorld world) + { + // 处理不再需要的对象,并释放对它的引用,以便让 GC 回收它。 + component._object.Dispose(); + component._object = null; + + // 如果需要的话,可以重置可重复使用对象的值。 + // component._reusedObject.Reset(); + + // 因为在这个示例中不需要完全重置组件,所以下面这行不需要。 + // component = default; + } +} +``` +
+ +> 世界组件和配置容器可以与扩展方法结合使用,用于创建扩展。 + +
+ +# 使用DragonECS的项目 +* [3D Platformer (Example)](https://github.com/DCFApixels/3D-Platformer-DragonECS) +![alt text](https://i.ibb.co/hm7Lrm4/Platformer.png) + +
+ +# 扩展 +* [Unity集成](https://github.com/DCFApixels/DragonECS-Unity) +* [自动依赖注入](https://github.com/DCFApixels/DragonECS-AutoInjections) +* [经典C#多线程](https://github.com/DCFApixels/DragonECS-ClassicThreads) +* [Hybrid](https://github.com/DCFApixels/DragonECS-Hybrid) +* [单帧组件](https://gist.github.com/DCFApixels/46d512dbcf96c115b94c3af502461f60) +* [IDE代码模板](https://gist.github.com/ctzcs/0ba948b0e53aa41fe1c87796a401660b) и [Unity代码模板](https://gist.github.com/ctzcs/d4c7730cf6cd984fe6f9e0e3f108a0f1) +* Graphs (Work in progress) + +> *你的扩展?如果你正在开发DragonECS的扩展,可以[在此处发布](#反馈). + +
+ +# FAQ +## 'ReadOnlySpan<>' could not be found +在Unity 2020.1.x版本中,控制台可能会出现以下错误: +``` +The type or namespace name 'ReadOnlySpan<>' could not be found (are you missing a using directive or an assembly reference?) +``` +要解决这个问题,需要在`Project Settings/Player/Other Settings/Scripting Define Symbols`中添加`ENABLE_DUMMY_SPAN`指令. +
+ +# 反馈 ++ Discord (RU-EN) [https://discord.gg/kqmJjExuCf](https://discord.gg/kqmJjExuCf) ++ QQ (中文) [949562781](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=IbDcH43vhfArb30luGMP1TMXB3GCHzxm&authKey=s%2FJfqvv46PswFq68irnGhkLrMR6y9tf%2FUn2mogYizSOGiS%2BmB%2B8Ar9I%2Fnr%2Bs4oS%2B&noverify=0&group_code=949562781) + +


+


+


+


+


+


diff --git a/README.md b/README.md index 2ed496c..c60e7bb 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@

# DragonECS - C# Entity Component System Framework -| Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) | -| :--- | :--- | :--- | +| Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) | [中文](https://github.com/DCFApixels/DragonECS/blob/main/README-ZN.md) | +| :--- | :--- | :--- | :--- | The [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Framework aims to maximize usability, modularity, extensibility and performance of dynamic entity changes. Without code generation and dependencies. Inspired by [LeoEcs](https://github.com/Leopotam/ecslite).