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