275 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: et-framework-dev
description: >-
Develop ET-Framework + YIUI + Luban + Proto + HybridCLR business code in the
Survivors Unity project. Use when adding/editing any code under
`My project/Packages/cn.etetet.*`, creating new packages, adding RPC/Proto
messages, defining Entity/Component, writing GM commands, or touching
Hotfix/HotfixView/Model/ModelView assemblies. Enforces the ET source
generator rules (ET0001~ET0032), assembly-layout (asmref) discipline, Proto
workflow, EntityRef usage, and the MCP-Unity debug loop.
---
# ET Framework Development — Survivors Project
ET 的 Roslyn 分析器和程序集结构非常严格,**违反规则会一次性触发几十个编译错误**。先按下面的"动手前必查"走一遍,再写代码。
---
## 动手前必查(每次都先做这 5 件事)
```
- [ ] 1. 看包是否激活:包目录下 Scripts/**/AssemblyReference.asmref 必须齐全,
Ignore.*.asmdef 必须保持 IGNORE constraint否则会顶替 asmref 失效)。
- [ ] 2. 看 Proto 是否生成cn.etetet.proto/CodeMode/Model/ClientServer/<Name>.cs 是否存在;
缺则跑 ET/Proto/Proto2CS 菜单。
- [ ] 3. 看每个新类放到了正确程序集层:
- 静态工具 / 占位 DTO → Model + [EnableClass]
- Entity/Component → Model/Server 或 Model/Client
- System / Service / Handler / Helper业务→ Hotfix
- UI 数据绑定 / 工具 → ModelView + [EnableClass]
- YIUI 事件 / GM 命令 → HotfixView
- [ ] 4. 看是否有静态字段Hotfix/HotfixView 程序集禁止任何非 const 字段和属性。
- [ ] 5. 看静态工具类是否互相调用BagNoticeHelper ↔ BagComponentSystem 这种环
会触发 ET0013要拆成单向依赖。
```
把这 5 条贴到 todo 里,写代码前一项一项打勾。
---
## ET 分析器铁律(高频踩坑顺序)
| ID | 规则 | 怎么改 |
|---|---|---|
| **ET0001** | `AddChild<T>` 的 T 必须在 `[ChildOf]` 声明里 | Entity 类加 `[ChildOf(typeof(ParentEntity))]` |
| **ET0002** | 访问别人的 Entity 字段需 `[FriendOf(typeof(T))]` | 在调用方类上加 `[FriendOf(typeof(Hero))]` 等 |
| **ET0003** | Entity 子类不能写构造函数 / 不能 `new` | 用 `AddChild`/`AddComponent` 创建 |
| **ET0004** | **Hotfix/HotfixView 程序集禁止非 const 字段和属性**(包括 `=> "..."` 的 expression-bodied property| 字段挪到 Model 程序集 + `[EnableClass]`;属性删掉,用 `typeof(T).Name` 之类直接 inline |
| **ET0013** | 静态类间环依赖 | 让一边 inline 内嵌另一边的小逻辑,打破环 |
| **ET0015** | static 字段必须 `[StaticField]` | 加 attribute但若同时在 Hotfix 会与 ET0004 冲突 → 必须挪走 |
| **ET0024/0025** | `EntitySystem` 必须在 `[EntitySystemOf]` 标记的 partial 类里 | `[EntitySystemOf(typeof(Hero))] partial class HeroSystem` |
| **ET0030** | NetMessage 必须从 Proto 生成 | 不要手写 Message 类 |
| **ET0031** | 禁止 `new XxxMessage()`Proto 类) | 用 `XxxMessage.Create()` 工厂方法 |
| **ET0032** | Model/ModelView 程序集禁止非 Entity 类(除非加 `[EnableClass]` | 静态工具类、占位 DTO 加 `[EnableClass]` |
完整规则解读 → 见 [analyzer-rules.md](analyzer-rules.md)
---
## 程序集分层硬规则
每个 ET 包 `cn.etetet.<name>` **必须**在子目录放 `AssemblyReference.asmref`,否则代码不会参与编译(一个隐藏到不报错的"代码静默失踪"陷阱)。
```
cn.etetet.mypkg/
├── Ignore.ET.MyPkg.asmdef # 占位 asmdefIGNORE constraint不参与编译
├── Proto/ # 仅放 .proto由 Proto2CS 产 cs 到 cn.etetet.proto
├── Scripts/
│ ├── Model/
│ │ ├── Share/AssemblyReference.asmref → ET.Model
│ │ ├── Client/AssemblyReference.asmref → ET.Model
│ │ └── Server/AssemblyReference.asmref → ET.Model
│ ├── Hotfix/
│ │ ├── Share/AssemblyReference.asmref → ET.Hotfix
│ │ ├── Client/AssemblyReference.asmref → ET.Hotfix
│ │ └── Server/AssemblyReference.asmref → ET.Hotfix
│ ├── ModelView/Client/AssemblyReference.asmref → ET.ModelView
│ └── HotfixView/Client/AssemblyReference.asmref → ET.HotfixView
```
`AssemblyReference.asmref` 内容固定为 `{ "reference": "ET.<ProgramAssembly>" }`
**临时屏蔽整个包**:把该包 `Scripts/**/AssemblyReference.asmref`(含 .meta全删保留 `Ignore.*.asmdef`。本次会话就用这招屏蔽了 `cn.etetet.gacha`
**判断要放哪一层**(最常错的决策):
| 代码类型 | 放哪 | 原因 |
|---|---|---|
| Entity / Component 定义 | `Model/Server``Model/Client` | Model 程序集只允许 Entity 派生类 |
| Entity 的 `System`Awake/Update/Destroy| `Hotfix/Server`(带 `[EntitySystemOf]`| 业务逻辑层 |
| Service / Helper / Mapper / Handler | `Hotfix/Server``Hotfix/Client` | 业务工具 |
| Proto 消息类 | **不要自己写**,跑 Proto2CS | 自动生成到 `cn.etetet.proto/CodeMode/` |
| 普通占位 DTO / Luban 生成类 / Lock 对象 | `Model/Share` + `[EnableClass]` | 需要持有静态状态时必须在 Model |
| YIUI 视图组件 / UIBindCDETable 派生 | `ModelView/Client` | YIUI 框架约定 |
| YIUI 事件 handler / GM 命令 / 客户端 RPC handler | `HotfixView/Client` | 视图层业务 |
更多分层细节 → 见 [assembly-layout.md](assembly-layout.md)
---
## Proto 工作流(不跑等于不存在)
ET 不直接编译 `.proto`,必须先跑生成器。
**1. 命名规则**Proto2CS 用文件名解析 opcode
```
<Name>_<C|S|CS>_<startOpcode>.proto
HeroOuter_C_3000.proto # C=客户端服务端通用opcode 从 3000 开始递增
```
**2. RPC 注解**(缺一个就不识别为 RPC
```proto
// ResponseType R2C_SummonHero
message C2G_SummonHero // ISessionRequest
{
int32 RpcId = 1;
string reqId = 2;
int32 configId = 3;
}
message R2C_SummonHero // ISessionResponse
{
int32 RpcId = 1;
int32 Error = 2;
string Message = 3;
}
// 非 RPC 单向通知用这个
message Hero_HeroInfo // IMessage
{
HeroInfo hero = 1;
}
```
**3. 生成步骤**
```
1. 写完 .protoUnity 菜单 ET → Proto → Proto2CS
2. 生成结果在 cn.etetet.proto/CodeMode/Model/{Client,Server,ClientServer}/<Name>.cs
3. Assets → Refresh让 Unity 索引到新文件
4. recompile_scripts 或 F6
```
**4. 用 MCP 一键触发**
```
execute_menu_item({"menuPath": "ET/Proto/Proto2CS"})
execute_menu_item({"menuPath": "Assets/Refresh"})
recompile_scripts({"waitForCompletion": true})
```
更多 → 见 [proto-workflow.md](proto-workflow.md)
---
## EntityRef\<T\> 使用规则
`EntityRef<T>` 是 ET 的弱引用包装结构体,**编译器不会自动 unwrap**。
```csharp
// ❌ 编译错误 CS1061
if (bag.ItemMap.TryGetValue(idx, out EntityRef<ItemUnit> unit))
{
unit.ItemId // EntityRef<ItemUnit> 没有 ItemId 字段
unit.Dispose() // 也没有 Dispose
}
// ✅ 用隐式转换拆包
if (bag.ItemMap.TryGetValue(idx, out EntityRef<ItemUnit> unitRef))
{
ItemUnit unit = unitRef; // 触发 implicit operator T(EntityRef<T> v)
unit.ItemId
unit.Dispose()
}
// ✅ Linq 场景
self.ItemMap.Values
.Select(x => (ItemUnit)x) // 显式 cast 也会调 implicit operator
.Where(x => x.ItemId == 1)
```
---
## MCP-Unity 调试循环
调试代码不要手动开 Unity 看日志,用 MCP 闭环。
```
1. 改完代码 → CallMcpTool execute_menu_item Assets/Refresh
2. CallMcpTool recompile_scripts {"waitForCompletion": true}
3. 若 errors > 0 → 看 logs 列表,分类批量修
4. errors == 0 → CallMcpTool execute_menu_item "Tools/Survivors/Open Init Scene And Play"
5. CallMcpTool get_console_logs {"types": ["error", "warning"], "count": 50}
```
**MCP 连接失败时**:不要立刻重试,先确认 Unity 编辑器没卡在编译/导入(往往是上一次 Refresh 还在跑)。等 5-10s 再发请求。
---
## 新建一个业务包的标准流程
参考 cn.etetet.hero / cn.etetet.bag 的结构。**写代码前先把骨架建好**。
```
Task Progress:
- [ ] 1. 拷贝 cn.etetet.statesync 作为模板,改包名
- [ ] 2. 删掉 Runtime/Model 下的 ET.Model.asmdef保留 Ignore.*.asmdef 即可)
- [ ] 3. 保留 / 重建以下 7 个 AssemblyReference.asmref
Scripts/Model/{Share,Client,Server}, Scripts/Hotfix/{Share,Client,Server},
Scripts/ModelView/Client, Scripts/HotfixView/Client按需
- [ ] 4. package.json dependencies 加 cn.etetet.core / login / logging / console / bag 等
- [ ] 5. Proto/<Pkg>_C_<opcode>.proto 写消息
- [ ] 6. ET → Proto → Proto2CS
- [ ] 7. Model 层写 Entity配 [ChildOf]/[ComponentOf]
- [ ] 8. Hotfix 层写 System[EntitySystemOf]/ Service / Handler
- [ ] 9. recompile_scripts 验证 0 错误
- [ ] 10. 写 GM 命令到 HotfixView/Client/GM 验证业务
```
---
## 已知 demo 噪音(看到时直接忽略,不要去修)
当前 `GlobalConfig.SceneName = StateSync`,走 `cn.etetet.statesync` + `cn.etetet.yiuistatesync` 这两个**仅参考** demo 包的流程。这两个错每次都会出现,**不影响业务**
| 错误关键字 | 触发时机 | 来源 |
|---|---|---|
| `actor id is 0` @ `MessageLocationSenderComponentSystem.cs:67` | 走 demo 大厅 → 进入地图 (EnterMapAsync) | statesync M2M 跨地图转移弱实现 |
| `cant set parent because parent iSence is null: CoroutineLockComponent` | 按停止键退出 Play | demo `LobbyPanelComponentSystem.OnEventEnterMapInvoke` 在 OnApplicationQuit 后还在 await CloseAsync |
排查规则:先看错误栈是否在 `cn.etetet.statesync` / `cn.etetet.yiuistatesync` 内部。是的话直接跳过。
---
## 这次会话踩过的坑(不要再犯)
1. **新包没 asmref → 代码静默失踪**。报错全在引用方报"找不到类型",而不是说"包没编译"。第一时间检查 asmref。
2. **Proto 写好不跑 Proto2CS → 类不存在**。F6 不会触发,必须走菜单。
3. **Hotfix 里写 `protected virtual string Module => "Net"`** → ET0004 把属性也当字段。要么挪到 Model + `[EnableClass]`,要么完全删除。
4. **`static readonly Dictionary` 当查找表** → Hotfix 禁。改 `switch` 表达式,或挪到 Model。
5. **`StringSplitOptions.TrimEntries`** → Unity 的 .NET Standard 2.1 不支持。用 `Split(' ', RemoveEmptyEntries).Select(x => x.Trim())`
6. **`new HeroInfo { ... }`** → ET0031。用 `HeroInfo.Create()` + 逐字段赋值。
7. **`bag.ItemMap.TryGetValue(.., out EntityRef<T> u); u.Field`** → CS1061。先 `T t = u;`
8. **Helper 之间互调形成环** → ET0013。让低层 helper 内联自己的小逻辑,不调上层。
9. **登录后 GM 面板没自定义命令** → 先查 `cn.etetet.<pkg>/Scripts/HotfixView/Client/GM/*.cs` 是否在 HotfixView 程序集(必须有 asmref再查 `[GM(EGMType.Common, ...)]` 标签是否正确。
10. **MCP 调用超时** → Unity 还在 Import/Compile先等 5-10s。
---
## 链路速记(启动到 GM 出现)
```
Init.unity (cn.etetet.loader)
→ Entry.Start (cn.etetet.core)
→ FiberInit_Main (SceneType 由 GlobalConfig.SceneName 决定)
→ EntryEvent1/2/3
→ EntryEvent3_InitClient (cn.etetet.yiuistatesync, SceneType.StateSync)
→ YIUIMgrComponent.Initialize()
→ YIUIEventInitializeAfter
→ YIUIEventInitializeAfterGMHandler (cn.etetet.yiuigm)
→ GMCommandComponent.Awake → 扫 GMAttribute → 显示在 GM 面板
```
要让自定义 GM 出现cs 文件**必须**位于 `<pkg>/Scripts/HotfixView/Client/...`HotfixView 程序集),并标 `[GM(EGMType.XXX, 1, "标题", "tooltip")]`
---
## 详细参考
- [analyzer-rules.md](analyzer-rules.md) — ET0001~ET0032 全规则解读 + 触发示例 + 修复模板
- [assembly-layout.md](assembly-layout.md) — 程序集分层完整规则、asmref 模板、新建包步骤
- [proto-workflow.md](proto-workflow.md) — Proto 命名、注解、生成、MCP 一键流程