ET框架的ECS架构是从ECS原生设计思想变形而来的(关于ECS架构的分析可以参考跳转链接:《ECS架构分析》),其特点是:
Entity:实体可以作为组件挂载到其他实体上,Entity之间可以有父子嵌套关系,和其他ECS架构一样,Entity只允许是纯数据的(除了基本接口)System:和其他ECS架构相比,一样的是系统是纯函数。不一样的是ET的系统不是“自驱”的,而是响应式的。与其说是System,个人倒是觉得可以认为是EventHandle事件处理函数。ET框架是基于U3D的,它的Entity和System的有点像GameObject的数据和函数的拆分:Entity有GameObject的组件式设计、父子嵌套、序列化等,而函数则被分成了多个系统(或者说事件响应函数),即被分成了Awake、Start、Update等等多个事件。和GameObject一样,这套ECS是ET用户开发者开发业务的“基石”。
Entity详解Entity继承了IDisposable,在删除的时候必须调用Dispose方法,以防非托管资源泄露
(资料图片仅供参考)
Scene是一个特殊的Entity,Entity是具有父子嵌套结构的,可以形成树形结构,而Scene则被定义树的根,它可以(注意是可以)没有父节点,其他普通的(例如单例可能是例外)Entity必须有父节点或者作为组件挂载在Entity上。通过Scene来维护一棵Entity树。
Domain指向Entity所在的那棵树的根节点,是指下述层次中的ZoneScene
ZoneScene的Id,在服务器端作为区服的索引id
层级先来看看常见的客户端模块生命周期管理分层:
App(Game)层:进入App时被初始化,持续整个应用程序生命周期。有资源管理模块,定时器模块,...User(Player)层:跟随玩家登录登出变化,登入被初始化,登出被清理掉。有背包模块,技能模块,...Scene层:随场景变化,切场景时初始化并清理上一个场景,并刷新某些关联模块,GC... 有地图,玩家角色单位,怪物 ...ET的客户端和上述类似,有:
Game + 单例:类似上述App层,有计数器、配置表、资源管理等单例组件ZoneScene:类似上述User层,有UI、技能、任务、背包等组件CurrentScene:当前地图(场景),有玩家、怪物、NPC等单位,还有场景相关的组件在服务器上,则不太一样:
GameScene:管理进程必备的基础组件ZoneScene:当前ZoneScene业务相关的组件,比如Gate类型的ZoneScene包含GateSessionKeyCompontent,而Map类型的不用。CurrentScene:服务器多数服务不需要,可能地图服务器或者战斗服务器会用到,像聊天服务大多数都用不到。System详解ET框架的ECS架构的System,其最明显的特征它是响应式的,说是System,感觉更像是平时用的EventHandle事件处理函数
事件机制EventSystem引述官方文档的介绍:
ECS最重要的特性一是数据跟逻辑分离,二是数据驱动逻辑。什么是数据驱动逻辑呢?不太好理解,我们举个例子:一个moba游戏,英雄都有血条,血条会在人物头上显示,也会在左上方头像UI上显示。这时候服务端发来一个扣血消息。我们怎么处理这个消息?第一种方法,在消息处理函数中修改英雄的血数值,修改头像上血条显示,同时修改头像UI的血条。这种方式很明显造成了模块间的耦合。第二种方法,扣血消息处理函数中只是改变血值,血值的改变抛出一个hpchange的事件,人物头像模块跟UI模块都订阅血值改变事件,在订阅的方法中分别处理自己的逻辑,这样各个模块负责自己的逻辑,没有耦合。
这里的事件机制被赋予了更多的意义,也就是ECS的System的核心意义:使业务更加的内聚,感知不到多个组件聚合在Entity中带来的耦合。
事件类型关联Entity的事件类型范式为:
public class AAABBBSystem: BBBSystem{public override void BBB(AAA aaa){}}/*- AAA是Entity的派生类- BBB是ET框架内置的一些事件名,如Awake、Start、Update...- 事件的抛出可以带N个参数,通过泛型处理的, 类似上述片段变形BBBSystem这样例如类型为Player的Entity的Awake事件订阅处理: */public class PlayerAwakeSystem: AwakeSystem{public override void Awake(Player self){//DoSomething}}
ET框架自动抛出AwakeSystem:组件工厂创建组件后抛出,只抛出一次StartSystem:Entity在UpdateSystem调用前抛出UpdateSystem:Entity每帧抛出DestroySystem:Entity被删除抛出DeserializeSystem:Entity反序列化时抛出LoadSystem:EventSystem加载dll时抛出,用于服务端热更新,重新加载dll做一些处理,比如重新注册handler开发者手动抛出ChangeSystem:组件内容改变时抛出若需要开发者进行抛出开发者自定义事件引述官方示例:
int oldhp = 10; int newhp = 5; // 抛出hp改变事件 Game.EventSystem.Run("HpChange", oldhp, newhp); // UI订阅hp改变事件 [Event("HpChange")] public class HpChange_ShowUI: AEvent { public override void Run(int a, int b) { throw new NotImplementedException(); } } // 模型头顶血条模块也订阅hp改变事件 [Event("HpChange")] public class HpChange_ModelHeadChange: AEvent { public override void Run(int a, int b) { throw new NotImplementedException(); } }
可以看到:
使用字符串作为事件的key使用C#特性(Attrbute)Event对响应事件的处理类标记,且该处理类继承AEvent可以带若干参数,据官方文档最多三个,有需要可以自行拓展消息事件引述官方示例:除此之外还有很多事件,例如消息事件。消息事件使用MessageHandler来声明,可以带参数指定哪种服务器需要订阅,更具体的消息事件可以参考消息模块。
[MessageHandler(AppType.Gate)]public class C2G_LoginGateHandler : AMRpcHandler{protected override void Run(Session session, C2G_LoginGate message, Action reply){G2C_LoginGate response = new G2C_LoginGate();reply(response);}}
ECS架构用例组件的组装可以封装起来,比如工厂模式,这里只是示意
// 首先得有一个父节点Entity parent = XXXHuman human = parent.AddChild();Head head = human.AddComponent();head.AddComponent();head.AddComponent();head.AddComponent();head.AddComponent();class Eye: Entity{public string Color { get; set; }}// 订阅Eye的Awake事件处理(AddComponent时抛出的)public class EyeAwakeSystem: AwakeSystem{public override void Awake(Eye self){self.Color = "Black";}}// ...