mirror of
https://github.com/DCFApixels/DragonECS.git
synced 2025-11-13 00:55:55 +08:00
Merge branch 'main' into bf
This commit is contained in:
commit
b2a06e88eb
263
README-RU.md
263
README-RU.md
@ -9,9 +9,37 @@
|
||||
<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# 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 Фреймворк
|
||||
|
||||
<table>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td colspan="3">Readme Languages:</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md">
|
||||
<img src="https://github.com/user-attachments/assets/3c699094-f8e6-471d-a7c1-6d2e9530e721"></br>
|
||||
<span>Русский</span>
|
||||
</a>
|
||||
</td>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS">
|
||||
<img src="https://github.com/user-attachments/assets/30528cb5-f38e-49f0-b23e-d001844ae930"></br>
|
||||
<span>English</span>
|
||||
</a>
|
||||
</td>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS/blob/main/README-ZH.md">
|
||||
<img src="https://github.com/user-attachments/assets/8e598a9a-826c-4a1f-b842-0c56301d2927"></br>
|
||||
<span>中文</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</br>
|
||||
|
||||
DragonECS - это [ECS](https://en.wikipedia.org/wiki/Entity_component_system) фреймворк нацеленный на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Разработан на чистом C#, без зависимостей и генерации кода. Вдохновлен [LeoEcs](https://github.com/Leopotam/ecslite).
|
||||
|
||||
@ -148,7 +176,7 @@ class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy
|
||||
// Будет вызван один раз в момент работы EcsPipeline.Run().
|
||||
public void Run () { }
|
||||
|
||||
// Будет вызван один раз в момент работы EcsPipeline.Destroy()
|
||||
// Будет вызван один раз в момент работы EcsPipeline.Destroy().
|
||||
public void Destroy () { }
|
||||
}
|
||||
```
|
||||
@ -162,16 +190,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 +216,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<SomeDataB>.
|
||||
.Inject(_someDataB)
|
||||
// Добавит системы реализующие IEcsInject<SomeDataA> в дерево инъекции
|
||||
// Добавит системы реализующие IEcsInject<SomeDataA> в дерево инъекции,
|
||||
// теперь эти системы так же получат _someDataB.
|
||||
.Injector.AddNode<SomeDataA>() //
|
||||
//...
|
||||
.Injector.AddNode<SomeDataA>()
|
||||
// ...
|
||||
.Add(new SomeSystem())
|
||||
// ...
|
||||
.BuildAndInit();
|
||||
|
||||
//...
|
||||
// ...
|
||||
// Для внедрения используется интерфейс IEcsInject<T> и его метод Inject(T obj)
|
||||
class SomeSystem : IEcsInject<SomeDataA>, IEcsRun
|
||||
{
|
||||
SomeDataA _someDataA
|
||||
//obj будет экземпляром типа SomeDataB
|
||||
// obj будет экземпляром типа SomeDataB.
|
||||
public void Inject(SomeDataA obj) => _someDataA = obj;
|
||||
|
||||
public void Run ()
|
||||
@ -222,33 +252,36 @@ class SomeSystem : IEcsInject<SomeDataA>, 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();
|
||||
```
|
||||
|
||||
### Слои
|
||||
Очередь систем можно разбить на слои. Слой определяет место в очереди для вставки систем. Если необходимо чтобы какая-то система была вставлена в конце очереди, вне зависимости от места добавления, эту систему можно добавить в слой EcsConsts.END_LAYER.
|
||||
Очередь систем можно разбить на слои. Слой определяет место в очереди для вставки систем. Например, если необходимо чтобы какая-то система была вставлена в конце очереди, вне зависимости от места добавления, эту систему можно добавить в слой 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 +308,12 @@ EcsPipelone pipeline = EcsPipeline.New()
|
||||
|
||||
Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от `EcsRunner<TInterface>`. Пример:
|
||||
``` c#
|
||||
//Интерфейс.
|
||||
// Интерфейс процесса.
|
||||
interface IDoSomethingProcess : IEcsProcess
|
||||
{
|
||||
void Do();
|
||||
}
|
||||
//Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах
|
||||
// Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах
|
||||
sealed class DoSomethingProcessRunner : EcsRunner<IDoSomethingProcess>, IDoSomethingProcess
|
||||
{
|
||||
public void Do()
|
||||
@ -288,19 +321,19 @@ sealed class DoSomethingProcessRunner : EcsRunner<IDoSomethingProcess>, IDoSomet
|
||||
foreach (var item in Process) item.Do();
|
||||
}
|
||||
}
|
||||
//...
|
||||
// ...
|
||||
|
||||
//Добавление раннера при создании пайплайна.
|
||||
// Добавление раннера при создании пайплайна.
|
||||
_pipeline = EcsPipeline.New()
|
||||
//...
|
||||
.AddRunner<DoSomethingProcessRunner>()
|
||||
//...
|
||||
.BuildAndInit();
|
||||
|
||||
//Запуск раннера если раннер был добавлен.
|
||||
// Запуск раннера если раннер был добавлен.
|
||||
_pipeline.GetRunner<IDoSomethingProcess>.Do()
|
||||
|
||||
//Или если раннер не был добавлен(Вызов GetRunnerInstance так же добавит раннер в пайплайн).
|
||||
// Или если раннер не был добавлен(Вызов GetRunnerInstance так же добавит раннер в пайплайн).
|
||||
_pipeline.GetRunnerInstance<DoSomethingProcessRunner>.Do()
|
||||
```
|
||||
> Раннеры имеют ряд требований к реализации:
|
||||
@ -314,31 +347,30 @@ _pipeline.GetRunnerInstance<DoSomethingProcessRunner>.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 +392,9 @@ if (poses.Has(entityID)) { /* ... */ }
|
||||
// Удалит компонент у сущности, бросит исключение если у сущности нет этого компонента.
|
||||
poses.Del(entityID);
|
||||
```
|
||||
> Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try`
|
||||
> Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try`.
|
||||
|
||||
Имеется возможность реализации пользовательского пула
|
||||
> эта функция будет описана в ближайшее время
|
||||
> Имеется возможность реализации пользовательского пула. Эта функция будет описана в ближайшее время.
|
||||
|
||||
## Аспект
|
||||
Это пользовательские классы наследуемые от `EcsAspect` и используемые для взаимодействия с сущностями. Аспекты одновременно являются кешем пулов и маской компонентов для фильтрации сущностей. Можно рассматривать аспекты как описание того с какими сущностями работает система.
|
||||
@ -371,7 +402,7 @@ poses.Del(entityID);
|
||||
Упрощенный синтаксис:
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
...
|
||||
// ...
|
||||
class Aspect : EcsAspect
|
||||
{
|
||||
// Кешируется пул и Pose добавляется во включающее ограничение.
|
||||
@ -390,12 +421,11 @@ class Aspect : EcsAspect
|
||||
Явный синтаксис (результат идентичен примеру выше):
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
...
|
||||
// ...
|
||||
class Aspect : EcsAspect
|
||||
{
|
||||
public EcsPool<Pose> poses;
|
||||
public EcsPool<Velocity> velocities;
|
||||
// вместо виртуальной функции, можно использовать конструктор Aspect(Builder b)
|
||||
protected override void Init(Builder b)
|
||||
{
|
||||
poses = b.Include<Pose>();
|
||||
@ -411,21 +441,20 @@ class Aspect : EcsAspect
|
||||
В аспекты можно добавлять другие аспекты, тем самым комбинируя их. Ограничения так же будут скомбинированы.
|
||||
``` 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.
|
||||
// Комбинирует с SomeAspect1.
|
||||
otherAspect1 = b.Combine<OtherAspect1>(1);
|
||||
// хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0.
|
||||
// Хотя для OtherAspect1 метод Combine был вызван раньше, сначала будет скомбинирован с OtherAspect2, так как по умолчанию order = 0.
|
||||
otherAspect2 = b.Combine<OtherAspect2>();
|
||||
// если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude<Pose>() тут оно будет заменено на b.Include<Pose>().
|
||||
// Если в OtherAspect1 или в OtherAspect2 было ограничение b.Exclude<Pose>() тут оно будет заменено на b.Include<Pose>().
|
||||
poses = b.Include<Pose>();
|
||||
}
|
||||
}
|
||||
@ -441,7 +470,7 @@ class Aspect : EcsAspect
|
||||
</details>
|
||||
|
||||
## Запросы
|
||||
Что бы получить необходимый набор сущностей используется метод-запрос `EcsWorld.Where<TAspcet>(out TAspcet aspect)`. В качестве `TAspcet` указывается аспект, сущности будут отфильтрованны по маске указанного аспекта. Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чемто похож на аналогичный из Linq).
|
||||
Что бы получить необходимый набор сущностей используется метод-запрос `EcsWorld.Where<TAspect>(out TAspect aspect)`. В качестве `TAspect` указывается аспект, сущности будут отфильтрованны по маске указанного аспекта. Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чем-то похож на аналогичный из Linq).
|
||||
Пример:
|
||||
``` c#
|
||||
public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
|
||||
@ -459,7 +488,7 @@ public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
|
||||
{
|
||||
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 +500,9 @@ public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
|
||||
### EcsSpan
|
||||
Коллекция сущностей, доступная только для чтения и выделяемая только в стеке. Состоит из ссылки на массив, длинны и идентификатора мира. Аналог `ReadOnlySpan<int>`.
|
||||
``` c#
|
||||
//Запрос Where возвращает сущности в виде EcsSpan
|
||||
// Запрос Where возвращает сущности в виде EcsSpan.
|
||||
EcsSpan es = _world.Where(out Aspect a);
|
||||
//Итерироваться можно по foreach и for
|
||||
// Итерироваться можно по foreach и for.
|
||||
foreach (var e in es)
|
||||
{
|
||||
// ...
|
||||
@ -481,68 +510,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<T>`. Редактирующие методы имеет 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 +582,9 @@ public class EcsRoot : MonoBehaviour
|
||||
private EcsDefaultWorld _world;
|
||||
private void Start()
|
||||
{
|
||||
//Создание мира для сущностей и компонентов
|
||||
// Создание мира для сущностей и компонентов.
|
||||
_world = new EcsDefaultWorld();
|
||||
//Создание пайплайна длясистем
|
||||
// Создание пайплайна для систем.
|
||||
_pipeline = EcsPipeline.New()
|
||||
// Добавление систем.
|
||||
// .Add(new SomeSystem1())
|
||||
@ -569,21 +598,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 +644,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 +674,7 @@ public class EcsRoot
|
||||
# Debug
|
||||
Фреймворк предоставляет дополнительные инструменты для отладки и логирования, не зависящие от среды. Так же многие типы имеют свой DebuggerProxy для более информативного отображения в IDE.
|
||||
## Мета-Атрибуты
|
||||
В чистом виде мета-атрибуты не имеют применения, но могут быть использованы для генерации автоматической документации и используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах.
|
||||
По умолчанию мета-атрибуты не имеют применения, но используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах. А так же могут быть использованы для генерации автоматической документации.
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
|
||||
@ -655,7 +684,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 +692,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 +706,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 +770,7 @@ var configs = new ConfigContainer()
|
||||
.Set(new SomeDataA(/* ... */))
|
||||
.Set(new SomeDataB(/* ... */)));
|
||||
EcsDefaultWorld _world = new EcsDefaultWorld(configs);
|
||||
//...
|
||||
// ...
|
||||
var _someDataA = _world.Configs.Get<SomeDataA>();
|
||||
var _someDataB = _world.Configs.Get<SomeDataB>();
|
||||
```
|
||||
@ -757,9 +779,9 @@ var _someDataB = _world.Configs.Get<SomeDataB>();
|
||||
_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>();
|
||||
```
|
||||
@ -803,15 +825,15 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
|
||||
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
|
||||
{
|
||||
private SomeClass _object; // Объект который будет утилизироваться.
|
||||
private SomeReusedClass _resusedObject; // Объект который будет переиспользоваться.
|
||||
private SomeReusedClass _reusedObject; // Объект который будет переиспользоваться.
|
||||
public SomeClass Object => _object;
|
||||
public SomeReusedClass ResusedObject => _resusedObject;
|
||||
public SomeReusedClass ReusedObject => _reusedObject;
|
||||
void IEcsWorldComponent<WorldComponent>.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<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
|
||||
{
|
||||
@ -820,19 +842,21 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
|
||||
component._object = null;
|
||||
|
||||
// Как вариант тут можно сделать сброс значений у переиспользуемого объекта.
|
||||
//component._resusedObject.Reset();
|
||||
//component._reusedObject.Reset();
|
||||
|
||||
//Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна.
|
||||
//component = default;
|
||||
// Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна.
|
||||
// component = default;
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
> Компоненты и конфиги можно применять для создания расширений в связке с методами расширений.
|
||||
|
||||
</br>
|
||||
|
||||
# Проекты на DragonECS
|
||||
* [3D Platformer (Example)](https://github.com/DCFApixels/3D-Platformer-DragonECS)
|
||||
* [3D Platformer (Example)](https://github.com/DCFApixels/3D-Platformer-DragonECS-Demo)
|
||||

|
||||
|
||||
</br>
|
||||
@ -845,7 +869,7 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
|
||||
* [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, дай знать и они будут добавлены сюда-->
|
||||
> *Твое расширение? Если разрабатываешь расширение для DragonECS, пиши [сюда](#обратная-связь).
|
||||
|
||||
</br>
|
||||
|
||||
@ -875,3 +899,4 @@ The type or namespace name 'ReadOnlySpan<>' could not be found (are you missing
|
||||
</br></br></br>
|
||||
</br></br></br>
|
||||
</br></br></br>
|
||||
<img width="0" src="https://github.com/user-attachments/assets/7bc29394-46d6-44a3-bace-0a3bae65d755"><!--Чтоб флаг подгружался в любом случае-->
|
||||
|
||||
900
README-ZH.md
Normal file
900
README-ZH.md
Normal file
@ -0,0 +1,900 @@
|
||||
<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# 实体组件系统框架
|
||||
|
||||
<table>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td colspan="3">Readme Languages:</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md">
|
||||
<img src="https://github.com/user-attachments/assets/7bc29394-46d6-44a3-bace-0a3bae65d755"></br>
|
||||
<span>Русский</span>
|
||||
</a>
|
||||
</td>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS">
|
||||
<img src="https://github.com/user-attachments/assets/30528cb5-f38e-49f0-b23e-d001844ae930"></br>
|
||||
<span>English</span>
|
||||
</a>
|
||||
</td>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS/blob/main/README-ZH.md">
|
||||
<img src="https://github.com/user-attachments/assets/3c699094-f8e6-471d-a7c1-6d2e9530e721"></br>
|
||||
<span>中文</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</br>
|
||||
|
||||
DragonECS 是一个[实体组件系统](https://www.imooc.com/article/331544)框架。专注于提升便利性、模块性、可扩展性和动态实体修改性能。 用纯C#开发的,没有依赖和代码生成。灵感来自于[LeoEcs](https://github.com/Leopotam/ecslite)。
|
||||
|
||||
> [!WARNING]
|
||||
> 该框架是预发布版本,因此 API 可能会有变化。在 `main` 分支中是当前的工作版本。</br>
|
||||
> Readme还没完成,如果有不清楚的地方,可以在这里提问 [反馈](#反馈)
|
||||
|
||||
## 目录
|
||||
- [安装](#安装)
|
||||
- [基础概念](#基础概念)
|
||||
- [实体](#实体)
|
||||
- [组件](#组件)
|
||||
- [系统](#系统)
|
||||
- [框架概念](#框架概念)
|
||||
- [系统管线](#系统管线)
|
||||
- [初始化](#初始化)
|
||||
- [依赖注入](#依赖注入)
|
||||
- [模块](#模块)
|
||||
- [层级](#层级)
|
||||
- [流程](#流程)
|
||||
- [世界](#世界)
|
||||
- [池子](#池子)
|
||||
- [方面](#方面)
|
||||
- [查询](#查询)
|
||||
- [集合](#集合)
|
||||
- [ECS入口](#ECS入口)
|
||||
- [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;
|
||||
|
||||
protected override void Init(Builder b)
|
||||
{
|
||||
// 与 SomeAspect1 进行组合。
|
||||
otherAspect1 = b.Combine<OtherAspect1>(1);
|
||||
// 即使对 OtherAspect1 调用 Combine 方法更早,Aspect 会首先与 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入口
|
||||
这是一个用户定义的类,作为 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-Demo)
|
||||

|
||||
|
||||
</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>
|
||||
<img width="0" src="https://github.com/user-attachments/assets/8e598a9a-826c-4a1f-b842-0c56301d2927"><!--Чтоб флаг подгружался в любом случае-->
|
||||
7
README-ZH.md.meta
Normal file
7
README-ZH.md.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 877c188fb31b69045adeec8ca9a19b33
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
841
README.md
841
README.md
@ -12,8 +12,36 @@
|
||||
</p>
|
||||
|
||||
# DragonECS - C# Entity Component System Framework
|
||||
| Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) |
|
||||
| :--- | :--- | :--- |
|
||||
|
||||
<table>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td colspan="3">Readme Languages:</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md">
|
||||
<img src="https://github.com/user-attachments/assets/7bc29394-46d6-44a3-bace-0a3bae65d755"></br>
|
||||
<span>Русский</span>
|
||||
</a>
|
||||
</td>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS">
|
||||
<img src="https://github.com/user-attachments/assets/3c699094-f8e6-471d-a7c1-6d2e9530e721"></br>
|
||||
<span>English</span>
|
||||
</a>
|
||||
</td>
|
||||
<td nowrap width="100">
|
||||
<a href="https://github.com/DCFApixels/DragonECS/blob/main/README-ZH.md">
|
||||
<img src="https://github.com/user-attachments/assets/8e598a9a-826c-4a1f-b842-0c56301d2927"></br>
|
||||
<span>中文</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</br>
|
||||
|
||||
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).
|
||||
|
||||
@ -22,6 +50,38 @@ The [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Framework aims
|
||||
>
|
||||
> While the English version of the README is incomplete, you can view the [Russian version](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md).
|
||||
|
||||
## Оглавление
|
||||
- [Installation](#Installation)
|
||||
- [Basic Concepts](#Basic-Concepts)
|
||||
- [Entity](#entity)
|
||||
- [Component](#component)
|
||||
- [System](#system)
|
||||
- [Framework Concepts](#Framework-Concepts)
|
||||
- [Pipeline](#Pipeline)
|
||||
- [Building](#Building)
|
||||
- [Dependency Injection](#Dependency-Injection)
|
||||
- [Modules](#Modules)
|
||||
- [Layers](#Layers)
|
||||
- [Processes](#Processes)
|
||||
- [World](#World)
|
||||
- [Pool](#Pool)
|
||||
- [Aspect](#Aspect)
|
||||
- [Queries](#Queries)
|
||||
- [Collections](#Collections)
|
||||
- [ECS Root](#ecs-root)
|
||||
- [Debug](#debug)
|
||||
- [Meta Attributes](#Meta-Attributes)
|
||||
- [EcsDebug](#ecsdebug)
|
||||
- [Profiling](#Profiling)
|
||||
- [Define Symbols](#define-symbols)
|
||||
- [Framework Extension Tools](#Framework-Extension-tools)
|
||||
- [World Components](#World-Components)
|
||||
- [Configs](#Configs)
|
||||
- [Projects powered by DragonECS](#Projects-powered-by-DragonECS)
|
||||
- [Extensions](#Extensions)
|
||||
- [FAQ](#faq)
|
||||
- [Feedback](#Feedback)
|
||||
|
||||
</br>
|
||||
|
||||
# Installation
|
||||
@ -48,8 +108,762 @@ The framework can also be added to the project as source code.
|
||||
|
||||
</br>
|
||||
|
||||
# Basic Concepts
|
||||
## Entity
|
||||
Сontainer for components. They are implemented as identifiers, of which there are two types:
|
||||
* `int` - a short-term identifier used within a single tick. Storing `int` identifiers is not recommended, use `entlong` instead;
|
||||
* `entlong` - long-term identifier, contains a full set of information for unique identification;
|
||||
``` c#
|
||||
// Creating a new entity in the world.
|
||||
int entityID = _world.NewEntity();
|
||||
|
||||
// Deleting an entity.
|
||||
_world.DelEntity(entityID);
|
||||
|
||||
// Copying components from one entity to another.
|
||||
_world.CopyEntity(entityID, otherEntityID);
|
||||
|
||||
// Cloning an entity.
|
||||
int newEntityID = _world.CloneEntity(entityID);
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Working with entlong</summary>
|
||||
|
||||
``` c#
|
||||
// Convert int to entlong.
|
||||
entlong entity = _world.GetEntityLong(entityID);
|
||||
// or
|
||||
entlong entity = (_world, entityID);
|
||||
|
||||
// Check that the entity is still alive.
|
||||
if (entity.IsAlive) { }
|
||||
|
||||
// Converting entlong to int. Throws an exception if the entity no longer exists.
|
||||
int entityID = entity.ID;
|
||||
// or
|
||||
var (entityID, world) = entity;
|
||||
|
||||
// Converting entlong to int. Returns true and the int identifier if the entity is still alive.
|
||||
if (entity.TryGetID(out int entityID)) { }
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
> **NOTICE:** Entities cannot exist without components, empty entities will be automatically deleted immediately after the last component is deleted.
|
||||
|
||||
## Component
|
||||
Data for entities. Must implement the ``IEcsComponent`` interface or other specifying type of component.
|
||||
```c#
|
||||
struct Health : IEcsComponent
|
||||
{
|
||||
public float health;
|
||||
public int armor;
|
||||
}
|
||||
struct PlayerTag : IEcsTagComponent {}
|
||||
```
|
||||
Built-in component types:
|
||||
* `IEcsComponent` - Components with data. Universal component type.
|
||||
* `IEcsTagComponent` - Tag components. Components without data.
|
||||
|
||||
## System
|
||||
Represent the core logic defining entity behaviors. They are implemented as user-defined classes that implement at least one of the process interfaces. Key processes include:
|
||||
```c#
|
||||
class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy
|
||||
{
|
||||
// Called once during EcsPipeline.Init() and before IEcsInit.Init().
|
||||
public void PreInit () { }
|
||||
|
||||
// Called once during EcsPipeline.Init() and after IEcsPreInit.PreInit().
|
||||
public void Init () { }
|
||||
|
||||
// Called each time during EcsPipeline.Run().
|
||||
public void Run () { }
|
||||
|
||||
// Called once during EcsPipeline.Destroy().
|
||||
public void Destroy () { }
|
||||
}
|
||||
```
|
||||
> For implementing additional processes, refer to the [Processes](#Processes) section.
|
||||
|
||||
</br>
|
||||
|
||||
# Framework Concepts
|
||||
## Pipeline
|
||||
Container and engine of systems. Responsible for setting up the system call queue, provides mechanisms for communication between systems, and dependency injection. Implemented as the `EcsPipeline` class.
|
||||
### Building
|
||||
Builder is responsible for building the pipeline. Systems are added to the Builder and at the end, the pipeline is built. Example:
|
||||
```c#
|
||||
EcsPipeline pipeline = EcsPipeline.New() // Создает Builder пайплайна.
|
||||
// Adds System1 to the systems queue.
|
||||
.Add(new System1())
|
||||
// Adds System2 to the queue after System1.
|
||||
.Add(new System2())
|
||||
// Adds System3 to the queue after System2, as a unique instance.
|
||||
.AddUnique(new System3())
|
||||
// Completes the pipeline building and returns its instance.
|
||||
.Build();
|
||||
pipeline.Init(); // Initializes the pipeline.
|
||||
```
|
||||
|
||||
```c#
|
||||
class SomeSystem : IEcsRun, IEcsPipelineMember
|
||||
{
|
||||
// Gets the pipeline instance to which the system belongs.
|
||||
public EcsPipeline Pipeline { get ; set; }
|
||||
|
||||
public void Run () { }
|
||||
}
|
||||
```
|
||||
> For simultaneous building and initialization, there is the method `Builder.BuildAndInit();`
|
||||
|
||||
### Dependency Injection
|
||||
The framework implements dependency injection for systems. This process begins during pipeline initialization and injects data passed to the Builder.
|
||||
> Using built-in dependency injection is optional.
|
||||
``` c#
|
||||
class SomeDataA { /* ... */ }
|
||||
class SomeDataB : SomeDataA { /* ... */ }
|
||||
|
||||
// ...
|
||||
SomeDataB _someDataB = new SomeDataB();
|
||||
EcsPipeline pipeline = EcsPipeline.New()
|
||||
// ...
|
||||
// Injects _someDataB into systems implementing IEcsInject<SomeDataB>.
|
||||
.Inject(_someDataB)
|
||||
// Adds systems implementing IEcsInject<SomeDataA> to the injection tree,
|
||||
// now these systems will also receive _someDataB.
|
||||
.Injector.AddNode<SomeDataA>()
|
||||
// ...
|
||||
.Add(new SomeSystem())
|
||||
// ...
|
||||
.BuildAndInit();
|
||||
|
||||
// ...
|
||||
// Injection uses the interface IEcsInject<T> and its method Inject(T obj).
|
||||
class SomeSystem : IEcsInject<SomeDataA>, IEcsRun
|
||||
{
|
||||
SomeDataA _someDataA
|
||||
// obj will be an instance of type SomeDataB.
|
||||
public void Inject(SomeDataA obj) => _someDataA = obj;
|
||||
|
||||
public void Run ()
|
||||
{
|
||||
_someDataA.DoSomething();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Modules
|
||||
Groups of systems that implement a common feature can be grouped into modules and easily added to the Pipeline.
|
||||
``` 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();
|
||||
```
|
||||
|
||||
### Layers
|
||||
Queues in the system can be segmented into layers. A layer defines a position in the queue for inserting systems. For example, if a system needs to be inserted at the end of the queue regardless of where it is added, you can add this system to the `EcsConsts.END_LAYER` layer.
|
||||
``` c#
|
||||
const string SOME_LAYER = nameof(SOME_LAYER);
|
||||
EcsPipeline pipeline = EcsPipeline.New()
|
||||
// ...
|
||||
// Inserts a new layer before the end layer EcsConsts.END_LAYER
|
||||
.Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER)
|
||||
// System SomeSystem will be added to the SOME_LAYER layer
|
||||
.Add(New SomeSystem(), SOME_LAYER)
|
||||
// ...
|
||||
.BuildAndInit();
|
||||
```
|
||||
The built-in layers are arranged in the following order:
|
||||
* `EcsConst.PRE_BEGIN_LAYER`
|
||||
* `EcsConst.BEGIN_LAYER`
|
||||
* `EcsConst.BASIC_LAYER` (Systems are added here if no layer is specified during addition)
|
||||
* `EcsConst.END_LAYER`
|
||||
* `EcsConst.POST_END_LAYER`
|
||||
|
||||
## Processes
|
||||
Processes are queues of systems that implement a common interface, such as `IEcsRun`. Runners are used to start processes. Built-in processes are started automatically. It is possible to implement custom processes.
|
||||
|
||||
<details>
|
||||
<summary>Built-in processes</summary>
|
||||
|
||||
* `IEcsPreInit`, `IEcsInit`, `IEcsRun`, `IEcsDestroy` - lifecycle processes of `EcsPipeline`.
|
||||
* `IEcsInject<T>` - [Dependency Injection](#Dependency-Injection) processes.
|
||||
* `IOnInitInjectionComplete` - Similar to the [Dependency Injection](#Dependency-Injection) process, but signals the completion of initialization injection.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Custom Processes</summary>
|
||||
|
||||
To add a new process, create an interface inherited from `IEcsProcess` and create a runner for it. A runner is a class that implements the interface of the process to be run and inherits from `EcsRunner<TInterface>`. Example:
|
||||
``` c#
|
||||
// Process interface.
|
||||
interface IDoSomethingProcess : IEcsProcess
|
||||
{
|
||||
void Do();
|
||||
}
|
||||
// Implementation of a runner. An example of implementation can also be seen in built-in processes.
|
||||
sealed class DoSomethingProcessRunner : EcsRunner<IDoSomethingProcess>, IDoSomethingProcess
|
||||
{
|
||||
public void Do()
|
||||
{
|
||||
foreach (var item in Process) item.Do();
|
||||
}
|
||||
}
|
||||
// ...
|
||||
|
||||
// Adding the runner when creating the pipeline
|
||||
_pipeline = EcsPipeline.New()
|
||||
//...
|
||||
.AddRunner<DoSomethingProcessRunner>()
|
||||
//...
|
||||
.BuildAndInit();
|
||||
|
||||
// Running the runner if it was added
|
||||
_pipeline.GetRunner<IDoSomethingProcess>.Do()
|
||||
|
||||
// or if the runner was not added (calling GetRunnerInstance will also add the runner to the pipeline).
|
||||
_pipeline.GetRunnerInstance<DoSomethingProcessRunner>.Do()
|
||||
```
|
||||
> Runners have several implementation requirements:
|
||||
> * Inheritance from `EcsRunner<T>` must be direct.
|
||||
> * Runner can only contain one interface (except `IEcsProcess`);
|
||||
> * The inheriting class of `EcsRunner<T>,` must also implement the `T` interface;
|
||||
|
||||
> It's not recommended to call `GetRunner` in a loop; instead, cache the retrieved runner instance.
|
||||
</details>
|
||||
|
||||
## World
|
||||
Is a container for entities and components.
|
||||
``` c#
|
||||
// Creating an instance of the world.
|
||||
_world = new EcsDefaultWorld();
|
||||
// Creating and deleting an entity as shown in the Entities section.
|
||||
var e = _world.NewEntity();
|
||||
_world.DelEntity(e);
|
||||
```
|
||||
> **NOTICE:** It's necessary to call EcsWorld.Destroy() on the world instance when it's no longer needed, otherwise it will remain in memory.
|
||||
|
||||
### World Configuration
|
||||
To initialize the world with a required size upfront and reduce warm-up time, you can pass an `EcsWorldConfig` instance to the constructor.
|
||||
|
||||
``` c#
|
||||
EcsWorldConfig config = new EcsWorldConfig(
|
||||
// Pre-initializes the world capacity for 2000 entities.
|
||||
entitiesCapacity: 2000,
|
||||
// Pre-initializes the pools capacity for 2000 components.
|
||||
poolComponentsCapacity: 2000);
|
||||
_world = new EcsDefaultWorld(config);
|
||||
```
|
||||
|
||||
## Pool
|
||||
Stash of components, providing methods for adding, reading, editing, and removing components on entities. There are several types of pools designed for different purposes:
|
||||
* `EcsPool` - universal pool, stores struct components implementing the `IEcsComponent` interface;
|
||||
* `EcsTagPool` - special pool for empty tag components, stores struct-components with `IEcsTagComponent` as bool values, which in comparison with `EcsPool` implementation has better memory and speed optimization;
|
||||
|
||||
Pools have 5 main methods and their variations:
|
||||
``` c#
|
||||
// One way to get a pool from the world.
|
||||
EcsPool<Pose> poses = _world.GetPool<Pose>();
|
||||
|
||||
// Adds component to entity, throws an exception if the entity already has the component.
|
||||
ref var addedPose = ref poses.Add(entityID);
|
||||
|
||||
// Returns exist component, throws an exception if the entity does not have this component.
|
||||
ref var gettedPose = ref poses.Get(entityID);
|
||||
|
||||
// Returns a read-only component, throwing an exception if the entity does not have this component.
|
||||
ref readonly var readonlyPose = ref poses.Read(entityID);
|
||||
|
||||
// Returns true if the entity has the component, otherwise false.
|
||||
if (poses.Has(entityID)) { /* ... */ }
|
||||
|
||||
// Removes component from entity, throws an exception if the entity does not have this component.
|
||||
poses.Del(entityID);
|
||||
```
|
||||
> There are "safe" methods that first perform a check for the presence or absence of a component. Such methods are prefixed with `Try`.
|
||||
|
||||
> It is possible to implement a user pool. This feature will be described shortly.
|
||||
|
||||
## Aspect
|
||||
These are custom classes inherited from `EcsAspect` and used to interact with entities. Aspects are both a pool cache and a component mask for filtering entities. You can think of aspects as a description of what entities the system is working with.
|
||||
|
||||
Simplified syntax:
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
// ...
|
||||
class Aspect : EcsAspect
|
||||
{
|
||||
// Caches the Pose pool and adds it to the inclusive constraint.
|
||||
public EcsPool<Pose> poses = Inc;
|
||||
// Caches the Velocity pool and adds it to the inclusive constraint.
|
||||
public EcsPool<Velocity> velocities = Inc;
|
||||
// Caches the FreezedTag pool and adds it to the exclusive constraint.
|
||||
public EcsTagPool<FreezedTag> freezedTags = Exc;
|
||||
|
||||
// During queries, it checks for the presence of components
|
||||
// in the inclusive constraint and absence in the exclusive constraint.
|
||||
// There is also Opt - it only caches the pool without affecting the mask.
|
||||
}
|
||||
```
|
||||
|
||||
Explicit syntax (the result is identical to the example above):
|
||||
``` 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>Combining aspects</summary>
|
||||
|
||||
Aspects can have additional aspects added to them, thus combining them. The constraints will also be combined accordingly.
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
// ...
|
||||
class Aspect : EcsAspect
|
||||
{
|
||||
public OtherAspect1 otherAspect1;
|
||||
public OtherAspect2 otherAspect2;
|
||||
public EcsPool<Pose> poses;
|
||||
|
||||
protected override void Init(Builder b)
|
||||
{
|
||||
// Combines with SomeAspect1.
|
||||
otherAspect1 = b.Combine<OtherAspect1>(1);
|
||||
// Although Combine was called earlier for OtherAspect1, it will first combine with OtherAspect2 because the default order is 0.
|
||||
otherAspect2 = b.Combine<OtherAspect2>();
|
||||
// If b.Exclude<Pose>() was specified in OtherAspect1 or OtherAspect2, it will be replaced with b.Include<Pose>() here.
|
||||
poses = b.Include<Pose>();
|
||||
}
|
||||
}
|
||||
```
|
||||
If there are conflicting constraints between the combined aspects, the new constraints will replace those added earlier. Constraints from the root aspect always replace constraints from added aspects. Here's a visual example of constraint combination:
|
||||
| | 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: | For `cmp2` will be chosen. :heavy_check_mark: |
|
||||
| Aspect | :x: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | For `cmp1` will be chosen. :x: |
|
||||
| Final Constraints | :x: | :heavy_check_mark: | :heavy_minus_sign: | :x: | :heavy_check_mark: | |
|
||||
|
||||
</details>
|
||||
|
||||
## Queries
|
||||
To get the set of required entities, there is a query method `EcsWorld.Where<TAspect>(out TAspect aspect)`. Aspect is specified as `TAspect`, the entities will be filtered by the mask of the specified aspect. The `Where` query is applicable to both `EcsWorld` and framework collections (in this respect, Where is somewhat similar to a similar one from Linq).
|
||||
Example:
|
||||
``` 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Collections
|
||||
|
||||
### EcsSpan
|
||||
Collection of entities that is read-only and stack-allocated. It consists of a reference to an array, its length, and the world identifier. Similar to `ReadOnlySpan<int>`.
|
||||
``` c#
|
||||
// Where query returns entities as EcsSpan.
|
||||
EcsSpan es = _world.Where(out Aspect a);
|
||||
// Iteration is possible using foreach and for loops.
|
||||
foreach (var e in es)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
for (int i = 0; i < es.Count; i++)
|
||||
{
|
||||
int e = es[i];
|
||||
// ...
|
||||
}
|
||||
```
|
||||
> Although `EcsSpan` is just an array, it does not allow duplicate entities.
|
||||
|
||||
### EcsGroup
|
||||
Sparse Set based auxiliary collection for storing a set of entities with O(1) add/delete/check operations, etc.
|
||||
``` c#
|
||||
// Getting a new group. EcsWorld contains pool of groups,
|
||||
// so a new one will be created or a free one will be reused.
|
||||
EcsGroup group = EcsGroup.New(_world);
|
||||
// Release the group.
|
||||
group.Dispose();
|
||||
```
|
||||
``` c#
|
||||
// Add entityID to the group.
|
||||
group.Add(entityID);
|
||||
// Check if entityID exists in the group.
|
||||
group.Has(entityID);
|
||||
// Remove entityID from the group.
|
||||
group.Remove(entityID);
|
||||
```
|
||||
``` c#
|
||||
// WhereToGroup query returns entities as a read-only group EcsReadonlyGroup.
|
||||
EcsReadonlyGroup group = _world.WhereToGroup(out Aspect a);
|
||||
// Iteration is possible using foreach and for loops.
|
||||
foreach (var e in group)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
for (int i = 0; i < group.Count; i++)
|
||||
{
|
||||
int e = group[i];
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Since groups are sets, they have methods similar to `ISet<T>`. Editing methods have 2 variants: either they modify `groupA` directly or return a new group:
|
||||
|
||||
``` c#
|
||||
// Union of groupA and groupB.
|
||||
groupA.UnionWith(groupB);
|
||||
EcsGroup newGroup = EcsGroup.Union(groupA, groupB);
|
||||
|
||||
// Intersection of groupA and groupB.
|
||||
groupA.IntersectWith(groupB);
|
||||
EcsGroup newGroup = EcsGroup.Intersect(groupA, groupB);
|
||||
|
||||
// Difference of groupA and groupB.
|
||||
groupA.ExceptWith(groupB);
|
||||
EcsGroup newGroup = EcsGroup.Except(groupA, groupB);
|
||||
|
||||
// Symmetric difference of groupA and groupB.
|
||||
groupA.SymmetricExceptWith(groupB);
|
||||
EcsGroup newGroup = EcsGroup.SymmetricExcept(groupA, groupB);
|
||||
|
||||
// Difference of all entities in world and groupA.
|
||||
groupA.Inverse();
|
||||
EcsGroup newGroup = EcsGroup.Inverse(groupA);
|
||||
```
|
||||
|
||||
## ECS Root
|
||||
This is a custom class that is the entry point for ECS. Its main purpose is to initialize, start systems on each engine Update and release resources when no longer needed.
|
||||
### Example for Unity
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
using UnityEngine;
|
||||
public class EcsRoot : MonoBehaviour
|
||||
{
|
||||
private EcsPipeline _pipeline;
|
||||
private EcsDefaultWorld _world;
|
||||
private void Start()
|
||||
{
|
||||
// Creating world for entities and components.
|
||||
_world = new EcsDefaultWorld();
|
||||
// Creating pipeline for systems.
|
||||
_pipeline = EcsPipeline.New()
|
||||
// Adding systems.
|
||||
// .Add(new SomeSystem1())
|
||||
// .Add(new SomeSystem2())
|
||||
// .Add(new SomeSystem3())
|
||||
|
||||
// Injecting world into systems.
|
||||
.Inject(_world)
|
||||
// Other injections.
|
||||
// .Inject(SomeData)
|
||||
|
||||
// Finalizing the pipeline construction.
|
||||
.Build();
|
||||
// Initialize the Pipeline and run IEcsPreInit.PreInit()
|
||||
// and IEcsInit.Init() on all added systems.
|
||||
_pipeline.Init();
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
// Invoking IEcsRun.Run() on all added systems.
|
||||
_pipeline.Run();
|
||||
}
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Invoking IEcsDestroy.Destroy() on all added systems.
|
||||
_pipeline.Destroy();
|
||||
_pipeline = null;
|
||||
// Requires deleting worlds that will no longer be used.
|
||||
_world.Destroy();
|
||||
_world = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
### Generic example
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
public class EcsRoot
|
||||
{
|
||||
private EcsPipeline _pipeline;
|
||||
private EcsDefaultWorld _world;
|
||||
// Engine initialization .
|
||||
public void Init()
|
||||
{
|
||||
// Creating world for entities and components.
|
||||
_world = new EcsDefaultWorld();
|
||||
// Creating pipeline for systems.
|
||||
_pipeline = EcsPipeline.New()
|
||||
// Adding systems.
|
||||
// .Add(new SomeSystem1())
|
||||
// .Add(new SomeSystem2())
|
||||
// .Add(new SomeSystem3())
|
||||
|
||||
// Внедрение мира в системы.
|
||||
.Inject(_world)
|
||||
// Other injections.
|
||||
// .Inject(SomeData)
|
||||
|
||||
// Finalizing the pipeline construction.
|
||||
.Build();
|
||||
// Initialize the Pipeline and run IEcsPreInit.PreInit()
|
||||
// and IEcsInit.Init() on all added systems.
|
||||
_pipeline.Init();
|
||||
}
|
||||
|
||||
// Engine update loop.
|
||||
public void Update()
|
||||
{
|
||||
// Invoking IEcsRun.Run() on all added systems.
|
||||
_pipeline.Run();
|
||||
}
|
||||
|
||||
// Engine cleanup.
|
||||
public void Destroy()
|
||||
{
|
||||
// Invoking IEcsDestroy.Destroy() on all added systems.
|
||||
_pipeline.Destroy();
|
||||
_pipeline = null;
|
||||
// Requires deleting worlds that will no longer be used.
|
||||
_world.Destroy();
|
||||
_world = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</br>
|
||||
|
||||
# Debug
|
||||
The framework provides additional tools for debugging and logging, independent of the environment. Also many types have their own DebuggerProxy for more informative display in IDE.
|
||||
## Meta Attributes
|
||||
By default, meta-attributes have no use, but are used in integrations with engines to specify display in debugging tools and editors. And can also be used to generate automatic documentation.
|
||||
``` c#
|
||||
using DCFApixels.DragonECS;
|
||||
|
||||
// Specifies custom name for the type, defaults to the type name.
|
||||
[MetaName("SomeComponent")]
|
||||
|
||||
// Used for grouping types.
|
||||
[MetaGroup("Abilities/Passive/")] // or [MetaGroup("Abilities", "Passive")]
|
||||
|
||||
// Sets the type color in RGB format, where each channel ranges from 0 to 255; defaults to white.
|
||||
[MetaColor(MetaColor.Red)] // or [MetaColor(255, 0, 0)]
|
||||
|
||||
// Adds description to the type.
|
||||
[MetaDescription("The quick brown fox jumps over the lazy dog")]
|
||||
|
||||
// Adds string tags to the type.
|
||||
[MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] to hide in the editor
|
||||
public struct Component : IEcsComponent { /* ... */ }
|
||||
```
|
||||
Getting meta-information:
|
||||
``` c#
|
||||
TypeMeta typeMeta = someComponent.GetMeta();
|
||||
// or
|
||||
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
|
||||
Has a set of methods for debugging and logging. It is implemented as a static class calling methods of Debug services. Debug services are intermediaries between the debugging systems of the environment and EcsDebug. This allows projects to be ported to other engines without modifying the debug code, by implementing the corresponding Debug service.
|
||||
|
||||
By default, `DefaultDebugService` is used, which outputs logs to the console. To implement a custom one, create a class inherited from `DebugService` and implement abstract class members.
|
||||
|
||||
``` c#
|
||||
// Output log.
|
||||
EcsDebug.Print("Message");
|
||||
|
||||
// Output log with tag.
|
||||
EcsDebug.Print("Tag", "Message");
|
||||
|
||||
// Break execution.
|
||||
EcsDebug.Break();
|
||||
|
||||
// Set another Debug Service.
|
||||
EcsDebug.Set<OtherDebugService>();
|
||||
```
|
||||
|
||||
## Profiling
|
||||
``` c#
|
||||
// Creating a marker named SomeMarker.
|
||||
private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker");
|
||||
|
||||
// ...
|
||||
|
||||
marker.Begin();
|
||||
// Code whose execution time is being measured.
|
||||
marker.End();
|
||||
|
||||
// or
|
||||
|
||||
using (marker.Auto())
|
||||
{
|
||||
// Code whose execution time is being measured.
|
||||
}
|
||||
```
|
||||
|
||||
</br>
|
||||
|
||||
# Define Symbols
|
||||
+ `DISABLE_POOLS_EVENTS` - disables reactive behavior in pools.
|
||||
+ `ENABLE_DRAGONECS_DEBUGGER` - enables EcsDebug functionality in release builds.
|
||||
+ `ENABLE_DRAGONECS_ASSERT_CHECKS` - enables omitted checks in the release build.
|
||||
+ `REFLECTION_DISABLED` - completely restricts the framework's use of Reflection.
|
||||
+ `DISABLE_DEBUG` - for environments where manual DEBUG disabling is not supported, e.g., Unity.
|
||||
+ `ENABLE_DUMMY_SPAN` - For environments where Span types are not supported, enables its replacement.
|
||||
+ `DISABLE_CATH_EXCEPTIONS` - Turns off the default exception handling behavior. By default, the framework will catch exceptions with the exception information output via EcsDebug and continue working.
|
||||
|
||||
</br>
|
||||
|
||||
# Framework Extension Tools
|
||||
There are additional tools for greater extensibility of the framework.
|
||||
|
||||
## Configs
|
||||
Constructors of `EcsWorld` and `EcsPipeline` classes can accept config containers implementing `IConfigContainer` or `IConfigContainerWriter` interface. These containers can be used to pass data and dependencies. The built-in container implementation is `ConfigContainer`, but you can also use your own implementation.</br>
|
||||
Example of using configs for EcsWorld:
|
||||
``` 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>();
|
||||
```
|
||||
Example of using configs for EcsPipeline:
|
||||
``` c#
|
||||
_pipeline = EcsPipeline.New()// similarly _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>();
|
||||
```
|
||||
|
||||
## World Components
|
||||
Components can be used to attach additional data to worlds. World components are `struct` types. Access to components via `Get` is optimized, the speed is almost the same as access to class fields.
|
||||
|
||||
Get component:
|
||||
``` c#
|
||||
ref WorldComponent component = ref _world.Get<WorldComponent>();
|
||||
```
|
||||
Component Implementation:
|
||||
``` c#
|
||||
public struct WorldComponent
|
||||
{
|
||||
// Data.
|
||||
}
|
||||
```
|
||||
Or:
|
||||
``` c#
|
||||
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
|
||||
{
|
||||
// Data.
|
||||
|
||||
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
|
||||
{
|
||||
// Actions during component initialization. Called before the first return from EcsWorld.Get().
|
||||
}
|
||||
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
|
||||
{
|
||||
// Actions when EcsWorld.Destroy is called.
|
||||
// Calling OnDestroy, obliges the user to manually reset the component if necessary.
|
||||
component = default;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Example of use</summary>
|
||||
|
||||
IEcsWorldComponent<T> interface events, can be used to automatically initialize component fields, and release resources.
|
||||
``` 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>
|
||||
|
||||
> Components and configs can be used to create extensions in conjunction with extension methods.
|
||||
|
||||
</br>
|
||||
|
||||
# Projects powered by DragonECS
|
||||
* [3D Platformer (Example)](https://github.com/DCFApixels/3D-Platformer-DragonECS)
|
||||
* [3D Platformer (Example)](https://github.com/DCFApixels/3D-Platformer-DragonECS-Demo)
|
||||
|
||||
</br>
|
||||
|
||||
@ -61,7 +875,28 @@ The framework can also be added to the project as source code.
|
||||
* [One-Frame Components](https://gist.github.com/DCFApixels/46d512dbcf96c115b94c3af502461f60)
|
||||
* [Code Templates for IDE](https://gist.github.com/ctzcs/0ba948b0e53aa41fe1c87796a401660b) and [for Unity](https://gist.github.com/ctzcs/d4c7730cf6cd984fe6f9e0e3f108a0f1)
|
||||
* Graphs (Work in progress)
|
||||
> > *Your extension? If you are developing an extension for Dragoness, you can share it [here](#feedback).
|
||||
|
||||
</br>
|
||||
|
||||
# FAQ
|
||||
## 'ReadOnlySpan<>' could not be found
|
||||
In Unity 2020.1.x, you may encounter this error in the console:
|
||||
```
|
||||
The type or namespace name 'ReadOnlySpan<>' could not be found (are you missing a using directive or an assembly reference?)
|
||||
```
|
||||
To fix this, add the define symbol `ENABLE_DUMMY_SPAN` to `Project Settings/Player/Other Settings/Scripting Define Symbols`.
|
||||
|
||||
</br>
|
||||
|
||||
# Feedback
|
||||
+ 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>
|
||||
<img width="0" src="https://github.com/user-attachments/assets/30528cb5-f38e-49f0-b23e-d001844ae930"><!--Чтоб флаг подгружался в любом случае-->
|
||||
|
||||
Loading…
Reference in New Issue
Block a user