![]() |
VOOZH | about |
dotnet add package Leopotam.Ecs --version 1.0.1
NuGet\Install-Package Leopotam.Ecs -Version 1.0.1
<PackageReference Include="Leopotam.Ecs" Version="1.0.1" />
<PackageVersion Include="Leopotam.Ecs" Version="1.0.1" />Directory.Packages.props
<PackageReference Include="Leopotam.Ecs" />Project file
paket add Leopotam.Ecs --version 1.0.1
#r "nuget: Leopotam.Ecs, 1.0.1"
#:package Leopotam.Ecs@1.0.1
#addin nuget:?package=Leopotam.Ecs&version=1.0.1Install as a Cake Addin
#tool nuget:?package=Leopotam.Ecs&version=1.0.1Install as a Cake Tool
Производительность, нулевые или минимальные аллокации, минимизация использования памяти, отсутствие зависимостей от любого игрового движка - это основные цели данного фреймворка.
ВАЖНО! РАЗРАБОТКА ДАННОГО ПРОЕКТА ПРЕКРАЩЕНА, ВМЕСТО НЕГО СЛЕДУЕТ ИСПОЛЬЗОВАТЬ EcsProto или EcsLite.
ВАЖНО! Не забывайте использовать
DEBUG-версии билдов для разработки иRELEASE-версии билдов для релизов: все внутренние проверки/исключения будут работать только вDEBUG-версиях и удалены для увеличения производительности вRELEASE-версиях.
ВАЖНО! LeoEcs-фрейморк не потокобезопасен и никогда не будет таким! Если вам нужна многопоточность - вы должны реализовать ее самостоятельно и интегрировать синхронизацию в виде ecs-системы.
Поддерживается установка в виде unity-модуля через git-ссылку в PackageManager или прямое редактирование Packages/manifest.json:
"com.leopotam.ecs": "https://github.com/Leopotam/ecs.git",
По умолчанию используется последняя релизная версия. Если требуется версия "в разработке" с актуальными изменениями - следует переключиться на ветку develop:
"com.leopotam.ecs": "https://github.com/Leopotam/ecs.git#develop",
Код так же может быть склонирован или получен в виде архива со страницы релизов.
Является контейнером для данных пользователя и не должен содержать логику (допускаются минимальные хелперы, но не куски основной логики):
struct WeaponComponent {
public int Ammo;
public string GunName;
}
Сама по себе ничего не значит и не существует, является исключительно контейнером для компонентов. Реализована как EcsEntity:
// NewEntity() используется для создания новых сущностей в контексте мира.
EcsEntity entity = _world.NewEntity ();
// Get() возвращает существующий на сущности компонент. Если компонент не существовал - он будет добавлен автоматически.
// Следует обратить внимание на "ref" - компоненты должны обрабатываться по ссылке.
ref Component1 c1 = ref entity.Get<Component1> ();
ref Component2 c2 = ref entity.Get<Component2> ();
// Del() удаляет компонент с сущности. Если это был последний компонент - сущность будет удалена автоматически. Если компонент не существовал - ошибки не будет.
entity.Del<Component2> ();
// Replace() выполняет замену компонента новым экземпляром. Если старый компонент не существовал - новый будет добавлен без ошибки.
WeaponComponent weapon = new WeaponComponent () { Ammo = 10, GunName = "Handgun" };
entity.Replace (weapon);
// Replace() позволяет выполнять "чейнинг" создания компонентов:
EcsEntity entity2 = world.NewEntity ();
entity2.Replace (new Component1 { Id = 10 }).Replace (new Component2 { Name = "Username" });
// Любая сущность может быть скопирована вместе с компонентами:
EcsEntity entity2Copy = entity2.Copy ();
// Любая сущность может "передать" свои компоненты другой сущности (сама будет уничтожена):
var newEntity = world.NewEntity ();
entity2Copy.MoveTo (newEntity); // все компоненты с "entity2Copy" переместятся на "newEntity", а "entity2Copy" будет удалена.
// Любая сущность может быть удалена, при этом сначала все компоненты будут автоматически удалены и только потом энтити будет считаться уничтоженной.
entity.Destroy ();
ВАЖНО! Сущности не могут существовать без компонентов и будут автоматически уничтожаться при удалении последнего компонента на них.
Является контейнером для основной логики для обработки отфильтрованных сущностей. Существует в виде пользовательского класса, реализующего как минимум один из IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem (и прочих поддерживаемых) интерфейсов:
class UserSystem : IEcsPreInitSystem, IEcsInitSystem, IEcsRunSystem, IEcsDestroySystem, IEcsPostDestroySystem {
public void PreInit () {
// Будет вызван один раз в момент работы EcsSystems.Init() и до срабатывания IEcsInitSystem.Init().
}
public void Init () {
// Будет вызван один раз в момент работы EcsSystems.Init() и после срабатывания IEcsPreInitSystem.PreInit().
}
public void Run () {
// Будет вызван один раз в момент работы EcsSystems.Run().
}
public void Destroy () {
// Будет вызван один раз в момент работы EcsSystems.Destroy() и до срабатывания IEcsPostDestroySystem.PostDestroy().
}
public void PostDestroy () {
// Будет вызван один раз в момент работы EcsSystems.Destroy() и после срабатывания IEcsDestroySystem.Destroy().
}
}
Все поля ecs-систем, совместимые c EcsWorld и EcsFilter<T> будут автоматически инициализированы валидными экземплярами соответствующих типов:
class HealthSystem : IEcsSystem {
// Поля с авто-инъекцией.
EcsWorld _world;
EcsFilter<WeaponComponent> _weaponFilter;
}
Экземпляр любого кастомного типа (класса) может быть инъецирован с помощью метода EcsSystems.Inject():
class SharedData {
public string PrefabsPath;
}
...
SharedData sharedData = new SharedData { PrefabsPath = "Items/{0}" };
EcsSystems systems = new EcsSystems (world);
systems
.Add (new TestSystem1 ())
.Inject (sharedData)
.Init ();
Каждая система будет просканирована на наличие полей, совместимых по типу с последующей инъекцией:
class TestSystem1 : IEcsInitSystem {
// Поле с авто-инъекцией.
SharedData _sharedData;
public void Init() {
var prefabPath = string.Format (_sharedData.Prefabspath, 123);
// prefabPath = "Items/123" к этому моменту.
}
}
ВАЖНО! Для инъекции подходят только нестатичные public/private-поля конечного класса системы, либо public/protected-поля базовых классов. Все остальные поля будут проигнорированы!
Является контейнером для хранения отфильтрованных сущностей по наличию или отсутствию определенных компонентов:
class WeaponSystem : IEcsInitSystem, IEcsRunSystem {
// Поля с авто-инъекцией.
EcsWorld _world;
// Мы хотим получить все сущности с компонентом "WeaponComponent"
// и без компонента "HealthComponent".
EcsFilter<WeaponComponent>.Exclude<HealthComponent> _filter;
public void Init () {
_world.NewEntity ().Get<WeaponComponent> ();
}
public void Run () {
foreach (int i in _filter) {
// Сущность, которая точно содержит компонент "WeaponComponent".
ref EcsEntity entity = ref _filter.GetEntity (i);
// Get1() позволяет получить доступ по ссылке на компонент,
// указанный первым в списке ограничений фильтра ("WeaponComponent").
ref WeaponComponent weapon = ref _filter.Get1 (i);
weapon.Ammo = System.Math.Max (0, weapon.Ammo - 1);
}
}
}
Любые компоненты из Include-списка ограничений фильтра могут быть получены через вызовы EcsFilter.Get1(), EcsFilter.Get2() и т.д - нумерация идет в том же порядке, что и в списке ограничений.
Если в компоненте нет данных и он используется исключительно как флаг-признак для фильтрации, то компонент может реализовать интерфейс IEcsIgnoreInFilter - это поможет уменьшить потребление памяти фильтром и немного увеличить производительность:
struct Component1 { }
struct Component2 : IEcsIgnoreInFilter { }
class TestSystem : IEcsRunSystem {
EcsFilter<Component1, Component2> _filter;
public void Run () {
foreach (var i in _filter) {
// Мы можем получить компонент "Component1".
ref var component1 = ref _filter.Get1 (i);
// Мы не можем получить "Component2" - кеш внутри фильтра не существует и будет выкинуто исключение.
ref var component2 = ref _filter.Get2 (i);
}
}
}
ВАЖНО!: Фильтры поддерживают до 6
Include-ограничений и 2Exclude-ограничений. Чем меньше ограничений в фильтре - тем он быстрее работает.
ВАЖНО! Нельзя использовать несколько фильтров с одинаковым списком ограничений, но выставленных в разном порядке - в
DEBUG-версии будет выкинуто исключение с описанием конфликтующих фильтров.
ВАЖНО! Один и тот же компонент не может быть в списках "Include" и "Exclude" одного фильтра одновременно.
Является контейнером для всех сущностей и фильтров, данные каждого экземпляра уникальны и изолированы от других миров.
ВАЖНО! Необходимо вызывать
EcsWorld.Destroy()у экземпляра мира если он больше не нужен.
Является контейнером для систем, которыми будет обрабатываться EcsWorld-экземпляр мира:
class Startup : MonoBehaviour {
EcsWorld _world;
EcsSystems _systems;
void Start () {
// Создаем окружение, подключаем системы.
_world = new EcsWorld ();
_systems = new EcsSystems (_world)
.Add (new WeaponSystem ());
_systems.Init ();
}
void Update () {
// Выполняем все подключенные системы.
_systems.Run ();
}
void OnDestroy () {
// Уничтожаем подключенные системы.
_systems.Destroy ();
// Очищаем окружение.
_world.Destroy ();
}
}
Экземпляр EcsSystems может быть использован как обычная ecs-система (вложена в другую EcsSystems):
// Инициализация.
EcsSystems nestedSystems = new EcsSystems (_world).Add (new NestedSystem ());
// Нельзя вызывать nestedSystems.Init() здесь,
// "rootSystems" выполнит этот вызов автоматически.
EcsSystems rootSystems = new EcsSystems (_world).Add (nestedSystems);
rootSystems.Init ();
// В цикле обновления нельзя вызывать nestedSystems.Run(),
// "rootSystems" выполнит этот вызов автоматически.
rootSystems.Run ();
// Очистка.
// Нельзя вызывать nestedSystems.Destroy() здесь,
// "rootSystems" выполнит этот вызов автоматически.
rootSystems.Destroy ();
Любая IEcsRunSystem система (включая вложенные EcsSystems) может быть включен или выключен из списка обработки:
class TestSystem : IEcsRunSystem {
public void Run () { }
}
EcsSystems systems = new EcsSystems (_world);
systems.Add (new TestSystem (), "my special system");
systems.Init ();
var idx = systems.GetNamedRunSystem ("my special system");
// "state" будет иметь значение "true", все системы включены по умолчанию.
var state = systems.GetRunSystemState (idx);
// Выключаем систему по ее индексу.
systems.SetRunSystemState (idx, false);
Проверено на Unity 2020.3 (не зависит от нее) и содержит asmdef-описания для компиляции в виде отдельных сборок и уменьшения времени рекомпиляции основного проекта.
Интеграция в Unity editor содержит шаблоны кода, а так же предоставляет мониторинг состояния мира.
Для использования фреймворка требуется C#7.3 или выше.
Каждая часть примера ниже должна быть корректно интегрирована в правильное место выполнения кода движком:
using Leopotam.Ecs;
class EcsStartup {
EcsWorld _world;
EcsSystems _systems;
// Инициализация окружения.
void Init () {
_world = new EcsWorld ();
_systems = new EcsSystems (_world);
_systems
// Системы с основной логикой должны
// быть зарегистрированы здесь, порядок важен:
// .Add (new TestSystem1 ())
// .Add (new TestSystem2 ())
// OneFrame-компоненты должны быть зарегистрированы
// в общем списке систем, порядок важен:
// .OneFrame<TestComponent1> ()
// .OneFrame<TestComponent2> ()
// Инъекция должна быть произведена здесь,
// порядок не важен:
// .Inject (new CameraService ())
// .Inject (new NavMeshSupport ())
.Init ();
}
// Метод должен быть вызван из
// основного update-цикла движка.
void UpdateLoop () {
_systems?.Run ();
}
// Очистка.
void Destroy () {
if (_systems != null) {
_systems.Destroy ();
_systems = null;
_world.Destroy ();
_world = null;
}
}
}
Фреймворк выпускается под двумя лицензиями, .
В случаях лицензирования по условиям MIT-Red не стоит расчитывать на персональные консультации или какие-либо гарантии.
Если не важно - существовал компонент ранее и просто нужна уверенность, что он теперь существует достаточно вызова EcsEntity.Get<T>().
Если нужно понимание, что компонент существовал ранее - это можно проверить с помощью вызова EcsEntity.Has<T>().
MonoBehaviour.Update(), а другую - в MonoBehaviour.FixedUpdate(). Как я могу это сделать?Для разделения систем на основе разных методов из MonoBehaviour необходимо создать под каждый метод отдельную EcsSystems-группу:
EcsSystems _update;
EcsSystems _fixedUpdate;
void Start () {
var world = new EcsWorld ();
_update = new EcsSystems (world).Add (new UpdateSystem ());
_update.Init ();
_fixedUpdate = new EcsSystems (world).Add (new FixedUpdateSystem ());
_fixedUpdate.Init ();
}
void Update () {
_update.Run ();
}
void FixedUpdate () {
_fixedUpdate.Run ();
}
Для этого достаточно пометить поле системы атрибутом [EcsIgnoreInject]:
// Это поле будет обработано инъекцией.
EcsFilter<C1> _filter1;
// Это поле будет проигнорировано инъекцией.
[EcsIgnoreInject] EcsFilter<C2> _filter2;
Компоненты поддерживают кастомную настройку значений через реализацию интерфейса IEcsAutoReset<>:
struct MyComponent : IEcsAutoReset<MyComponent> {
public int Id;
public object LinkToAnotherComponent;
public void AutoReset (ref MyComponent c) {
c.Id = 2;
c.LinkToAnotherComponent = null;
}
}
Этот метод будет автоматически вызываться для всех новых компонентов, а так же для всех только что удаленных, до помещения их в пул.
ВАЖНО! В случае применения
IEcsAutoResetвсе дополнительные очистки/проверки полей компонента отключаются, что может привести к утечкам памяти. Ответственность лежит на пользователе!
ВАЖНО!: Компоненты, реализующие
IEcsAutoResetне совместимы с вызовамиentity.Replace(). Рекомендуется не использоватьentity.Replace()или любые другие способы полной перезаписи компонентов.
Для автоматической очистки компонентов, которые должны жить один цикл, место их очистки может быть зарегистрировано в общем списке систем внутри EcsSystems:
struct MyOneFrameComponent { }
EcsSystems _update;
void Start () {
var world = new EcsWorld ();
_update = new EcsSystems (world);
_update
.Add (new CalculateSystem ())
// Все "MyOneFrameComponent" компоненты будут
// удалены здесь.
.OneFrame<MyOneFrameComponent> ()
// Здесь можно быть уверенным, что ни один
// "MyOneFrameComponent" не существует.
.Add (new UpdateSystem ())
.Init ();
}
void Update () {
_update.Run ();
}
Мир может быть создан с явным указанием EcsWorldConfig-конфигурации:
var config = new EcsWorldConfig () {
// Размер по умолчанию для World.Entities.
WorldEntitiesCacheSize = 1024,
// Размер по умолчанию для World.Filters.
WorldFiltersCacheSize = 128,
// Размер по умолчанию для World.ComponentPools.
WorldComponentPoolsCacheSize = 512,
// Размер по умолчанию для Entity.Components (до удвоения).
EntityComponentsCacheSize = 8,
// Размер по умолчанию для Filter.Entities.
FilterEntitiesCacheSize = 256,
};
var world = new EcsWorld(config);
ВАЖНО! Это приведет к модификации исходного кода фреймворка и несовместимости с обновлениями.
Необходимо воспользоваться кодогенерацией EcsFilter классов и заменить содержимое файла EcsFilter.cs.
ВАЖНО! Так делать не рекомендуется из-за падения производительности.
Для активации этого функционала следует добавить LEOECS_FILTER_EVENTS в список директив комплятора, а затем - добавить слушатель событий:
class CustomListener: IEcsFilterListener {
public void OnEntityAdded (in EcsEntity entity) {
// Сущность добавлена в фильтр.
}
public void OnEntityRemoved (in EcsEntity entity) {
// Сущность удалена из фильтра.
}
}
class MySystem : IEcsInitSystem, IEcsDestroySystem {
readonly EcsFilter<Component1> _filter = null;
readonly CustomListener _listener = new CustomListener ();
public void Init () {
// Подписка слушателя на события фильтра.
_filter.AddListener (_listener);
}
public void Destroy () {
// Отписка слушателя от событий фильтра.
_filter.RemoveListener (_listener);
}
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 1 NuGet packages that depend on Leopotam.Ecs:
| Package | Downloads |
|---|---|
|
Undine.LeopotamEcs
Undine bindings for LeopotamEcs framework |
Showing the top 1 popular GitHub repositories that depend on Leopotam.Ecs:
| Repository | Stars |
|---|---|
|
Doraku/Ecs.CSharp.Benchmark
Benchmarks of some C# ECS frameworks.
|