12 KiB
name, description
| name | description |
|---|---|
| et-framework-dev | 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
程序集分层硬规则
每个 ET 包 cn.etetet.<name> 必须在子目录放 AssemblyReference.asmref,否则代码不会参与编译(一个隐藏到不报错的"代码静默失踪"陷阱)。
cn.etetet.mypkg/
├── Ignore.ET.MyPkg.asmdef # 占位 asmdef(IGNORE 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
Proto 工作流(不跑等于不存在)
ET 不直接编译 .proto,必须先跑生成器。
1. 命名规则(Proto2CS 用文件名解析 opcode):
<Name>_<C|S|CS>_<startOpcode>.proto
例:HeroOuter_C_3000.proto # C=客户端服务端通用,opcode 从 3000 开始递增
2. RPC 注解(缺一个就不识别为 RPC):
// 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. 写完 .proto,Unity 菜单 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
EntityRef<T> 使用规则
EntityRef<T> 是 ET 的弱引用包装结构体,编译器不会自动 unwrap。
// ❌ 编译错误 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 内部。是的话直接跳过。
这次会话踩过的坑(不要再犯)
- 新包没 asmref → 代码静默失踪。报错全在引用方报"找不到类型",而不是说"包没编译"。第一时间检查 asmref。
- Proto 写好不跑 Proto2CS → 类不存在。F6 不会触发,必须走菜单。
- Hotfix 里写
protected virtual string Module => "Net"→ ET0004 把属性也当字段。要么挪到 Model +[EnableClass],要么完全删除。 static readonly Dictionary当查找表 → Hotfix 禁。改switch表达式,或挪到 Model。StringSplitOptions.TrimEntries→ Unity 的 .NET Standard 2.1 不支持。用Split(' ', RemoveEmptyEntries).Select(x => x.Trim())。new HeroInfo { ... }→ ET0031。用HeroInfo.Create()+ 逐字段赋值。bag.ItemMap.TryGetValue(.., out EntityRef<T> u); u.Field→ CS1061。先T t = u;。- Helper 之间互调形成环 → ET0013。让低层 helper 内联自己的小逻辑,不调上层。
- 登录后 GM 面板没自定义命令 → 先查
cn.etetet.<pkg>/Scripts/HotfixView/Client/GM/*.cs是否在 HotfixView 程序集(必须有 asmref),再查[GM(EGMType.Common, ...)]标签是否正确。 - 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 — ET0001~ET0032 全规则解读 + 触发示例 + 修复模板
- assembly-layout.md — 程序集分层完整规则、asmref 模板、新建包步骤
- proto-workflow.md — Proto 命名、注解、生成、MCP 一键流程