ET|03 ECS组件式编程
区别于Unity的ECS以及Entitas,更像是Unity Mono的编程模式
以一台电脑为例,进行ET组件式编程的模板教程,电脑作为实体,而他的组件就是各种配件,比如机箱,显示器等
Entity & Component
- Entity 实体只是一个概念上的定义,指的是一个独立物体,对ECS来说,Entity是组件的集合
- Component 组件是数据的集合,不产生行为
- 创建电脑实体
继承Entity表示这是一个实体类,继承IAwake是框架要求,用于初始化
public class Computer: Entity, IAwake
{
}
- 创建一个机箱组件
[ComponentOf]很明显了,标记这个类作为Computer类的组件
但是为什么组件类仍然是一个Entity呢?很简单,以为某一个组件仍然可能是由多个部分组成的
[ComponentOf(typeof(Computer))]
public class PCCaseComponent: Entity,IAwake
{
}
- 类似的,创建显示器、键盘、鼠标组件
[ComponentOf(typeof (Computer))]
public class KeyboardComponent: Entity, IAwake
{
}
[ComponentOf(typeof(Computer))]
public class MonitorsComponent: Entity,IAwake
{
}
[ComponentOf(typeof (Computer))]
public class MouseComponent: Entity, IAwake
{
}
以上代码均创建在Model程序集中,Model,即数据层
System
系统,是行为的集合,不包含数据,但根据组件对数据进行具体的操作
- ComputerSystem
按照框架要求,系统类,必须是一个静态类,通过扩展方法对组件进行操作
public static class ComputerSystem
{
public static void Start(this Computer self)
{
Log.Debug("Computer Start!");
}
}
- 按照要求编写另外两个System
public static class MonitorsSystem
{
public static void Display(this MonitorsComponent self)
{
Log.Debug("Display!");
}
}
public static class PCCaseComponentSystem
{
public static void StartPower(this PCCaseComponent self)
{
Log.Debug("Start Power!");
}
}
- 改变Scene的Child类型
找到这个类,给他添加一个属性[ChildType()],内容为空,意思就是任何Entity都可以是Scene的子节点,Scene会作为整个ECS的根节点
[ChildType()]
public sealed class Scene: Entity
{
}
- 测试
AppStartInitFinish_CreateLoginUI,这个类会在登录完成之后调用,具体生命周期先不谈,我们在创建UI后我们添加一些代码
public class AppStartInitFinish_CreateLoginUI: AEvent<EventType.AppStartInitFinish>
{
protected override void Run(EventType.AppStartInitFinish args)
{
UIHelper.Create(args.ZoneScene, UIType.UILogin, UILayer.Mid).Coroutine();
// 添加一些代码
var computer = args.ZoneScene.AddChild<Computer>();
computer.AddComponent<PCCaseComponent>();
computer.AddComponent<MonitorsComponent>();
computer.Start();
}
}
同时改一下ComputerSystem
public static class ComputerSystem
{
public static void Start(this Computer self)
{
Log.Debug("Computer Start!");
self.GetComponent<PCCaseComponent>().StartPower();
self.GetComponent<MonitorsComponent>().Display();
}
}
然后打开Unity,编译代码,运行服务器,最后再运行Unity
可以看到Log
运行在服务端
在服务器的数据层,创建一个相同的文件夹,然后添加存在文件
记得要选择添加链接
测试
服务端的入口函数在AppStart_Init中
编写类似的代码
var computer = Game.Scene.AddChild<Computer>();
computer.AddComponent<PCCaseComponent>();
computer.AddComponent<MonitorsComponent>();
computer.Start();
编译服务器,然后运行
Log不会出现在C#控制台,因为服务器的Log会到日志中去
日志在 ET/Logs中
Model & HotFix
- Model 数据层,定义Component与Entity,没有行为
- HotFix 逻辑层,定义System行为,且该行为是逻辑行为,可以脱离Unity Mono运行
- ModelView/HotFixView 类似于上面两个,但是会和Unity引擎进行交互,所以是显示层
Scene场景树
定义如下:
客户端层级树
注意,Scene本身也是Entity,场景树更多是一种概念上的抽象,而非框架上的
生命周期
以Demo工程为例,游戏以Mono脚本Init为根,驱动CodeLoader运行,真正的逻辑在后者内
public class Init
{
void Start()
{
CodeLoader.Instance.Start();
}
}
CodeLoader主要是读取Dll(热更新,走IL或者HybirdCLR),并运行游戏真正的逻辑
游戏逻辑的入口是:
public static class Entry
{
public static void Start()
{
Game.EventSystem.Publish(new EventType.AppStart());
}
}
很简单,就是抛了一条App启动的事件
其监听位置在类AppStart_Init中的
public class AppStart_Init: AEvent<EventType.AppStart>
{
private async ETTask RunAsync(EventType.AppStart args)
{
Game.Scene.AddComponent<TimerComponent>();
...
// 创建ZoneScene
Scene zoneScene = SceneFactory.CreateZoneScene(1, "Game", Game.Scene);
Game.EventSystem.Publish(new EventType.AppStartInitFinish() { ZoneScene = zoneScene });
}
}
其中Game.Scene约等于一个单例类
public static class Game
{
...
private static Scene scene;
public static Scene Scene
{
get
{
if (scene != null)
{
return scene;
}
...
return scene;
}
}
}
服务端层级树
服务端可以有多个ZoneScene,分别负载不同的功能
给服务端添加一个ZoneScene
以账号为例,找到场景类型枚举,添加一个类型
public enum SceneType
{
...
Account = 7,
}
然后再服务端入口代码处,可以看到服务端运行时会默认创建一些Scene
public class AppStart_Init: AEvent<EventType.AppStart>
{
private async ETTask RunAsync(EventType.AppStart args)
{
...
switch (Game.Options.AppType)
{
case AppType.Server:
{
// 这里是读取的配表
var processScenes = StartSceneConfigCategory.Instance.GetByProcess(Game.Options.Process);
foreach (StartSceneConfig startConfig in processScenes)
{
await SceneFactory.Create(...);
}
break;
}
}
...
}
}
我们进入工厂类,参考代码添加一行
public static class SceneFactory
{
public static async ETTask<Scene> Create(...)
{
...
switch (scene.SceneType)
{
...
case SceneType.Account:
scene.AddComponent<NetKcpComponent, IPEndPoint, int>(...);
Log.Error("Account Success"); // 加一行测试代码
break;
}
return scene;
}
}
因为是账号模块,所以徐娅添加一个NetKcpComponent用于连接
因为是读表的,所以我们还需要修改配表
ET 6.0\Excel\StartSceneConfig@s.xlsx
然后运行同级目录的win_startExcelExport.bat
导表工具可以替换成Luban
编译运行服务器,可以发现一行错误日志