Данный репозитопий это сборник заметок и рекомендаций по коду для фреймворка 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 есть привязка к миру. Иначе высока вероятность "магических" ошибок в саязи с путанницей идентификаторов.
В фреймворке некоторые методы имеют 2 версии, основную и с суффиксом Unchecked. Unchecked методы это методы которые опускают все проверки и не правильное выполнение которых может привести к нестабильному состоянию компонентов фреймворка или всего проекта. К Unchecked методам стоит относиться как к Unsafe, лучше применять только в тонких для производительности местах и если вы не уверены что делаете, лучше не использовать.