/DragonECS-Vault

A set of notes and code recommendations for the DragonECS framework.

image

DragonECS-Vault

Данный репозитопий это сборник заметок и рекомендаций по коду для фреймворка DragonECS.

Материал расположенный в этом репозитории не является сводом правил, не обязателен к ознакомлению, и имеет только рекомендательный характер.

Местами материал может быть даже спорным, по спорным темам приглашаю вас обкашлять их в Discord

Стиль кода

Пример стиля

class ApplyVelocitySystem : IEcsRun, IEcsInject<EcsDefaultWorld>, IEcsInject<TimeService>
{
    class Aspect : EcsAspect
    {
        public EcsPool<Pose> poses = Inc;
        public EcsPool<Velocity> velocities = Inc;
        public EcsTagPool<FreezedTag> freezedTags = Exc;
    }

    public void Inject(EcsDefaultWorld obj) => _world = obj;
    public void Inject(TimeService obj) => _time = obj;

    EcsDefaultWorld _world;
    TimeService _time;

    public void Run()
    {
        foreach (var e in _world.Where(out Aspect a))
        {
            a.poses.Get(e).position += a.velocities.Get(e).value * _time.DeltaTime;
        }
    }
}

Тот же пример, но с Auto-Injections:

class ApplyVelocitySystem : IEcsRun
{
    class Aspect : EcsAspect
    {
        public EcsPool<Pose> poses = Inc;
        public EcsPool<Velocity> velocities = Inc;
        public EcsTagPool<FreezedTag> freezedTags = Exc;
    }

    [EcsInject] EcsDefaultWorld _world;
    [EcsInject] TimeService _time;

    public void Run()
    {
        foreach (var e in _world.Where(out Aspect a))
        {
            a.poses.Get(e).position += a.velocities.Get(e).value * _time.DeltaTime;
        }
    }
}

Начну с объявления аспектов. Аспекты, хоть и могут использоваться одновременно несколькими системами, удобнее всего объявлять для каждой системы свои прямо внутри систем. Объявление аспекта стоит делать в самом верху системы, так удобнее читать систему, сначала идет название системы из которого можно понять что она делает, далее идет описание аспектов с которыми система работает.

Именование полей в Аспектах. Поля для кеша пулов называть по названию компонента во множественном числе,например EcsPool<Health> healths. Для этого пулы фейково реализуют IEnumerable, чтобы автодополненте IDE предлагало такое наименование.

Модификаторы доступа private/public для членов систем не имеют применения, все взаимодейсвие с системами происходит либо косвенно через данные в компонентах, а если напрямую, то через интерфейсы. Поэтому для сокращения бойлерплейта и улучшения восприятия модификаторы доступа могут быть опушены где это возможно.

Именование.

Именование аспектов. Многие системы работают только с одним аспектом, так что аспекты можно называть просто Aspect. Если аспектов несколько то основной также называть Aspect а второстепенный с префиксом, например EventAspect.

Именование переменных. По той же причине что системы часто работают только с одним аспектом, возвращаемый запросом Where экземпляр аспекта можно называть просто a, а сущности внутри foreach просто e, например: foreach(var e in _world.Where(out Aspect a). Аналогично именованию аспектов, если система работает с несколькими аспектами, то к a и e добавляется префикс, например: foreach(var eventE in _world.Where(out EventAspect eventA).

Модули

Группы систем и компонетов объединенные одной логикой или реализующие некую фичу лучше ораганизовывать в модули. Модули можно рассматривать как отедльные сборки, можно даже разбивать их на отдельные сборки. Папка одного модуля имеет следующую иерархию:

.../
------+
      | SomeModule/
      +------+
      |      | Components/
      |      +------+
      |      |      ¦ SomeComponent.cs
      ¦      |      : IsTagged.cs
      :      |      ·
      ·      | Events/
      ·      +------+
             |      ¦ SomeEvent.cs
             |      : SomeSignal.cs
             |      ·
             | Systems/
             +------+
             |      ¦ SomeSystem1.cs
             |      : SomeSystem2.cs
             |      · 
             ¦ SomeModule.cs
             :
             ·
  • SomeComponent.cs - обычные компоненты.
  • IsTagged.cs - компоненты-теги которые используются просто как bool флаг, рекомендуется называть по аналогии с рекомендацией наименования bool полей, тоесть с префиксом Is.
  • SomeSignal.cs - компонент-событие крепящийся к цели события.
  • SomeEvent.cs - компонент-событие используемый в сущности-событие, для создания нескольких событий для одной сущности.
  • SomeSystem.cs - Системы.
  • SomeModule.cs - Класс реализующий интерфейс IEcsModule, добавляющий в пайплайн системы модуля.

Модули так же удобно группировать в один надмодуль, тоесть создавать модуль который в Import методе просто добавляет другие модули. Например в своем проекте те модули которые могут работать независимо от Unity объединяю в модуль ProjectCoreModule и то что работает с Unity объединяю в ProjectUnityModule. Посде такого раздедения в EcsRoot достаточно просто добавить эти 2 модуля.

Для систем и компонентов присущих одному модулю добавлять мета-атрибут MetaGroup, в качесве корневой группы использовать название модуля. Пример:

// Слово модуль из SomeModule будет автоматически удаленно, останется только Some
[MetaGroup(nameof(SomeModule))]
public struct SomeComponent : IEcsComponent
{
   //...
}

Далее можно следующей подгруппой выбрать то чем является тип, компонентом или системой. Для этого в EcsConsts есть предусмотренные константы.

[MetaGroup(nameof(SomeModule), EcsConsts.COMPONENTS_GROUP)]
public struct SomeComponent : IEcsComponent { /* ... */ }

[MetaGroup(nameof(SomeModule), EcsConsts.SYSTEMS_GROUP)]
public class SomeSystem : IEcsRun { /* ... */ }

Мульти миры

Фреймворк поддерживает создание нескольких миров и их совместную обработку в системах. Сразу оговорюсь это опциональная возможность, и если вы только начинаете свой путь в ецс или вам не удобно пользоваться этой фишкой, все можно помещать в один мир. Разделение может быть полезно с точки зрения улучшения использования памяти, группы сущностей которые по своей специфики не могут иметь общих аспектов с другими, такие сущности можно выделять в отдельные миры. Довольно типичный пример разделения миров это дефолтный мир где обрабатываются игровые сущности, и мир событий где обрабатываются сущности-события, по своей специфики у игровых и событийных сущностей редко будут пересечения аспектов, поэтому их можно выделить в отдельный мир.

Для хранения в компонентах ссылок на сущности между мирами категорически рекомендуется использовать entlong, так как у entlong есть привязка к миру. Иначе высока вероятность "магических" ошибок в саязи с путанницей идентификаторов.

Unchecked

В фреймворке некоторые методы имеют 2 версии, основную и с суффиксом Unchecked. Unchecked методы это методы которые опускают все проверки и не правильное выполнение которых может привести к нестабильному состоянию компонентов фреймворка или всего проекта. К Unchecked методам стоит относиться как к Unsafe, лучше применять только в тонких для производительности местах и если вы не уверены что делаете, лучше не использовать.