6.0 KiB
6.0 KiB
Proto 完整工作流
1. 命名与放置
.proto 文件必须放在某个 cn.etetet.<pkg>/Proto/ 目录下,且文件名遵守:
<Module>_<Direction>_<StartOpcode>.proto
| 字段 | 取值 | 说明 |
|---|---|---|
Module |
任意(如 HeroOuter、BagOuter、LoginInner) |
模块标识 |
Direction |
C / S / CS |
C=外网消息(客户端可见),S=内网消息(仅服务端),CS=同时 |
StartOpcode |
整数 | Opcode 从这里递增分配给每条 message |
opcode 分配规范(避免冲突):
| 模块 | Outer 起始 | Inner 起始 |
|---|---|---|
| Login | 1000 | 20001 |
| Router | 1100 | - |
| StateSync | 11001 | 21001 |
| ActorLocation | - | 20100 |
| Hero | 3000 | - |
| Bag | 3100 | - |
| Gacha | 3200 | - |
| 业务新模块 | 4000+ | 30000+ |
2. 消息注解(决定生成的 C# 类继承谁)
RPC 请求 / 响应
// ResponseType R2C_SummonHero ← 必须在 message 上一行
message C2G_SummonHero // ISessionRequest
{
int32 RpcId = 1;
string reqId = 2; ← 推荐字段,前 2 个固定 RpcId + reqId
int32 configId = 3;
}
message R2C_SummonHero // ISessionResponse
{
int32 RpcId = 1;
int32 Error = 2; ← 前 3 个固定 RpcId / Error / Message
string Message = 3;
HeroInfo hero = 4;
}
生成结果继承 MessageObject, ISessionRequest 和 MessageObject, ISessionResponse,并通过 [ResponseType(nameof(R2C_SummonHero))] 自动配对。
单向通知(服务端推客户端)
message Hero_HeroInfo // IMessage
{
HeroInfo hero = 1;
}
嵌套 DTO
message HeroInfo
{
int64 heroId = 1;
int32 level = 2;
repeated SkillInfo skills = 3; // List<SkillInfo>
}
message SkillInfo
{
int32 skillId = 1;
int32 skillLevel = 2;
}
不加任何 // 注解的 message 默认继承 MessageObject,可作为 DTO 嵌入其他消息。
完整可识别的父类标记
Proto2CS 解析时,把 message X // Y 里的 Y 直接拼到生成类的继承列表。常见值:
IRequest/IResponse— 内网请求/响应ISessionRequest/ISessionResponse— 客户端 ↔ Gate 的 RPCIMessage— 单向通知IActorMessage/IActorRequest/IActorResponse— Actor 模型消息
3. 生成
Unity 菜单触发:
ET → Proto → Proto2CS
MCP 触发:
CallMcpTool execute_menu_item {"menuPath": "ET/Proto/Proto2CS"}
工具会:
- 扫描所有
cn.etetet.*/Proto/*.proto - 按文件名解析 Opcode
- 输出三份 C# 到:
cn.etetet.proto/CodeMode/Model/Client/<Name>.cscn.etetet.proto/CodeMode/Model/Server/<Name>.cscn.etetet.proto/CodeMode/Model/ClientServer/<Name>.cs
CodeMode 下只有
ClientServer/子目录有AssemblyReference.asmref(指向 ET.Model),所以在CodeMode: 3 = ClientServer模式下,只有 ClientServer 那份会被编译进 ET.Model。Client/和Server/是预留给纯客户端/纯服务端模式的。
4. 调用
客户端发 RPC
C2G_SummonHero request = C2G_SummonHero.Create();
request.configId = 1;
R2C_SummonHero response = await clientScene
.GetComponent<ClientSenderComponent>()
.Call(request, false) as R2C_SummonHero;
if (response.Error != 0)
{
Log.Error(response.Message);
}
服务端 RPC Handler
// Scripts/Hotfix/Server/C2G_SummonHeroHandler.cs
[MessageSessionHandler(SceneType.Gate)]
public class C2G_SummonHeroHandler : RpcMessageHandler<C2G_SummonHero, R2C_SummonHero>
{
protected override async ETTask OnRun(
Session session, C2G_SummonHero request, R2C_SummonHero response)
{
Player player = session.GetComponent<SessionPlayerComponent>()?.Player;
if (player == null) {
response.Error = ErrorCode.ERR_RpcFail;
return;
}
// 业务...
await ETTask.CompletedTask;
}
}
注意:
- 不要写
protected override string Module => "X"这种属性,ET0004 会报。 - 文件放
Scripts/Hotfix/Server/。
服务端推单向通知
Hero_HeroInfo notice = Hero_HeroInfo.Create();
notice.hero = HeroMapper.ToProto(hero);
session.Send(notice);
客户端通知 Handler
// Scripts/Hotfix/Client/Hero_HeroInfoHandler.cs
[MessageHandler(SceneType.Main)]
[FriendOf(typeof(HeroComponent))]
public class Hero_HeroInfoHandler : MessageHandler<Scene, Hero_HeroInfo>
{
protected override async ETTask Run(Scene scene, Hero_HeroInfo message)
{
var comp = scene.GetComponent<HeroComponent>() ?? scene.AddComponent<HeroComponent>();
comp.HeroMap[message.hero.heroId] = message.hero;
EventSystem.Instance.Publish(scene, new HeroUpdatedEvent { HeroId = message.hero.heroId });
await ETTask.CompletedTask;
}
}
5. 常见 Proto 坑
- 写完 .proto 不跑 Proto2CS → 引用方报"找不到类型":必须先生成。
- 缺
// ISessionRequest:消息生成出来不继承ISessionRequest,handler 的MessageSessionHandler<TReq, TResp>类型约束失败 → 编译报错。 - 缺
// ResponseType R2C_X:客户端await session.Call(req)无法推断响应类型。 - 文件名 opcode 重复:和别的 proto 撞 opcode → 运行时崩。规范分配规则见上面表格。
- 手写
new HeroInfo {...}→ ET0031。改HeroInfo.Create()。 - 修改 .proto 字段顺序 / 类型 → MemoryPack 序列化兼容性问题。线上版本要么不改,要么走版本兼容方案。
6. 一键自检 checklist
写完一个新 .proto 后:
- [ ] 文件名格式 <Module>_<Direction>_<Opcode>.proto
- [ ] Opcode 不与其他 .proto 冲突(grep "_C_<同段号>_")
- [ ] RPC 消息有 // ResponseType
- [ ] 请求带 // ISessionRequest 或 // IRequest
- [ ] 响应带 // ISessionResponse 或 // IResponse
- [ ] 通知带 // IMessage
- [ ] 跑了 ET/Proto/Proto2CS
- [ ] 检查 cn.etetet.proto/CodeMode/Model/ClientServer/<Name>.cs 已生成
- [ ] Assets/Refresh 让 Unity 索引到新文件
- [ ] recompile_scripts 验证 0 错误