LeoECS - 简单的轻量级 C# 实体-组件-系统框架

性能,零/小 内存 分配/占用空间,这个项目的主要目标——不依赖于任何游戏引擎。

**重要!**它是“基于结构”的版本,如果你搜索“基于类”的版本-检查基于类的分支!

本框架要求C#7.3或以上。

**重要!**不要忘记在生产环境中使用调试版本进行开发和发布版本:所有内部错误检查/异常抛出只在调试版本中起作用,并在发布环境中出于性能原因而被删除。

**重要!**Ecs核心API不安全,永远不会安全!如果您需要多线程处理-您应该在您的ecs系统中实现它。

下载

作为Unity模块

此存储库可以直接从git url安装为unity模块。这样,新行应该添加到“Packages/manifest.json”中:

"com.leopotam.ecs": "https://github.com/Leopotam/ecs.git",

默认情况下,将使用最新发布的版本。如果您需要中继/开发版本,则应在哈希之后添加分支的“开发”名称:

"com.leopotam.ecs": "https://github.com/Leopotam/ecs.git#develop",

作为来源

如果您不能/不想使用unity模块,可以从Releases page下载代码作为所需版本的存档源。

ECS主要部件

Component(组件)

用户数据容器,内部没有/有小逻辑:

struct WeaponComponent {public int Ammo;public string GunName;
}

**重要!**别忘了手动初始化每个新组件的所有字段-它们将在回收到池时重置为默认值。

Entity(实体)

组件容器。实现为“EcsEntity”,用于包装内部标识符:

//在世界上下文中创建新实体
EcsEntity entity = _world.NewEntity ();//Get()返回实体上的组件。如果组件不存在-将添加该组件。
ref Component1 c1 = ref entity.Get<Component1> ();
ref Component2 c2 = ref entity.Get<Component2> ();//Del()从实体中删除组件。
entity.Del<Component2> ();//可以用组件的新实例替换组件。如果组件不存在-它将被添加。
var weapon = new WeaponComponent () { Ammo = 10, GunName = "Handgun" };
entity.Replace (weapon);//使用Replace()可以链接组件的创建:
var entity2 = world.NewEntity ();
entity2.Replace (new Component1 { Id = 10 }).Replace (new Component2 { Name = "Username" });//任何实体都可以与所有组件一起复制:
var entity2Copy = entity2.Copy ();//任何实体都可以合并/“移动”到另一个实体(源将被销毁):
var newEntity = world.NewEntity ();
entity2Copy.MoveTo (newEntity);
//entity2Copy中的所有组件已移动到newEntity,entity2Copy已销毁。//任何实体都可以被摧毁。
entity.Destroy ();

**重要!**没有组件的实体将在最后一次EcsEntity.Del()调用时自动删除。

System(系统)

用于处理过滤实体的逻辑容器。用户类应实现 “IECsInitSystem”、 “IEcsDestroySystem”、 “IEcsRunSystem”(或其他支持的)接口:

