19 KiB
DragonECS - C# Entity Component System Framework
Languages: | Русский | English(WIP) |
---|
Данный ECS Фреймворк нацелен на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей.
NOTICE: Проект в стадии разработки. API может меняться.
Оглавление
Установка
-
Unity-модуль
Поддерживается установка в виде Unity-модуля в при помощи добавления git-URL в PackageManager или ручного добавления в Packages/manifest.json
:
https://github.com/DCFApixels/DragonECS.git
-
В виде иходников
Фреймворк так же может быть добавлен в проект в виде исходников.
Версионирование
В DragonECS применяется следующая семантика версионирования: Открыть
Основные концепции
Entity
Сущности - это то к чему крепятся данные. Реализованны в виде идентификаторов, которых есть 2 вида:
int
- однократный идентификатор, применяется в пределах одного тика. Не рекомендуется хранитьint
идентификаторы, в место этого используйтеentlong
;entlong
- долговременный идентификатор, содержит в себе полный набор информации для однозначной идентификации;
// Создаие новой сущности в мире.
int entityID = _world.NewEmptyEntity();
// Удаление сушности.
_world.DelEntity(entityID);
// Копирование компонентов одной сущности в другую.
_world.CopyEntity(entityID, otherEntityID);
// Клонирование сущности.
int newEntityID = _world.CloneEntity(entityID);
Работа с entlong
// конвертация int в entlong.
entlong entity = _world.GetEntityLong(entityID);
// или
entlong entity = entityID.ToEntityLong(_world);
// Проверка что сущность еще жива
if (entity.IsAlive) { }
// Конвертация entlong в int. Если сущность уже не существует, будет брошено исключение.
int entityID = entity.ID;
// Конвертация entlong в int. Вернет true и ее int идентификатор, если сущность еще жива.
if (entity.TryGetID(out int entityID)) { }
ВАЖНО! Сущности не могут существовать без компонентов, пустые сущности будут автоматически удаляться сразу после удаления последнего компонента либо в конце тика.
Component
Компоненты - это данные для сущностей. Обязаны реализовывать интерфейс IEcsComponent
или другой указываюший вид компонента.
struct Health : IEcsComponent
{
public float health;
public int armor;
}
struct PlayerTag : IEcsTagComponent {}
Встроенные виды компонентов:
IEcsComponent
- Компоненты с данными.IEcsTagComponent
- Компоненты-теги. Без данных.
Компоненты-теги хоть и не имеют данных, само наличие или отсутсвие такого компонента у сущности уже несет ифномрацию и может применяться для определения типа сущности.
System
Системы - это основная логика, тут задается поведение сущностей. Существуют в виде пользовательских классов, реализующих как минимум один из интерфейсов процессов. Основные процессы:
class SomeSystem : IEcsPreInitProcess, IEcsInitProcess, IEcsRunProcess, IEcsDestroyProcess
{
// Будет вызван один раз в момент работы EcsPipeline.Init() и до срабатывания IEcsInitProcess.Init()
public void PreInit (EcsPipeline pipeline) { }
// Будет вызван один раз в момент работы EcsPipeline.Init() и после срабатывания IEcsPreInitProcess.PreInit()
public void Init (EcsPipeline pipeline) { }
// Будет вызван один раз в момент работы EcsPipeline.Run().
public void Run (EcsPipeline pipeline) { }
// Будет вызван один раз в момент работы EcsPipeline.Destroy()
public void Destroy (EcsPipeline pipeline) { }
}
Для реализации дополнительных процессов перейдите к разделу Процессы.
Концепции фреймворка
Пайплайн
Является контейнером и двжиком систем, определяя поочередность их вызова, предоставляющий механизм для сообщений между системами и механизм внедрения зависимостей. Реализован в виде класса EcsPipeline
.
Построение
За построение пайплайна отвечает Builder. В Builder добавляются системы, а в конце строится пайплайн. Пример:
EcsPipelone pipeline = EcsPipeline.New() //Создает Builder пайплайна
// Добавляет систему System1 в очередь систем
.Add(new System1())
// Добавляет System2 в очередь после System1
.Add(new System2())
// Добавляет System3 в очередь после System2, но в единичном экземпляре
.AddUnique(new System3())
// Завершает построение пайплайна и возвращает его экземпляр
.Build();
pipeline.Init(); // Инициализация пайплайна
Есть упрощенная инициализация пайплайна сразу в момент построение, для используйте Builder.BuildAndInit();
Внедрение зависимостей
Внедрение зависимостей - это процесс который запускается вместе с инициализацией пайплайна и внедряет данные переданные в Builder.
EcsPipelone pipeline = EcsPipeline.New()
//...
.Inject(_someData) // Внедрит в системы экземлпяр _someData
//...
.BuildAndInit();
Модули
Группы систем реализующие общую фичу можно объединять в модули, и просто добавлять модули в Pipeline.
using DCFApixels.DragonECS;
class Module : 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());
}
}
EcsPipelone pipeline = EcsPipeline.New()
//...
.AddModule(new Module())
//...
.BuildAndInit();
Слои
Очередь систем можно разбить на слои. Слой определяет место в очереди для вставки систем. Если необходимо чтобы какая-то система была вставлена в конце очереди, внезависимости от места добавления, эту систему можно добавить в слой EcsConsts.END_LAYER.
const string SOME_LAYER = nameof(SOME_LAYER);
EcsPipelone pipeline = EcsPipeline.New()
//...
.Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER) // Вставляет новый слой перед конечным слоем EcsConsts.END_LAYER
//...
.BuildAndInit();
Встроенные слои расположены в следующем порядке:
EcsConst.PRE_BEGIN_LAYER
EcsConst.BEGIN_LAYER
EcsConst.BASIC_LAYER
(Если при добавблении системы не казать слой, то она будет доавблена сюда)EcsConst.END_LAYER
EcsConst.POST_END_LAYER
Процессы
Процессы - это очереди систем реализующие общий интерфейс, например IEcsRunProcess. Для запуска процессов используются Runner-ы. Система ранеров и процессов может использоваться для создания реактивного поведения или для управления очередью вызова систем. Встроенные процессы вызываются автоматически, для ручного запуска испольщуйте раннеры получаемые из EcsPipeline.GetRunner().
Метод GetRunner относительно медленный, поэтому рекомендуется кешировать полученные раннеры.
Встроенные процессы:
IEcsPreInitProcess
,IEcsInitProcess
,IEcsRunProcess
,IEcsDestroyProcess
- процессы жизненого цикла PipelineIEcsPreInject
,IEcsInject<T>
- процессы системы внедрения зависимостей для Pipeline.IEcsPreInitInjectProcess
- Так же процесс системы внедрения зависимостей, но работает в пределах до выполнения IEcsInitProcess, сигнализирует о инициализации предварительных внедрений и окончании.
Пользовательские Процессы
Для добавления нового процесса создайте интерфейс наследованный от IEcsSystem и создайте раннер для него. Раннеры это классы реализующие интерфейс запускаемого процесса и наследуемые от EcsRunner. Пример реализации раннера для IEcsRunProcess:
public sealed class EcsRunRunner : EcsRunner<IEcsRunProcess>, IEcsRunProcess
{
public void Run(EcsSession session)
{
foreach (var item in targets) item.Run(session);
}
}
Раннеры имеют ряд требований к реализации:
- Для одного интерфейса может быть только одна реализация раннера;
- Наследоваться от
EcsRunner<TInterface>
можно только напрямую;- Раннер может содержать только один интерфейс(за исключением
IEcsSystem
);- Наследуемый класс
EcsRunner<TInterface>,
в качествеTInterface
должен принимать реализованный интерфейс;- Раннер не может быть размещен внутри другого класса.
Мир
Является контейнером для сущностей и компонентов.
Группа
Группы это структуры данных для хранения списка сущностей и с быстрыми операциями добавления/удаления/проверки наличия и т.д. Реализованы классом EcsGroup и структурой EcsReadonlyGroup.
Пул
Является контейнером для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей
EcsPool
- универсальный пул, хранит struct-компоненты реализующие интерфейс IEcsComponent;EcsTagPool
- подходит для хранения пустых компонентов-тегов, в сравнении с EcsPool имеет лучше оптимизацию памяти и дейсвий с пулом, хранит в себе struct-компоненты реализующие IEcsTagComponent;
Так же имеется возможность реализации пользовательского пула
Субъект
Это классы наследуемые от EcsSubject, которые используются как посредник для взаимодейсвия с сушностями.
Запросы
Используйте метод-запрос EcsWorld.Where<TSubject>(out TSubject subject)
для получения необходимого системе набора сущностей. Запросы работают в связке с субъектами, субъекты определяют ограничения запросов, результатом запроса становится группа сущностей удовлетворяющия условиям субъекта. По умолчанию запрос делает выборку из всех сущностей в мире, но так же можно сделать выборку из определенной группы сущностей, для этого используйте EcsWorld.WhereFor<TSubject>(EcsReadonlyGroup sourceGroup, out TSubject subject)
Debug
Фреймворк предоставляет дополнительные интрументы для отладки и логирования, не зависящие от среды.
Атрибуты
В чистом виде атрибуты не имеют применения, но испольщуются в интеграциях с движками для задания отображения в отладочных интурментах и редакторах.
// Задает пользовательское название типа, по умолчанию используется имя типа.
using DCFApixels.DragonECS;
...
[DebugName("SomeComponent")]
// Задает цвет типа в системе rgb, где каждый канал принимает занчение от 0 до 255, по умолчанию белый.
// ВЫбрать цвет можно как вручную задав rgb значения, так и использовать заранее заготовленные цвета в enum DebugColor.
[DebugColor(DebugColor.Red)]
// Добавляет описание типу.
[DebugDescription("The quick brown fox jumps over the lazy dog")]
// Скрывает тип.
[DebugHide]
public struct Component { }
EcsDebug
Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы являются посредниками между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить его на другие движки, достаточно только реализовать специальный Debug-сервис.
По умолчанию используется DefaultDebugService
который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от DebugService
и реализайте абстрактные члены класса.
Расширения
- Автоматическое внедрение зависимостей
- Интеграция с движком Unity (Work in progress)