Привет! Это движок, имплементирующий ECS. Тут ты можешь делать многое, к чему и так привык. При этом, ты можешь чуть больше, чем обычные ECS, так как помимо ECS в библиотеке есть встроенный CQRS и DI. CQRS в связке с ECS представляется мне максимально подходящей штукой для управления состоянием сущностей через системы. DI необходим для того, чтобы всё это работало удобно.
Так как всё в этом мире завязано на "мир" (World), то мы начнём с его создания.
var world = new WorldBuilder()
.DebugWorld() // чтобы получать больше информации в Debug-режиме
.CreateLogger(logger => logger.UseConsoleSink()); // чтобы логи выводились в консоль
.Build(); Этот код создаёт сконфигурированный объект World. Так как библиотека AOT-friendly, тут нет сканеров assembly для регистрации отдельных компонентов. Всё нужно регистрировать вручную.
Компонент - это один из базовых кирпичиков ECS. Для того, чтобы системы могли наполнить сущность жизнью, сущность должна обладать признаками - компоненты и есть эти признаки.
public struct FlyComponent(int current, int max) : IActorComponent { // необходимо указать маркерный интерфейс
public int CurrentSpeed;
public int MaxSpeed;
}Компоненты должны быть struct, чтобы всё в вашем мире могло работать эффективно и быстро.
Например, обрабатывать в системах или обработчиках шины сообщений.
В этом мире есть две сущности - Actor'ы и Asset'ы.
- Актёры - это динамичные изменяемые сущности, которые могут создаваться и удаляться, и которым можно добавлять и удалять компоненты.
- Ассеты - это сущности, которые нельзя изменить, представляющие из себя настройки мира, но которые также имеют компоненты. Единственное отличие - их нельзя создавать в процессе работы, так как они, по задумке, представляют из себя ресурсы, на основе которых будут создаваться актёры.
Создать актёра можно следующим образом:
Actor actor = world.Actors.CreateActor();
actor.Add(new FlyComponent(10, 20));
Actor<FlyComponent> typedActor = actor.As<FlyComponent>();В последней строчку был создан т.н. "типизированный актёр" или актёр, в котором точно есть летающий компонент. Эта щепотка типизации очень важна в случаях, когда вам хотелось бы знать заранее - действительно ли актёр содержит нужный компонент. Например, в шине сообщений.
Структура Actor и Actor<T> предоставляют удобную обёртку для взаимодействия с актёром. Актёр всегда знает к какому ActorContext он принадлежит и просто проксирует методы к контексту, чтобы программисту было проще.
Более привычным типом entity для классических ECS будет являться содержимое поля Actor.Id, которое содержит в себе идентификатор актёра.
Если вам удобнее работать с ними, можно сделать так:
ActorId actorId = typedActor;Созданный актёр может быть создан с использованием метода world.Actors.Create().
В этом случае он не будет обладать компонентом FlyComponent. Если мы не добавили его ранее, то добавим его:
actor.Add(new FlyComponent { CurrentSpeed = 5, MaxSpeed = 7 });То, что компонент теперь принадлежит актёру можно легко проверить с помощью метода:
actor.Has<FlyComponent>(); // it's true!Поздравляю, мы создали компонент и теперь можем его редактировать. Для этого достаточно получить ссылку на компонент и отредактировать его.
ref var component = ref actor.Get<FlyComponent>();
component.Current = 15;Однако, в данном случае никто никогда не узнает о том, что было что-то изменено. Иногда это важно, например, чтобы держать кэш позиций некоторых актёров.
Чтобы все точно узнали, что были изменения, нужно использовать специальный метод:
var component = actor.Get<FlyComponent>(); // копируем компонент
component.Current = 15; // изменяем значение копии
actor.Update(component); // в этот момент все узнают, что что-то изменилосьТут всё тоже предельно просто. Представим, что у нас есть компонент Pilot (на самом деле структура PilotComponent : IActorComponent), который мы уже добавили к актёру-самолёту.
После того как пилот захотел спать, он покинет самолёт следующим способом:
actor.Remove<PilotComponent>();Если пилота там не было, метод вернёт false и мы сможем обработать это вопиющее нарушение правил полётов.
Если компонент был, то метод вернёт true, а значит мы можем быть уверенными, что пассажиры в безопасности.
Пассажиры! Их тоже можно создать примерно так:
Asset passengerAsset = world.GetAsset<PassengerAsset>();
Actor passenger = world.Actor.BuildActor(passengerAsset, Arg.Rent("plane", planeActor))Мы находим ассет пассажира (вспомним, что у него есть компоненты).
Допустим, что он всего один.
На второй строке, с помощью зарегистрированных IActorBuilder (имплементации "строителей" актёров по компонентам ассета), буквательно создаём актёра.
Кстати, если нам будет нужно, мы сможем проверить, по какому ассету был построен актёр, запросив actor.TryGetAsset.
После создания пассажиров, мы можем в прямом смысле посадить их на самолёт. Для этого нужно воспользоваться следующим методом:
Actor<FlyComponent> plane = ...;
Actor passenger = ...;
plane.AddChild(passenger);Если повторить это много раз, то в самолёте будут сидеть 2-400 пассажиров (в зависимости от типа самолёта, конечно).