class WeaponSystem : IEcsPreInitSystem, IEcsInitSystem, IEcsDestroySystem, IEcsPostDestroySystem {public void PreInit () {//将在EcsSystems.Init()调用期间和iecsintsystem.Init之前调用一次。}public void Init () {//将在EcsSystems.Init()调用期间调用一次。}public void Destroy () {//将在EcsSystems.Destroy()调用期间调用一次。}public void PostDestroy () {//将在EcsSystems.Destroy()调用期间和IEcsDestroySystem.Destroy之后调用一次。}
}
class HealthSystem : IEcsRunSystem {public void Run () {//将在每个EcsSystems.Run()调用上调用。}
}

数据注入

ECS系统的所有兼容“EcsWorld”和“EcsFilter”字段将自动初始化(自动注入):

class HealthSystem : IEcsSystem {//自动注入字段。EcsWorld _world = null;EcsFilter<WeaponComponent> _weaponFilter = null;
}

任何自定义类型的实例都可以通过EcsSystems.Inject()方法注入到所有系统:

var systems = new EcsSystems (world).Add (new TestSystem1 ()).Add (new TestSystem2 ()).Add (new TestSystem3 ()).Inject (a).Inject (b).Inject (c).Inject (d);
systems.Init ();

每个系统都将被扫描到具有适当初始化的兼容字段(可以包含所有字段或没有一个字段)。

**重要!**任何用户类型的数据注入都可以用于在系统之间共享外部数据。

多ECS系统的数据注入

如果您想使用多个“EcsSystems”,您可以找到DI的奇怪行为

struct Component1 { }class System1 : IEcsInitSystem {EcsWorld _world = null;public void Init () {_world.NewEntity ().Get<Component1> ();}
}class System2 : IEcsInitSystem {EcsFilter<Component1> _filter = null;public void Init () {Debug.Log (_filter.GetEntitiesCount ());}
}var systems1 = new EcsSystems (world);
var systems2 = new EcsSystems (world);
systems1.Add (new System1 ());
systems2.Add (new System2 ());
systems1.Init ();
systems2.Init ();

您将在控制台获得“0”。问题是DI从每个“EcsSystems”中的“ Init()”方法开始。这意味着任何新的“EcsFilter”实例(具有延迟初始化)将只正确地注入到当前的“EcsSystems”中。

要修复此行为,应按以下方式修改启动代码:

var systems1 = new EcsSystems (world);
var systems2 = new EcsSystems (world);
systems1.Add (new System1 ());
systems2.Add (new System2 ());
systems1.ProcessInjects ();
systems2.ProcessInjects ();
systems1.Init ();
systems2.Init ();

修复后你应该在控制台得到“1”。

指定类

EcsFilter

用于保存具有指定组件列表的筛选实体的容器:

class WeaponSystem : IEcsInitSystem, IEcsRunSystem {//自动注入字段:EcsWorld实例和EcsFilter。EcsWorld _world=null;//我们想要得到有“WeaponComponent”而没有“HealthComponent”的实体。EcsFilter<WeaponComponent>.Exclude<HealthComponent> _filter = null;public void Init () {_world.NewEntity ().Get<WeaponComponent> ();}public void Run () {foreach (var i in _filter) {//包含WeaponComponent的实体。ref var entity = ref _filter.GetEntity (i);//Get1将返回链接到附加的“WeaponComponent”。ref var weapon = ref _filter.Get1 (i);weapon.Ammo = System.Math.Max (0, weapon.Ammo - 1);}}
}

**重要!**如果要销毁此数据的一部分(实体或组件),则不应对此筛选器上foreach循环之外的任何筛选器数据使用“ref”修饰符—这将破坏内存完整性。

filterInclude约束中的所有组件都可以通过EcsFilter.Get1()EcsFilter.Get2()等进行快速访问—顺序与在筛选器类型声明中使用的顺序相同。

如果不需要快速访问(例如,对于没有数据的基于标志的组件),组件可以实现“IEcsIgnoreInFilter”接口,以减少内存使用并提高性能:

struct Component1 { }struct Component2 : IEcsIgnoreInFilter { }class TestSystem : IEcsRunSystem {EcsFilter<Component1, Component2> _filter = null;public void Run () {foreach (var i in _filter) {//它的有效代码。ref var component1 = ref _filter.Get1 (i);//由于内存/性能原因,_filter.Get2()的缓存导致其无效代码为空。ref var component2 = ref _filter.Get2 (i);}}
}

重要信息:任何过滤器都支持最多6种组件类型,如“include”约束,最多支持2个组件类型作为“排除”约束。约束更短—性能更好。

重要提示:如果您尝试使用两个具有相同组件但顺序不同的筛选器,则会出现异常,其中包含有关冲突类型的详细信息,但仅限于“DEBUG”模式。在“RELEASE”模式下,将跳过所有检查。

EcsWorld

所有实体/组件的根级容器,工作方式与隔离环境类似。

重要提示:当实例不再使用时,不要忘记调用EcsWorld.Destroy()方法。

EcsSystems

要处理“EcsWorld”实例的系统组:

class Startup : MonoBehaviour {EcsWorld _world;EcsSystems _systems;void Start () {//创建ecs环境。_world = new EcsWorld ();_systems = new EcsSystems (_world).Add (new WeaponSystem ());_systems.Init ();}void Update () {//处理所有相关系统。_systems.Run ();}void OnDestroy () {//销毁系统逻辑组。_systems.Destroy ();//毁灭世界。_world.Destroy ();}
}

EcsSystems实例可用作嵌套系统(支持任何类型的IEcsInitSystemIEcsRunSystem、ECS行为):

//initialization初始化。
var nestedSystems = new EcsSystems (_world).Add (new NestedSystem ());
//不要在这里调用nestedSystems.Init(),rootSystems会自动执行。var rootSystems = new EcsSystems (_world).Add (nestedSystems);
rootSystems.Init ();//update loop 更新循环。//不要在这里调用nestedSystems.Run(),rootSystems将自动执行它。
rootSystems.Run ();// destroying 销毁
//不要在这里调用nestedSystems.Destroy(),rootSystems会自动执行。
rootSystems.Destroy ();

在运行时可以进行处理启用或禁用任何“IEcsRunSystem”或“EcsSystems”实例:

class TestSystem : IEcsRunSystem {public void Run () { }
}
var systems = new EcsSystems (_world);
systems.Add (new TestSystem (), "my special system");
systems.Init ();
var idx = systems.GetNamedRunSystem ("my special system");//这里的状态为真,默认情况下所有系统都处于活动状态。
var state = systems.GetRunSystemState (idx);//禁止系统执行。
systems.SetRunSystemState (idx, false);

引擎集成

Unity

在unity 2019.1上测试(不依赖于它),并包含用于编译到单独的程序集文件的程序集定义(出于性能原因)。

Unity编辑器集成包含代码模板和world debug viewer。

自定义引擎Custom engine

代码示例-每个部分应集成在引擎执行流的适当位置

using Leopotam.Ecs;class EcsStartup {EcsWorld _world;EcsSystems _systems;//ecs世界和系统初始化。void Init () {        _world = new EcsWorld ();_systems = new EcsSystems (_world);_systems// 在此处注册系统,例如:// .Add (new TestSystem1 ())// .Add (new TestSystem2 ())// 注册一帧组件(顺序很重要),例如:// .OneFrame<TestComponent1> ()// .OneFrame<TestComponent2> ()// 在此处插入服务实例(顺序并不重要),例如// .Inject (new CameraService ())// .Inject (new NavMeshSupport ()).Init ();}//引擎更新循环。void UpdateLoop () {_systems?.Run ();}//清理。void Destroy () {if (_systems != null) {_systems.Destroy ();_systems = null;_world.Destroy ();_world = null;}}
}

LeoECS支持的项目

With sources:

