在本文,笔者介绍一下这段时间重构的 ET 网络模块,希望能够遇到需要它的人吧。
ET: 一个双端 C# Unity 游戏开发框架,网络 + 热重载的功能亮点; Attribute + 扩展方法 + 反射的底层驱动逻辑;ECS 架构特色。
前言
做一个飞屏软件,负责技术的同事敲定用 ET ,emmm.... 内心是抗拒的,毕竟项目不大,但反过来想想,项目不大正好是个学习 ET 的契机。
因为我负责控制端,用不到热更,并且习惯了在 Unity 内做开发,所以我决定把 ET 的网络模块剥离出来,用在我负责的控制端上,这样一来,既能够与 ET 后台对接,也能保持我自己的开发习惯。
当然,非要说细节上为啥抗拒,其实是有几点的,作为 ET 0 基础小白斗胆小声BB几句:
- 为了热重载,严格约束用户按照 ET ECS 架构开发,因此开发一个应用得整好几个解决方案,而且还不是常规的打开方式,跨解决方案的引用查找还挺难(可能是我太菜哈)。
- 不需要热更,ILRuntime 插件冗余了,不能忍,如果考虑热更那也只会是对现有开发模式无侵入的 HCLR。
- 修改在 Model 、 Hotfix 程序集,需要编译才能看到修改的效果,而开启自动编译一度让我困惑。
- 框架文件结构限定的比较死,由于很多路径都是写死在逻辑中,原本仅有一个 Server ,一个 Client,想要做 一个 Server + 两个 Client 令初次接触 ET 的我无从下手(当然,现在整明白了,改一些脚本中文件夹路径就行,按说 ET7 啥都内敛到了 Unity 内部,也不预设热更方案也就没有这些痛点了)。
抽离
- 更新 ET6.0 Github最新提交后,一通复制粘贴,把网络部分梳理到新工程中
- 转移 Protobuf、Litjson 、ETTask、Timer 、KCP 到新工程。
- 删除 Litjson 、Protobuf 中为适配 ILRuntime 而撰写的逻辑。
- 此时,网络模块已经大体抽离,然后再根据报错,将缺失的、离散在周边的脚本汇集整理。
- 到了这一步,网络模块还是 ECS 架构的(由于模块无需热更, ECS 没有拆分在各个程序集)所以一通改造,把各个功能模块的 ECS 整合成单脚本(其实就是把 Component的字段,System 中的 Awake 、Update 等方法整到一起,使用 MonoBehaviour 驱动)。
- ET EventSystem ,ET 的心脏,用于在启动或热重载时根据 Attribute 做 Type 预绑定,同时也是一个事件总线。网络模块、ECS以及事件分发都依赖它,一通删除,保留网络消息类型注册部分即可。
- 断开对 ET Entity 的依赖:删除继承关系,同时将 GetChild 、AddChild 进行简单的改写成实例查询和实例构建就好了。
- 两个有意思的地方:LitJson 保留是为了 网络消息实例以 json 输出,方便log;KCP 最后被干掉了是因为 ET6.0 为了通用,即便是 KServer 、KChannel 也默认不使用 KCP。
重构
- 管理器性质的组件都依赖了 MonoBehaviour 驱动,但他们多为管理器性质,所以笔者将他们改为静态类。
- 对管理器性质的组件的初始化放在 ET Eventsystem 中统一按顺序进行(比如 opcode管理器就要优先于其他消息分发管理器)。
- 对管理器性质的组件的 Update 统一放在 ET Eventsystem 中统一使用 PlayerLoop 驱动。
- 作为底层核心网络消息,将 Ping 消息 OuterMessage 中单独抽离出来,实现网络模块对 OuterMessage 零依赖。
- 重新设计非 RPC 网络消息的监听与取消监听,实现非 RPC 消息在继承了 MonoBehaviour 的类更方便的被捕获。
- 代码生成:实现非RPC 网络消息处理器的一键生成;修正 OuterMessage 的一键生成逻辑,跳过 Ping 消息。
- 引入 Loom 做线程间数据传递,剔除原有的带 Thread 关键字的组件,避免了不必要的依赖注入,使用
using static Loom;
的形式简化多线程间数据的投递。
模块化
借助 Unity 官方 Assembly Definition File ,我们可以在 Unity 项目中轻松的模块化 ET 网络模块。
- 首先,将依赖的插件抽离,放在 ThirdPart,为他们加上各自的 .asmdef 文件,使其模块化
- 然后,将用户自定义的消息类 (OuterMessage.cs 和 OuterOpcode.cs)抽离,网络模块对其 0 依赖,方便随时剔除,随时生成,随时更新。
-
网络模块重构成以下形式:
可见:
- ettask 、timer 、litjson、protobuf 都成了独立的程序集。
- 网络核心又拆分出 editor + runtime + generated(generated 为一键生成的 outermessage 和 messagehandler)
使用
- clone 本项目,将文件夹 ET Network Module 拷贝到你的项目中(可考虑自行改为 UPM )
- 删除 Generated 文件夹。
- 如果有代码引用了 OuterMessage 中的类型,则这个代码所在的文件夹改名,加上 ~ (波浪号)完成编译后再删除波浪号
- 约定:前后端公用的 outermessage.proto 中 Ping 消息置顶。
- 通过 Tools 菜单中提供的工具,依次生成 OuterMessage.cs 、OuterOpcode.cs 以及各个非 RPC 消息处理器
- 调用 ET 原生 API:call + send 实现通信,
var response = await Session.call(new SomeRPCRequest())
或者Session.Send(new NoRPCRequest())
- 非 RPC 消息的监听:使用
using static MessageHandler
即可在自己的类中通过ListenSignal
、RemoveSignal
处理感兴趣的网络消息,示例代码如下:
using ET;
using UnityEngine;
using static MessageHandler;
public class HandlerUsageCase : MonoBehaviour
{
private void Start()
{
ListenSignal<M2C_CreateMyUnit>(OnMyUnitCreated);
ListenSignal<M2C_CreateUnits>(OnUnitsCreated);
}
private void OnUnitsCreated(Session arg1, M2C_CreateUnits arg2)
{
// 撰写你自己的逻辑
}
private void OnMyUnitCreated(Session arg1, M2C_CreateMyUnit arg2)
{
// 撰写你自己的逻辑
}
private void OnDestroy()
{
RemoveSignal<M2C_CreateMyUnit>(OnMyUnitCreated);
RemoveSignal<M2C_CreateUnits>(OnUnitsCreated);
}
}
演示
演示一:消息生成,消息处理器生成
演示二:测试与 ET 服务器通信(登录网关,登录地图,Ping(心跳))
写到最后
- 为了方便的测试此 ET 网络模块,提供 ET6.0 示例 Server 编译而来的 .exe 文件,通过菜单 “Tools/Start Test Server” 开启。
- 需要留意的是,因为彻底的重构,导致了业务逻辑代码必不可能复用,所以 ET 服务器的 AI压测、AI陪玩啥的怕是用不了,这点大家应该是明了的。
- 本文配套项目地址:https://github.com/Bian-Sh/ET-Network-Module
原创作品,转载请注明出处