# Zdotp **Repository Path**: zhangyi111222333/zy-actor-framework ## Basic Information - **Project Name**: Zdotp - **Description**: 游戏双端框架Zdotp(zy.program)。客户端基于Unity,服务端基于.Net。 - **Primary Language**: C# - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 27 - **Forks**: 0 - **Created**: 2025-01-02 - **Last Updated**: 2025-04-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Zdotp 游戏开发双端框架Zdotp,原名为ZyActorFramework。 gitee地址:https://gitee.com/zhangyi111222333/zy-actor-framework ## 一、项目简介 Zdotp 是一个游戏开发轻量级双端框架,前端基于Unity引擎,后端基于.Net 9.0,均为C#语言编写。 ## 二、软件架构 下述是Zdotp的基本框架。Book目录下使用手册正在整理。 ### 服务端 服务端软件架构图如下: ![image-20250123220514107](https://zyzy991.oss-cn-nanjing.aliyuncs.com/img/image-20250123220514107.png) #### 1. Actor、Group和System 同Erlang和Skynet的Actor模型相似,Zdotp为单进程多线程Actor模型,后续条件允许的情况下或推出分布式版本。 - **Actor**:作为本框架的基本运算单元,不同 Actor 不会共享状态和数据,确保了各个 Actor 运行的独立性和安全性。Actor之间交互通过消息队列,这避免了因数据共享引发的并发问题。 - **Group**:等价于线程,可将多个 Actor 绑定至同一个 Group 中,使其运行在同一线程上。这有助于合理分配系统资源,优化线程利用效率。 - **System**:等价于进程,所有Group由System启动。 #### 2. 数据交互 - Zdotp.Actor在设计上参考了ET和Fantasy的ECS设计,将具体Actor逻辑和数据进行分离。目前EC仍然处于Actor内部,后续考虑加入Component削减Actor的扁平化现象。 - 逻辑处理类需要继承 ActorController 实现消息监听,其中A是ActorBase的派生类,M是消息包的派生类。 - 加载程序集时,Controller托管实例化,消息包完成池化。 ~~~c# public class MultiRoomChatHandler : ActorController { protected override void Run(MultiRoomActor actor, ActorSystem system, MessageInfo info, RoomChatMessage data) { } } ~~~ #### 3. 网络模块 - 网络模块目前仅支持Kcp通信,开发者基于框架Actor模型实现了ActorKcp。 - 基于数据和逻辑分离,逻辑处理类需要继承 KcpMessageHandler< T >,其中Message是继承于IMessage< T >的类型,也就是Proto文件编译后生成的C# class。 - 框架使用者在双端编译protobuf后,无论是客户端还是服务端,都无需关心网络包的序列化和反序列化。 ~~~c# public class KcpHandler : KcpMessageHandler { protected override void Run(ActorKcp kcpActor, ActorSystem system, uint sessionId, DataPacket netMessage) { } } ~~~ #### 4. 定时器 运行在Actor对象的Update的定时器系统,Timer和KeyFrame均在加载程序集时完成池化。使用如下: ~~~c# _timer1 = actor.MyActorSystem.CreateTimer(timer => { // timer.IsLoop = false; timer.AddKeyFrame(2, () => { actor.Logger.Info($"Timer1 is trigger [2s]"); }); timer.AddKeyFrame(1, () => { actor.Logger.Info($"Timer1 is trigger [1s]"); }); }, _ => { _timer1 = null; }); _timer1.Start(actor); ~~~ #### 5. 对象池 Zdotp中几乎所有组成部分都依托于对象池系统,同时也为框架使用者开放。 #### 6. 日志系统 Zdotp.Logger封装NLog实现日志框架。 ### 客户端 客户端软件架构图如下: ![image-20250123220716278](https://zyzy991.oss-cn-nanjing.aliyuncs.com/img/image-20250123220716278.png) #### 1. Panel、Node、UiRoot和GameFace - **panel**:继承自PanelBase,UI模块主体,作为Mono组件存储在UiRoot中。例如登录面板、大厅面板、游戏轮盘面板。 - **Node**:继承自NodeBase,逻辑模块主体,作为静态对象存储在GameFace中。例如负责网络模块的Node、负责资源模块的Node、负责房间模块的Node。 - **uiRoot**:继承自MonoBehaviour,在进程开始后注入进GameFace中,通过栈式管理ui。 - **gameFace**:静态对象。加载程序集,做panel和node的初始化操作,作为框架的全局驱动。 #### 2. 模块交互 - 框架提供了**发布订阅**的方式进行模块间交互。 - 和服务端不同的是,Zdotp.Server的Actor模型鼓励使用者将一个MailObject类型用于ActorA到ActorB的单工信道。客户端中可以将MailObject用于发布订阅。 - 框架开发者认为Panel是**无状态的**,所以只有当前Panel(栈顶)才会监听消息。 - 此外,对于Node的获取,可以通过GameFace.GetNode< T >() 的方式取到对应Node。 #### 3. 网络模块 与Zdotp.Server类似,示例如下: ~~~c# public class KcpHandler : KcpMessageHandler { protected override void Run(NodeKcp kcpNode, DataPacket netMessage) { } } ~~~ #### 4. 对象池 - 具有和服务端相同功能的 [ Pure C# ] 对象池系统。 - 此外,针对于Unity的GameObject,开发了一套特定的对象池系统,框架开发者提供的NodeRes中可以调用对象池API。 ## 三、如何测试?(unity2022) ### 5.1 下载发行版本 将release版本中的包进行下载解压。zdotp-out中的dll文件引入后端项目,将Zdotp.Unity解压后丢进unity项目的Assets目录中。 ### 5.2 测试运行 #### 服务端 1. 服务端创建控制台程序,将zdotp-out中的dll导入控制台程序中。 2. 创建C#文件。 KcpHandler.cs ~~~c# using Pb; using Zdotp.Actor; using Zdotp.Actor.Controller; using Zdotp.Actor.Kcp; using Zdotp.Actor.Packet; using Zdotp.Net.NetMessageHandler; namespace DemoServer; public class TestActor : ActorBase; public class TestMessage : MailObject { public string Content; } public class TestActorHandler : ActorController { protected override void Run(TestActor actor, ActorSystem system, MessageInfo info, TestMessage data) { var str = $"TestActor receive : {data.Content}"; actor.Logger.Info(str); actor.SendMessage(system.CreateMail(message => { message.Data = ActorSystem.GetNetPacket(new DataPacket { Content = str }); }), 288, info.SessionId); } } public class ConnectController : ActorController { protected override void Run(ActorKcp actor, ActorSystem system, MessageInfo info, ConnectMessage data) { actor.Logger.Info($"session {info.SessionId} connect..."); } } public class ConnectHandler : ActorController { protected override void Run(ActorKcp actor, ActorSystem system, MessageInfo info, DisConnectMessage data) { actor.Logger.Info($"session {info.SessionId} disconnect..."); } } public class DataPacketHandler : KcpMessageHandler { protected override void Run(ActorKcp kcpActor, ActorSystem system, uint sessionId, DataPacket netMessage) { var str = $"KcpActor receive : {netMessage.Content}"; kcpActor.Logger.Info(str); kcpActor.SendMessage(system.CreateMail(message => { message.Content = netMessage.Content; }), 288, sessionId); // gate类内部使用SendMsgToClient回显给客户端 kcpActor.SendMsgToClient(sessionId, new DataPacket {Content = str}); } } ~~~ Program.cs ~~~c# using Zdotp.Actor; using Zdotp.Actor.Kcp; using Zdotp.ObjectPool; namespace DemoServer; internal class Program { public static void Main(string[] args) { var actorSystem = new ActorSystem("demo-system"); ObjectPoolManager.IsLogger = true; actorSystem.AddActor(() => new ActorKcp("127.0.0.1", 7890), 1); actorSystem.AddActor(() => new TestActor(), 2); actorSystem.Start([typeof(Program).Assembly]); } } ~~~ #### 客户端 客户端找到Zdotp.Unity/Demo/Scene/SampleScene场景,在Unity中运行。 ![image-20250123235614234](https://zyzy991.oss-cn-nanjing.aliyuncs.com/img/image-20250123235614234.png)