  • 太空入侵者(枪弹变异)游戏
  • 跑步者游戏
  • 吃豆人游戏
  • TicTacToe game (obsoleted api). “Making of” video (in Russian)
  • GTA5自定义模式(由基于类的版本提供支持))

发布的游戏:

  • “空手交货城市大亨”
  • “破坏者””
  • “讨厌的鸟”
  • “我是箭””
  • “催眠剂”
  • “TowerRunner Revenge”
  • “原住民”

拓展Extensions

  • Unity editor integration
  • Unity uGui events support
  • Multi-threading support
  • Service locator
  • Engine independent types

常见问题(FAQ)

基于结构,基于类的版本?哪个更好?为什么?

基于类的版本是稳定的,但在活跃的开发环境下不会再稳定了——除了错误修复(可以在“基于类”的分支中找到)。

结构只基于一个正在进行开发的版本。它应该比基于类的版本更快,组件清理更简单,并且您可以稍后更轻松地切换到“unity ecs”(如果您愿意)。即使在“unity ecs”发布之后,这个框架仍将处于开发阶段。

我想知道——组件是否已经添加到实体中,并获得它/添加新组件,否则,我如何做到?

如果您不关心组件是否已添加,并且您只想确保实体包含该组件-只需调用EcsEntity.Get<T>-它将返回已存在的组件,如果不存在,则添加全新的组件。

如果您想知道该组件是否存在(稍后在自定义逻辑中使用它),请使用EcsEntity.Has<T>方法,该方法将返回该组件之前添加的事实。

我想在MonoBehaviour.Update() 处理一个系统,在MonoBehaviour.FixedUpdate()处理另一个系统。我该怎么做?

For splitting systems by MonoBehaviour-method multiple EcsSystems logical groups should be used:

应使用多个“EcsSystems”逻辑组的MonoBehaviour方法来拆分系统:

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 = null;//将跳过。[EcsIgnoreInject]EcsFilter<C2> _filter2 = null;

我不喜欢foreach循环,我知道for循环更快。我怎么用?

当前foreach循环的实现速度足够快(自定义枚举器,无内存分配),在10k项和更多项上可以发现较小的性能差异。当前版本不再支持for循环迭代。

我一次又一次地复制和粘贴我的重置组件代码。我怎么能用其他方式做呢?

如果要简化代码并将reset/init代码保留在一个位置,可以设置自定义处理程序来处理组件的清理/初始化:

struct MyComponent : IEcsAutoReset<MyComponent>
{public int Id;public object LinkToAnotherComponent;public void AutoReset(ref MyComponent c){c.Id = 2; c.LinkToAnotherComponent = null;}
}

对于全新的组件实例,在从实体中移除组件之后,在回收到组件池之前,将自动调用此方法。

重要提示:对于自定义的“AutoReset”行为,引用类型字段没有任何附加检查,您应该提供正确的cleanup/init行为,而不会出现内存泄漏。

我将组件用作只工作一个帧的事件,然后在执行序列的最后一个系统中删除它。太无聊了,我怎么能把它自动化呢?

如果要删除单帧组件而不附加自定义代码,可以在“EcsSystems”中注册它们:

struct MyOneFrameComponent
{}
EcsSystems _update;
void Start()
{var world = new EcsWorld(); _update = new EcsSystems(world); _update.Add(new CalculateSystem()).Add(new UpdateSystem()).OneFrame<MyOneFrameComponent>().Init();
}
void Update() { _update.Run(); }

重要提示:具有指定类型的所有单帧组件都将在执行流中的 调用OneFrame()注册该组件的位置处 删除。

我需要对内部结构的默认缓存大小进行更多的控制,我该怎么做?

可以使用EcsWorldConfig实例设置自定义缓存大小:

var config = new EcsWorldConfig()
{// World.Entities default cache size.   WorldEntitiesCacheSize = 1024,// World.Filters default cache size.   WorldFiltersCacheSize = 128,// World.ComponentPools default cache size.   WorldComponentPoolsCacheSize = 512,// Entity.Components default cache size (not doubled). EntityComponentsCacheSize = 8,// Filter.Entities default cache size.    FilterEntitiesCacheSize = 256,
};
var world = new EcsWorld(config); ...

我需要超过6个“包括”或超过2个“排除”在过滤器组件,我怎么做呢?

You can use EcsFilter autogen-tool and replace EcsFilter.cs file with brand new generated content.

我想添加一些反应行为对过滤器项目的变化,我怎么做呢?

可以使用 LEOECS_FILTER_EVENTS 定义来启用自定义事件侦听器对筛选器的支持:

class CustomListener : IEcsFilterListener
{public void OnEntityAdded(in EcsEntity entity){// reaction on compatible entity was added to filter.        对兼容实体的反应已添加到筛选器。    }public void OnEntityRemoved(in EcsEntity entity){// reaction on noncompatible entity was removed from filter.        //对不相容实体的反应已从筛选器中移除。    }
}
class MySystem : IEcsInitSystem, IEcsDestroySystem
{readonly EcsFilter<Component1> _filter = null;readonly CustomListener _listener = new CustomListener(); public void Init(){// subscribe listener to filter events.        //订阅侦听器以筛选事件。        _filter.AddListener(_listener);}public void Destroy(){// unsubscribe listener to filter events.       //取消订阅侦听器以筛选事件。        _filter.RemoveListener(_listener);}
}

Unity简单的轻量级ECS框架 LeoECS中文文档相关推荐

