update readme, add chinese language

This commit is contained in:
Mikhail 2024-07-31 15:08:29 +08:00
parent 753250025f
commit 655541e543
3 changed files with 989 additions and 119 deletions

View File

@ -9,9 +9,9 @@
<a href="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"><img alt="QQ" src="https://img.shields.io/badge/QQ-JOIN-00b269?logo=tencentqq&logoColor=%23ffffff&style=for-the-badge"></a> <a href="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"><img alt="QQ" src="https://img.shields.io/badge/QQ-JOIN-00b269?logo=tencentqq&logoColor=%23ffffff&style=for-the-badge"></a>
</p> </p>
# DragonECS - C# Entity Component System Framework # DragonECS - C# Entity Component System Фреймворк
| 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) |
| :--- | :--- | :--- | | :--- | :--- | :--- | :--- |
DragonECS - это [ECS](https://en.wikipedia.org/wiki/Entity_component_system) фреймворк нацеленный на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Разработан на чистом C#, без зависимостей и генерации кода. Вдохновлен [LeoEcs](https://github.com/Leopotam/ecslite). 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(). // Будет вызван один раз в момент работы EcsPipeline.Run().
public void Run () { } public void Run () { }
// Будет вызван один раз в момент работы EcsPipeline.Destroy() // Будет вызван один раз в момент работы EcsPipeline.Destroy().
public void Destroy () { } public void Destroy () { }
} }
``` ```
@ -162,16 +162,16 @@ class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy
### Построение ### Построение
За построение пайплайна отвечает Builder. В Builder добавляются системы, а в конце строится пайплайн. Пример: За построение пайплайна отвечает Builder. В Builder добавляются системы, а в конце строится пайплайн. Пример:
```c# ```c#
EcsPipelone pipeline = EcsPipeline.New() //Создает Builder пайплайна EcsPipeline pipeline = EcsPipeline.New() // Создает Builder пайплайна.
// Добавляет систему System1 в очередь систем // Добавляет систему System1 в очередь систем.
.Add(new System1()) .Add(new System1())
// Добавляет System2 в очередь после System1 // Добавляет System2 в очередь после System1.
.Add(new System2()) .Add(new System2())
// Добавляет System3 в очередь после System2, но в единичном экземпляре // Добавляет System3 в очередь после System2, но в единичном экземпляре.
.AddUnique(new System3()) .AddUnique(new System3())
// Завершает построение пайплайна и возвращает его экземпляр // Завершает построение пайплайна и возвращает его экземпляр.
.Build(); .Build();
pipeline.Init(); // Инициализация пайплайна pipeline.Init(); // Инициализация пайплайна.
``` ```
```c# ```c#
@ -193,13 +193,15 @@ class SomeDataB : SomeDataA { /*...*/ }
// ... // ...
SomeDataB _someDataB = new SomeDataB(); SomeDataB _someDataB = new SomeDataB();
EcsPipelone pipeline = EcsPipeline.New() EcsPipeline pipeline = EcsPipeline.New()
// ... // ...
// Внедрит _someDataB в системы реализующие IEcsInject<SomeDataB>. // Внедрит _someDataB в системы реализующие IEcsInject<SomeDataB>.
.Inject(_someDataB) .Inject(_someDataB)
// Добавит системы реализующие IEcsInject<SomeDataA> в дерево инъекции // Добавит системы реализующие IEcsInject<SomeDataA> в дерево инъекции,
// теперь эти системы так же получат _someDataB. // теперь эти системы так же получат _someDataB.
.Injector.AddNode<SomeDataA>() // .Injector.AddNode<SomeDataA>()
// ...
.Add(new SomeSystem())
// ... // ...
.BuildAndInit(); .BuildAndInit();
@ -208,7 +210,7 @@ EcsPipelone pipeline = EcsPipeline.New()
class SomeSystem : IEcsInject<SomeDataA>, IEcsRun class SomeSystem : IEcsInject<SomeDataA>, IEcsRun
{ {
SomeDataA _someDataA SomeDataA _someDataA
//obj будет экземпляром типа SomeDataB // obj будет экземпляром типа SomeDataB.
public void Inject(SomeDataA obj) => _someDataA = obj; public void Inject(SomeDataA obj) => _someDataA = obj;
public void Run () public void Run ()
@ -222,20 +224,21 @@ class SomeSystem : IEcsInject<SomeDataA>, IEcsRun
Группы систем реализующие общую фичу можно объединять в модули, и просто добавлять модули в Pipeline. Группы систем реализующие общую фичу можно объединять в модули, и просто добавлять модули в Pipeline.
``` c# ``` c#
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
class Module : IEcsModule class Module1 : IEcsModule
{ {
public void Import(EcsPipeline.Builder b) public void Import(EcsPipeline.Builder b)
{ {
b.Add(new System1()); b.Add(new System1());
b.Add(new System2(), EcsConsts.END_LAYER); // данная система будет добавлена в слой EcsConsts.END_LAYER b.Add(new System2());
b.Add(new System3()); b.AddModule(new Module2());
// ...
} }
} }
``` ```
``` c# ``` c#
EcsPipelone pipeline = EcsPipeline.New() EcsPipeline pipeline = EcsPipeline.New()
// ... // ...
.AddModule(new Module()) .AddModule(new Module1())
// ... // ...
.BuildAndInit(); .BuildAndInit();
``` ```
@ -244,10 +247,12 @@ EcsPipelone pipeline = EcsPipeline.New()
Очередь систем можно разбить на слои. Слой определяет место в очереди для вставки систем. Если необходимо чтобы какая-то система была вставлена в конце очереди, вне зависимости от места добавления, эту систему можно добавить в слой EcsConsts.END_LAYER. Очередь систем можно разбить на слои. Слой определяет место в очереди для вставки систем. Если необходимо чтобы какая-то система была вставлена в конце очереди, вне зависимости от места добавления, эту систему можно добавить в слой EcsConsts.END_LAYER.
``` c# ``` c#
const string SOME_LAYER = nameof(SOME_LAYER); const string SOME_LAYER = nameof(SOME_LAYER);
EcsPipelone pipeline = EcsPipeline.New() EcsPipeline pipeline = EcsPipeline.New()
// ... // ...
.Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER) // Вставляет новый слой перед конечным слоем EcsConsts.END_LAYER // Вставляет новый слой перед конечным слоем EcsConsts.END_LAYER
.Add(New SomeSystem(), SOME_LAYER) // Система SomeSystem будет вставлена в слой SOME_LAYER .Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER)
// Система SomeSystem будет вставлена в слой SOME_LAYER
.Add(New SomeSystem(), SOME_LAYER)
// ... // ...
.BuildAndInit(); .BuildAndInit();
``` ```
@ -314,31 +319,30 @@ _pipeline.GetRunnerInstance<DoSomethingProcessRunner>.Do()
## Мир ## Мир
Является контейнером для сущностей и компонентов. Является контейнером для сущностей и компонентов.
``` c# ``` c#
//Создание экземпляра мира // Создание экземпляра мира.
_world = new EcsDefaultWorld(); _world = new EcsDefaultWorld();
//Пример из раздела Сущности // Создание и удаление сущности по примеру из раздела Сущности.
var e = _world.NewEntity(); var e = _world.NewEntity();
_world.DelEntity(e); _world.DelEntity(e);
``` ```
> **NOTICE:** Необходимо вызывать EcsWorld.Destroy() у экземпляра мира если он больше не используется, иначе он будет висеть в памяти. > **NOTICE:** Необходимо вызывать EcsWorld.Destroy() у экземпляра мира если он больше не используется, иначе он будет висеть в памяти.
### Конфигурация мира ### Конфигурация мира
При создании мира, в конструктор можно передать экземпляр `EcsWorldConfig`. Для инициализации мира сразу необходимого размера и сокращения времени прогрева, в конструктор можно передать экземпляр `EcsWorldConfig`.
``` c# ``` c#
EcsWorldConfig config = new EcsWorldConfig( EcsWorldConfig config = new EcsWorldConfig(
//предварительно инициализирует вместимость мира для 2000 сущностей // Предварительно инициализирует вместимость мира для 2000 сущностей.
entitiesCapacity: 2000, entitiesCapacity: 2000,
//предварительно инициализирует вместимость пулов для 2000 компонентов // Предварительно инициализирует вместимость пулов для 2000 компонентов.
poolComponentsCapacity: 2000); poolComponentsCapacity: 2000);
_world = new EcsDefaultWorld(config); _world = new EcsDefaultWorld(config);
``` ```
> Компоненты и конфиги можно применять для создания расширений в связке с методами расширений.
## Пул ## Пул
Является контейнером для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей: Является хранилищем для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей:
* `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс `IEcsComponent`; * `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс `IEcsComponent`;
* `EcsTagPool` - подходит для хранения пустых компонентов-тегов, в сравнении с `EcsPool` имеет лучше оптимизацию памяти и скорости, хранит struct-компоненты с `IEcsTagComponent`; * `EcsTagPool` - специальный пул для пустых компонентов-тегов, хранит struct-компоненты с `IEcsTagComponent` как bool значения, что в сравнении с реализацией `EcsPool` имеет лучше оптимизацию памяти и скорости;
Пулы имеют 5 основных метода и их разновидности: Пулы имеют 5 основных метода и их разновидности:
``` c# ``` c#
@ -360,10 +364,9 @@ if (poses.Has(entityID)) { /* ... */ }
// Удалит компонент у сущности, бросит исключение если у сущности нет этого компонента. // Удалит компонент у сущности, бросит исключение если у сущности нет этого компонента.
poses.Del(entityID); poses.Del(entityID);
``` ```
> Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try` > Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try`.
Имеется возможность реализации пользовательского пула > Имеется возможность реализации пользовательского пула. Эта функция будет описана в ближайшее время.
> эта функция будет описана в ближайшее время
## Аспект ## Аспект
Это пользовательские классы наследуемые от `EcsAspect` и используемые для взаимодействия с сущностями. Аспекты одновременно являются кешем пулов и маской компонентов для фильтрации сущностей. Можно рассматривать аспекты как описание того с какими сущностями работает система. Это пользовательские классы наследуемые от `EcsAspect` и используемые для взаимодействия с сущностями. Аспекты одновременно являются кешем пулов и маской компонентов для фильтрации сущностей. Можно рассматривать аспекты как описание того с какими сущностями работает система.
@ -371,7 +374,7 @@ poses.Del(entityID);
Упрощенный синтаксис: Упрощенный синтаксис:
``` c# ``` c#
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
... // ...
class Aspect : EcsAspect class Aspect : EcsAspect
{ {
// Кешируется пул и Pose добавляется во включающее ограничение. // Кешируется пул и Pose добавляется во включающее ограничение.
@ -390,12 +393,11 @@ class Aspect : EcsAspect
Явный синтаксис (результат идентичен примеру выше): Явный синтаксис (результат идентичен примеру выше):
``` c# ``` c#
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
... // ...
class Aspect : EcsAspect class Aspect : EcsAspect
{ {
public EcsPool<Pose> poses; public EcsPool<Pose> poses;
public EcsPool<Velocity> velocities; public EcsPool<Velocity> velocities;
// вместо виртуальной функции, можно использовать конструктор Aspect(Builder b)
protected override void Init(Builder b) protected override void Init(Builder b)
{ {
poses = b.Include<Pose>(); poses = b.Include<Pose>();
@ -411,21 +413,21 @@ class Aspect : EcsAspect
В аспекты можно добавлять другие аспекты, тем самым комбинируя их. Ограничения так же будут скомбинированы. В аспекты можно добавлять другие аспекты, тем самым комбинируя их. Ограничения так же будут скомбинированы.
``` c# ``` c#
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
... // ...
class Aspect : EcsAspect class Aspect : EcsAspect
{ {
public OtherAspect1 otherAspect1; public OtherAspect1 otherAspect1;
public OtherAspect2 otherAspect2; public OtherAspect2 otherAspect2;
public EcsPool<Pose> poses; public EcsPool<Pose> poses;
// функция Init аналогична конструктору Aspect(Builder b) // Функция Init аналогична конструктору Aspect(Builder b).
protected override void Init(Builder b) protected override void Init(Builder b)
{ {
// комбинирует с SomeAspect1. // Комбинирует с SomeAspect1.
otherAspect1 = b.Combine<OtherAspect1>(1); otherAspect1 = b.Combine<OtherAspect1>(1);
// хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0. // Хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0.
otherAspect2 = b.Combine<OtherAspect2>(); otherAspect2 = b.Combine<OtherAspect2>();
// если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude<Pose>() тут оно будет заменено на b.Include<Pose>(). // Если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude<Pose>() тут оно будет заменено на b.Include<Pose>().
poses = b.Include<Pose>(); poses = b.Include<Pose>();
} }
} }
@ -441,7 +443,7 @@ class Aspect : EcsAspect
</details> </details>
## Запросы ## Запросы
Что бы получить необходимый набор сущностей используется метод-запрос `EcsWorld.Where<TAspcet>(out TAspcet aspect)`. В качестве `TAspcet` указывается аспект, сущности будут отфильтрованны по маске указанного аспекта. Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чемто похож на аналогичный из Linq). Что бы получить необходимый набор сущностей используется метод-запрос `EcsWorld.Where<TAspect>(out TAspect aspect)`. В качестве `TAspect` указывается аспект, сущности будут отфильтрованны по маске указанного аспекта. Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чем-то похож на аналогичный из Linq).
Пример: Пример:
``` c# ``` c#
public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld> public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
@ -459,7 +461,7 @@ public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
{ {
foreach (var e in _world.Where(out Aspect a)) 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; a.healths.Get(e).points -= a.damageSignals.Get(e).points;
} }
} }
@ -471,9 +473,9 @@ public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
### EcsSpan ### EcsSpan
Коллекция сущностей, доступная только для чтения и выделяемая только в стеке. Состоит из ссылки на массив, длинны и идентификатора мира. Аналог `ReadOnlySpan<int>`. Коллекция сущностей, доступная только для чтения и выделяемая только в стеке. Состоит из ссылки на массив, длинны и идентификатора мира. Аналог `ReadOnlySpan<int>`.
``` c# ``` c#
//Запрос Where возвращает сущности в виде EcsSpan // Запрос Where возвращает сущности в виде EcsSpan.
EcsSpan es = _world.Where(out Aspect a); EcsSpan es = _world.Where(out Aspect a);
//Итерироваться можно по foreach и for // Итерироваться можно по foreach и for.
foreach (var e in es) foreach (var e in es)
{ {
// ... // ...
@ -487,26 +489,26 @@ for (int i = 0; i < es.Count; i++)
> Хотя `EcsSpan` является просто массивом, в нем не допускается дублирование сущностей. > Хотя `EcsSpan` является просто массивом, в нем не допускается дублирование сущностей.
### EcsGroup ### EcsGroup
Вспомогательная коллекция основаная на spase set для хранения множества сущностей с O(1) операциями добавления/удаления/проверки и т.д. Вспомогательная коллекция основанная на Sparse Set для хранения множества сущностей с O(1) операциями добавления/удаления/проверки и т.д.
``` c# ``` c#
//Получем новую группу. EcsWorld содержит в себе пул групп, // Получаем новую группу. EcsWorld содержит в себе пул групп,
// поэтому будет создана новая или переиспользована свободная. // поэтому будет создана новая или переиспользована свободная.
EcsGroup group = EcsGroup.New(_world); EcsGroup group = EcsGroup.New(_world);
// Освобождаем группу. // Освобождаем группу.
group.Dispose(); group.Dispose();
``` ```
``` c# ``` c#
//Добвялем сущность entityID. // Добавляем сущность entityID.
group.Add(entityID); group.Add(entityID);
// Проверяем наличие сущности entityID. // Проверяем наличие сущности entityID.
group.Has(entityID); group.Has(entityID);
//Удялем сущность entityID. // Удаляем сущность entityID.
group.Remove(entityID); group.Remove(entityID);
``` ```
``` c# ``` c#
//Запрос WhereToGroup возвращает сущности в виде группы только для чтения EcsReadonlyGroup // Запрос WhereToGroup возвращает сущности в виде группы только для чтения EcsReadonlyGroup.
EcsReadonlyGroup group = _world.WhereToGroup(out Aspect a); EcsReadonlyGroup group = _world.WhereToGroup(out Aspect a);
//Итерироваться можно по foreach и for // Итерироваться можно по foreach и for.
foreach (var e in group) foreach (var e in group)
{ {
// ... // ...
@ -520,29 +522,29 @@ for (int i = 0; i < group.Count; i++)
Так как группы это множества, они содержат методы аналогичные `ISet<T>`. Редактирующие методы имеет 2 варианта, с записью результата в groupA, либо с возвращением новой группы: Так как группы это множества, они содержат методы аналогичные `ISet<T>`. Редактирующие методы имеет 2 варианта, с записью результата в groupA, либо с возвращением новой группы:
``` c# ``` c#
// Объединение groupA и groupB // Объединение groupA и groupB.
groupA.UnionWith(groupB); groupA.UnionWith(groupB);
EcsGroup newGroup = EcsGroup.Union(groupA, groupB); EcsGroup newGroup = EcsGroup.Union(groupA, groupB);
// Пересечение groupA и groupB // Пересечение groupA и groupB.
groupA.IntersectWith(groupB); groupA.IntersectWith(groupB);
EcsGroup newGroup = EcsGroup.Intersect(groupA, groupB); EcsGroup newGroup = EcsGroup.Intersect(groupA, groupB);
// Разность groupA и groupB // Разность groupA и groupB.
groupA.ExceptWith(groupB); groupA.ExceptWith(groupB);
EcsGroup newGroup = EcsGroup.Except(groupA, groupB); EcsGroup newGroup = EcsGroup.Except(groupA, groupB);
// Симметрическая разность groupA и groupB // Симметрическая разность groupA и groupB.
groupA.SymmetricExceptWith(groupB); groupA.SymmetricExceptWith(groupB);
EcsGroup newGroup = EcsGroup.SymmetricExcept(groupA, groupB); EcsGroup newGroup = EcsGroup.SymmetricExcept(groupA, groupB);
//Разница всех сущностей в мире и groupA // Разница всех сущностей в мире и groupA.
groupA.Inverse(); groupA.Inverse();
EcsGroup newGroup = EcsGroup.Inverse(groupA); EcsGroup newGroup = EcsGroup.Inverse(groupA);
``` ```
## Корень ECS ## Корень ECS
Это пользовательский класс который является точкой входа для ECS. Основное назначение инициализация, запуск систем на каждый Update движка и очистка по окончанию использования. Это пользовательский класс который является точкой входа для ECS. Основное назначение инициализация, запуск систем на каждый Update движка и освобождение ресурсов по окончанию использования.
### Пример для Unity ### Пример для Unity
``` c# ``` c#
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
@ -553,9 +555,9 @@ public class EcsRoot : MonoBehaviour
private EcsDefaultWorld _world; private EcsDefaultWorld _world;
private void Start() private void Start()
{ {
//Создание мира для сущностей и компонентов // Создание мира для сущностей и компонентов.
_world = new EcsDefaultWorld(); _world = new EcsDefaultWorld();
//Создание пайплайна длясистем // Создание пайплайна для систем.
_pipeline = EcsPipeline.New() _pipeline = EcsPipeline.New()
// Добавление систем. // Добавление систем.
// .Add(new SomeSystem1()) // .Add(new SomeSystem1())
@ -569,21 +571,21 @@ public class EcsRoot : MonoBehaviour
// Завершение построения пайплайна. // Завершение построения пайплайна.
.Build(); .Build();
//Инициализация пайплайна и запуск IEcsPreInit.PreInit // Инициализация пайплайна и запуск IEcsPreInit.PreInit()
//и IEcsInit.Init у всех добавленных систем // и IEcsInit.Init() у всех добавленных систем.
_pipeline.Init(); _pipeline.Init();
} }
private void Update() private void Update()
{ {
//Запуск IEcsRun.Run у всех добавленных систем // Запуск IEcsRun.Run() у всех добавленных систем.
_pipeline.Run(); _pipeline.Run();
} }
private void OnDestroy() private void OnDestroy()
{ {
//Запускает IEcsDestroyInit.Destroy у всех добавленных систем // Запускает IEcsDestroy.Destroy() у всех добавленных систем.
_pipeline.Destroy(); _pipeline.Destroy();
_pipeline = null; _pipeline = null;
//Обязательно удалять миры которые больше не будут использованы // Обязательно удалять миры которые больше не будут использованы.
_world.Destroy(); _world.Destroy();
_world = null; _world = null;
} }
@ -615,22 +617,22 @@ public class EcsRoot
// Завершение построения пайплайна. // Завершение построения пайплайна.
.Build(); .Build();
// Инициализация пайплайна и запуск IEcsPreInit.PreInit // Инициализация пайплайна и запуск IEcsPreInit.PreInit()
// и IEcsInit.Init у всех добавленных систем. // и IEcsInit.Init() у всех добавленных систем.
_pipeline.Init(); _pipeline.Init();
} }
// Update-цикл движка. // Update-цикл движка.
public void Update() public void Update()
{ {
// Запуск IEcsRun.Run у всех добавленных систем. // Запуск IEcsRun.Run() у всех добавленных систем.
_pipeline.Run(); _pipeline.Run();
} }
// Очистка окружения. // Очистка окружения.
public void Destroy() public void Destroy()
{ {
// Запускает IEcsDestroyInit.Destroy у всех добавленных систем. // Запускает IEcsDestroy.Destroy() у всех добавленных систем.
_pipeline.Destroy(); _pipeline.Destroy();
_pipeline = null; _pipeline = null;
// Обязательно удалять миры которые больше не будут использованы. // Обязательно удалять миры которые больше не будут использованы.
@ -645,7 +647,7 @@ public class EcsRoot
# Debug # Debug
Фреймворк предоставляет дополнительные инструменты для отладки и логирования, не зависящие от среды. Так же многие типы имеют свой DebuggerProxy для более информативного отображения в IDE. Фреймворк предоставляет дополнительные инструменты для отладки и логирования, не зависящие от среды. Так же многие типы имеют свой DebuggerProxy для более информативного отображения в IDE.
## Мета-Атрибуты ## Мета-Атрибуты
В чистом виде мета-атрибуты не имеют применения, но могут быть использованы для генерации автоматической документации и используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах. По умолчанию мета-атрибуты не имеют применения, но используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах. А так же могут быть использованы для генерации автоматической документации.
``` c# ``` c#
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
@ -655,7 +657,7 @@ using DCFApixels.DragonECS;
// Используется для группировки типов. // Используется для группировки типов.
[MetaGroup("Abilities/Passive/")] // или [MetaGroup("Abilities", "Passive")] [MetaGroup("Abilities/Passive/")] // или [MetaGroup("Abilities", "Passive")]
// Задает цвет типа в системе rgb, где каждый канал принимает значение от 0 до 255, по умолчанию белый. // Задает цвет типа в RGB кодировке, где каждый канал принимает значение от 0 до 255, по умолчанию белый.
[MetaColor(MetaColor.Red)] // или [MetaColor(255, 0, 0)] [MetaColor(MetaColor.Red)] // или [MetaColor(255, 0, 0)]
// Добавляет описание типу. // Добавляет описание типу.
@ -663,9 +665,9 @@ using DCFApixels.DragonECS;
// Добавляет строковые теги. // Добавляет строковые теги.
[MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе [MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе
public struct Component { } public struct Component : IEcsComponent { /* ... */ }
``` ```
Получение мета-информации. С помощью TypeMeta: Получение мета-информации:
``` c# ``` c#
TypeMeta typeMeta = someComponent.GetMeta(); TypeMeta typeMeta = someComponent.GetMeta();
// или // или
@ -677,24 +679,17 @@ var description = typeMeta.Description;
var group = typeMeta.Group; var group = typeMeta.Group;
var tags = typeMeta.Tags; var tags = typeMeta.Tags;
``` ```
С помощью EcsDebugUtility:
``` c#
EcsDebugUtility.GetMetaName(someComponent);
EcsDebugUtility.GetColor(someComponent);
EcsDebugUtility.GetDescription(someComponent);
EcsDebugUtility.GetGroup(someComponent);
EcsDebugUtility.GetTags(someComponent);
```
## EcsDebug ## EcsDebug
Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить проект на другие движки, достаточно только реализовать специальный Debug-сервис. Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить проект на другие движки, достаточно только реализовать соответствующий Debug-сервис.
По умолчанию используется `DefaultDebugService` который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от `DebugService` и реализуйте абстрактные члены класса. По умолчанию используется `DefaultDebugService` который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от `DebugService` и реализуйте абстрактные члены класса.
``` c# ``` c#
// Логирование. // Вывод лога.
EcsDebug.Print("Message"); EcsDebug.Print("Message");
// Логирование с тегом. // Вывод лога с тегом.
EcsDebug.Print("Tag", "Message"); EcsDebug.Print("Tag", "Message");
// Прерывание игры. // Прерывание игры.
@ -803,15 +798,15 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
public struct WorldComponent : IEcsWorldComponent<WorldComponent> public struct WorldComponent : IEcsWorldComponent<WorldComponent>
{ {
private SomeClass _object; // Объект который будет утилизироваться. private SomeClass _object; // Объект который будет утилизироваться.
private SomeReusedClass _resusedObject; // Объект который будет переиспользоваться. private SomeReusedClass _reusedObject; // Объект который будет переиспользоваться.
public SomeClass Object => _object; public SomeClass Object => _object;
public SomeReusedClass ResusedObject => _resusedObject; public SomeReusedClass ReusedObject => _reusedObject;
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world) void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
{ {
if (component._resusedObject == null) if (component._reusedObject == null)
component._resusedObject = new SomeReusedClass(); component._reusedObject = new SomeReusedClass();
component._object = new SomeClass(); component._object = new SomeClass();
// Теперь при получении компонента через EcsWorld.Get, _resusedObject и _object уже будут созданы. // Теперь при получении компонента через EcsWorld.Get, _reusedObject и _object уже будут созданы.
} }
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world) void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
{ {
@ -820,7 +815,7 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
component._object = null; component._object = null;
// Как вариант тут можно сделать сброс значений у переиспользуемого объекта. // Как вариант тут можно сделать сброс значений у переиспользуемого объекта.
//component._resusedObject.Reset(); //component._reusedObject.Reset();
// Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна. // Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна.
// component = default; // component = default;
@ -829,6 +824,8 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
``` ```
</details> </details>
> Компоненты и конфиги можно применять для создания расширений в связке с методами расширений.
</br> </br>
# Проекты на DragonECS # Проекты на DragonECS
@ -845,7 +842,7 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
* [One-Frame Components](https://gist.github.com/DCFApixels/46d512dbcf96c115b94c3af502461f60) * [One-Frame Components](https://gist.github.com/DCFApixels/46d512dbcf96c115b94c3af502461f60)
* [Шаблоны кода IDE](https://gist.github.com/ctzcs/0ba948b0e53aa41fe1c87796a401660b) и [для Unity](https://gist.github.com/ctzcs/d4c7730cf6cd984fe6f9e0e3f108a0f1) * [Шаблоны кода IDE](https://gist.github.com/ctzcs/0ba948b0e53aa41fe1c87796a401660b) и [для Unity](https://gist.github.com/ctzcs/d4c7730cf6cd984fe6f9e0e3f108a0f1)
* Графы (Work in progress) * Графы (Work in progress)
<!--* Твое расширение? Если разрабатываешь свои расширения для DragonECS, дай знать и они будут добавлены сюда--> > *Твое расширение? Если разрабатываешь расширение для DragonECS, пиши [сюда](#обратная-связь).
</br> </br>

873
README-ZN.md Normal file
View File

@ -0,0 +1,873 @@
<p align="center">
<img width="660" src="https://github.com/DCFApixels/DragonECS/assets/99481254/c09e385e-08c1-4c04-904a-36ad7e25e45b">
</p>
<p align="center">
<img alt="Version" src="https://img.shields.io/github/package-json/v/DCFApixels/DragonECS?color=%23ff4e85&style=for-the-badge">
<img alt="License" src="https://img.shields.io/github/license/DCFApixels/DragonECS?color=ff4e85&style=for-the-badge">
<a href="https://discord.gg/kqmJjExuCf"><img alt="Discord" src="https://img.shields.io/badge/Discord-JOIN-00b269?logo=discord&logoColor=%23ffffff&style=for-the-badge"></a>
<a href="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"><img alt="QQ" src="https://img.shields.io/badge/QQ-JOIN-00b269?logo=tencentqq&logoColor=%23ffffff&style=for-the-badge"></a>
</p>
# 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` 分支中是当前的工作版本。</br>
> Readme还没完成如果有不清楚的地方可以在这里提问 [反馈](#反馈)
## 目录
- [安装](#安装)
- [基础概念](#基础概念)
- [实体](#实体)
- [组件](#组件)
- [系统](#系统)
- [框架概念](#框架概念)
- [系统管线](#系统管线)
- [初始化](#初始化)
- [依赖注入](#依赖注入)
- [模块](#模块)
- [层级](#层级)
- [流程](#流程)
- [世界](#世界)
- [池子](#池子)
- [方面](#方面)
- [查询](#查询)
- [集合](#集合)
- [根本](#根本)
- [Debug](#debug)
- [元属性](#元属性)
- [EcsDebug](#ecsdebug)
- [性能分析](#性能分析)
- [Define Symbols](#define-symbols)
- [扩展的功能](#扩展的功能)
- [世界组件](#世界组件)
- [配置](#配置)
- [使用DragonECS的项目](#使用dragonecs的项目)
- [扩展](#扩展)
- [FAQ](#faq)
- [反馈](#反馈)
</br>
# 安装
版本的语义 [[打开]](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
```
* ### 作为源代码
框架也可以通过复制源代码添加到项目中。
</br>
# 基础概念
## 实体
**实体**是附加数据的基础。它们以标识符的形式实现,有两种类型:
* `int` - 是在单个更新中使用的一次性标识符。不建议存储`int`标识符,而应使用 `entlong`
* `entlong` - 是一个长期标识符,包含一整套用于明确识别的信息;
``` c#
// 在世界中创建一个新实体。
int entityID = _world.NewEntity();
// 删除实体。
_world.DelEntity(entityID);
// 一个实体的组件复制到另一个实体。
_world.CopyEntity(entityID, otherEntityID);
// 克隆实体。
int newEntityID = _world.CloneEntity(entityID);
```
<details>
<summary>entlong使用</summary>
``` 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)) { }
```
</details>
> **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 () { }
}
```
> 如何实现附加流程在[流程部分](#流程)中描述。
</br>
# 框架概念
## 管线
系统的容器和引擎. 负责设置系统调用队列,提供系统间消息和依赖注入功能。管线以 `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<SomeDataB> 的系统中。
.Inject(_someDataB)
// 将实现 IEcsInject<SomeDataA> 的系统添加到注入树中,
// 这样这些系统也会得到_someDataB。
.Injector.AddNode<SomeDataA>() //
// ...
.BuildAndInit();
// ...
// IEcsInject<T> 接口及其方法 Inject(T obj) 用于注入。
class SomeSystem : IEcsInject<SomeDataA>, 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`接口。用于启动这些流程的是启动器。内置流程会自动启动。还可以实现用户流程。
<details>
<summary>内置流程</summary>
* `IEcsPreInit`, `IEcsInit`, `IEcsRun`, `IEcsDestroy` - 生命周期流程`EcsPipeline`.
* `IEcsInject<T>` - [依赖注入](#依赖注入)的流程.
* `IOnInitInjectionComplete` - 也是[依赖注入](#依赖注入)的流程,而是表示初始化注入的完成。
</details>
<details>
<summary>用户流程</summary>
Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от `EcsRunner<TInterface>`. Пример:
``` c#
//Интерфейс.
interface IDoSomethingProcess : IEcsProcess
{
void Do();
}
//Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах
sealed class DoSomethingProcessRunner : EcsRunner<IDoSomethingProcess>, IDoSomethingProcess
{
public void Do()
{
foreach (var item in Process) item.Do();
}
}
//...
//Добавление раннера при создании пайплайна.
_pipeline = EcsPipeline.New()
//...
.AddRunner<DoSomethingProcessRunner>()
//...
.BuildAndInit();
//Запуск раннера если раннер был добавлен.
_pipeline.GetRunner<IDoSomethingProcess>.Do()
//Или если раннер не был добавлен(Вызов GetRunnerInstance так же добавит раннер в пайплайн).
_pipeline.GetRunnerInstance<DoSomethingProcessRunner>.Do()
```
> Раннеры имеют ряд требований к реализации:
> * Наследоваться от `EcsRunner<T>` можно только напрямую;
> * Раннер может содержать только один интерфейс(за исключением `IEcsProcess`);
> * Наследуемый класс `EcsRunner<T>,` должен так же реализовать интерфейс `T`;
> Не рекомендуется в цикле вызывать `GetRunner`, иначе кешируйте полученный раннер.
</details>
## 世界
是实体和组件的容器。
``` 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<Pose> poses = _world.GetPool<Pose>();
// 向实体添加一个组件,如果实体已经拥有该组件,则抛出异常。
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<Pose> poses = Inc;
// 缓存池,并将 Velocity 添加到包含限制中。
public EcsPool<Velocity> velocities = Inc;
// 缓存池,并将 FreezedTag 添加到排除限制中。
public EcsTagPool<FreezedTag> freezedTags = Exc;
// 在查询时将检查包含限制掩码中的组件存在性,
// 同时确保排除限制中的组件不存在。
// 还有Opt模式它只缓存池不影响掩码。
}
```
显式语法(结果与上面的示例相同):
``` c#
using DCFApixels.DragonECS;
// ...
class Aspect : EcsAspect
{
public EcsPool<Pose> poses;
public EcsPool<Velocity> velocities;
protected override void Init(Builder b)
{
poses = b.Include<Pose>();
velocities = b.Include<Velocity>();
b.Exclude<FreezedTag>();
}
}
```
<details>
<summary>结合方面</summary>
В аспекты можно добавлять другие аспекты, тем самым комбинируя их. Ограничения так же будут скомбинированы.
``` c#
using DCFApixels.DragonECS;
...
class Aspect : EcsAspect
{
public OtherAspect1 otherAspect1;
public OtherAspect2 otherAspect2;
public EcsPool<Pose> poses;
// функция Init аналогична конструктору Aspect(Builder b)
protected override void Init(Builder b)
{
// комбинирует с SomeAspect1.
otherAspect1 = b.Combine<OtherAspect1>(1);
// хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0.
otherAspect2 = b.Combine<OtherAspect2>();
// если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude<Pose>() тут оно будет заменено на b.Include<Pose>().
poses = b.Include<Pose>();
}
}
```
Если будут конфликтующие ограничения у комбинируемых аспектов, то новые ограничения будут заменять добавленные ранее. Ограничения корневого аспекта всегда заменяют ограничения из добавленных аспектов. Визуальный пример комбинации ограничений:
| | 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: | |
</details>
## 查询
要获取所需的实体集,需要使用 `EcsWorld.Where<TAspect>(out TAspect aspect)` 查询方法。在 TAspect 参数中指定的是一个方面,实体将按照指定方面的掩码进行过滤。`Where`查询既适用于`EcsWorld`也适用于框架的集合在这方面Where与Linq中的类似查询方式有些相似
示例:
``` c#
public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
{
class Aspect : EcsAspect
{
public EcsPool<Health> healths = Inc;
public EcsPool<DamageSignal> damageSignals = Inc;
public EcsTagPool<IsInvulnerable> 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<int>`.
``` 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<T>`的方法。编辑方法有两种方式:一种是将结果写入到 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;
}
}
```
</br>
# 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<OtherDebugService>();
```
## 性能分析
``` c#
// 创建名为 SomeMarker 的标记器。
private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker");
// ...
marker.Begin();
// 要测量速度的代码。
marker.End();
// 或者
using (marker.Auto())
{
// 要测量速度的代码。
}
```
</br>
# 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 输出异常信息,然后继续执行。
</br>
# 扩展的功能
为了增强框架的可扩展性,提供了其他工具。
## 配置
`EcsWorld``EcsPipeline` 类的构造函数可以接受实现 `IConfigContainer``IConfigContainerWriter` 接口的配置容器。使用这些容器可以传递数据和依赖关系。内置的容器实现是 `ConfigContainer`,但也可以使用自定义的实现。</br>
为世界使用配置容器的示例:
``` 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<SomeDataA>();
var _someDataB = _world.Configs.Get<SomeDataB>();
```
为管线使用配置容器的示例:
``` c#
_pipeline = EcsPipeline.New()// 相当于 _pipeline = EcsPipeline.New(new ConfigContainer())。
.Configs.Set(new SomeDataA(/* ... */))
.Configs.Set(new SomeDataB(/* ... */))
// ...
.BuildAndInit();
// ...
var _someDataA = _pipeline.Configs.Get<SomeDataA>();
var _someDataB = _pipeline.Configs.Get<SomeDataB>();
```
## 世界组件
使用世界组件可以将额外的数据附加到世界上. 世界组件使用 `struct` 类型来实现。访问组件的 `Get` 方法经过了速度优化,速度几乎与访问类字段相同。
``` c#
// 获取组件。
ref WorldComponent component = ref _world.Get<WorldComponent>();
```
世界组件实现:
``` c#
public struct WorldComponent
{
// 数据。
}
```
或者:
``` c#
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
{
// 数据。
// 接口的初始化方法。
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
{
// 初始化组件时执行的操作。在从 EcsWorld.Get 返回之前调用。
}
// 接口的销毁方法。
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
{
// 在调用 EcsWorld.Destroy 时执行的操作。
// 调用 OnDestroy 要求用户手动将组件重置为默认状态,如果需要的话。
component = default;
}
}
```
<details>
<summary>使用示例</summary>
`IEcsWorldComponent<T>` 接口的事件可用于自动初始化组件字段和释放资源。
``` c#
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
{
private SomeClass _object; // 被回收的对象。
private SomeReusedClass _reusedObject; // 被重复使用的对象。
public SomeClass Object => _object;
public SomeReusedClass ReusedObject => _reusedObject;
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
{
if (component._reusedObject == null)
component._reusedObject = new SomeReusedClass();
component._object = new SomeClass();
// 当通过 EcsWorld.Get 获取组件时_reusedObject 和 _object 已经被创建。
}
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
{
// 处理不再需要的对象,并释放对它的引用,以便让 GC 回收它。
component._object.Dispose();
component._object = null;
// 如果需要的话,可以重置可重复使用对象的值。
// component._reusedObject.Reset();
// 因为在这个示例中不需要完全重置组件,所以下面这行不需要。
// component = default;
}
}
```
</details>
> 世界组件和配置容器可以与扩展方法结合使用,用于创建扩展。
</br>
# 使用DragonECS的项目
* [3D Platformer (Example)](https://github.com/DCFApixels/3D-Platformer-DragonECS)
![alt text](https://i.ibb.co/hm7Lrm4/Platformer.png)
</br>
# 扩展
* [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的扩展可以[在此处发布](#反馈).
</br>
# 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`指令.
</br>
# 反馈
+ 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)
</br></br></br>
</br></br></br>
</br></br></br>
</br></br></br>
</br></br></br>
</br></br></br>

View File

@ -12,8 +12,8 @@
</p> </p>
# DragonECS - C# Entity Component System Framework # 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). 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).