  1. Spring 系列框架的中文文档

    现在互联网上的 Spring 框架相关的中文文档基本上都是机器翻译,内容晦涩难懂且常年未更新.例如,像 spring-security 等这种概念繁多,体系复杂的技术,对于新手来说,没有优质的技术文档 ...

  2. vuetify中文文档_我们为什么选择Vuetify作为前端框架

    尝试了很多不同的前端框架,最终我们选择Vuetify(https://vuetifyjs.com)前端框架. 从Bootstrap开始,到iview,Buefy,elementUI,我们都是不断的尝试 ...

  3. Keras 深度学习框架中文文档

    2019独角兽企业重金招聘Python工程师标准>>> Keras深度学习框架中文文档 Keras官网:http://keras.io/ Github项目:https://githu ...

  4. thinkjs能在浏览器html文件,thinkjs框架的默认模板引擎Nunjucks的中文文档

    Node 端使用 $ npm install nunjucks 0 $npminstallnunjucks 下载后可直接 require('nunjucks') 使用 浏览器端使用 可直接使用 nun ...

  5. keras中文文档_【DL项目实战02】图像识别分类——Keras框架+卷积神经网络CNN(使用VGGNet)

    版权声明:小博主水平有限,希望大家多多指导. 目录: [使用传统DNN] BG大龍:[DL项目实战02]图像分类--Keras框架+使用传统神经网络DNN​zhuanlan.zhihu.com [使用 ...

  6. semantic ui中文文档_Vuetify-广受欢迎的Material风格的开源UI框架

    全世界范围内广受欢迎的 Vue UI 框架,一个非常精致的 Material Design UI 套件. Material Design 风格 UI 框架 Vuetify 是一个基于 Vue.js 精 ...

  7. 【关于如何自行下载Unity中文文档的说明】

    关于如何自行下载Unity中文文档的说明 由于诸如带宽.万里长城(qi ang).内网开发等多种原因,诸多英语未精通如吾辈国人大猴子有了在内网中查阅Unity中文文档的需求,但是Unity默认只提供了 ...

  8. unity 中文文档

    中文文档 https://connect.unity.com/doc/Scripting/Accessibility.VisionUtility

  9. Unity官方中文文档

    注意 这个是官方中文文档 这个转载自游戏开发群 其实是群内有个dalao拿到分享 https://connect.unity.com/doc

最新文章

  1. vs+命令行运行带参数cpp文件
  2. chrome 插件安装:无法添加来自此网站的应用,解决办法。安装本地crx插件方法
  3. p标签里面不能嵌套div
  4. php 累,php 记录进行累结果
  5. PodPresent-K8s时区配置
  6. 程序员面试金典 - 面试题 04.09. 二叉搜索树序列(双端队列+回溯)**
  7. 一:HTTP协议(超详解)
  8. css按钮口诀 - CSS BUG顺口溜
  9. 完美解决html中select的option不能隐藏的问题。
  10. api 读内存整数_10万+QPS 真的只是因为单线程和基于内存?
  11. 熊猫烧香病毒源代码及分析
  12. 服务器root权限安全策略配置
  13. 立方尾不变-Python
  14. php降序怎写,PHP数组如何按键名实现降序排列
  15. 便宜SSL证书申请平台 证书获取方案
  16. 关于微信小程序picker之multiSelector多列选择器
  17. cv2.error: OpenCV(4.5.2) C:\Users\runneradmin\AppData\Local\Temp\pip-req-build-1y7gm6kn\opencv\modul
  18. C++ 项目实战:跨平台的文件与视频压缩解压工具的设计与实现
  19. 薛蛮子和前8848总裁吕春维共同创立的车托帮
  20. PinBlock/PinBuffer的计算方法

热门文章

  1. linux线程及线程间通讯
  2. 关闭Mac的Microsoft AutoUpdate
  3. python异常怎么处理_python中的异常怎么处理?
  4. 【亲测有效】强烈推荐VideoSrt自动生成视频字幕(语音识别)
  5. DreamWeaver经典50问
  6. win10 SystemParametersInfo 设置屏保 不好使_抖音爆火的电子时钟罗盘屏保怎么搞呢?!...
  7. Oracle BBED单个数据文件跳过所有归档恢复
  8. 7-10 简版田忌赛马 (25 分)python
  9. SPSS一小时速成笔记(未完)
  10. 前端三剑客之 CSS - JavaEE初阶 - 细节狂魔