12 KiB
Raw Blame History

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         # 占位 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/ServerModel/Client Model 程序集只允许 Entity 派生类
Entity 的 SystemAwake/Update/Destroy Hotfix/Server(带 [EntitySystemOf] 业务逻辑层
Service / Helper / Mapper / Handler Hotfix/ServerHotfix/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. 写完 .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


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 内部。是的话直接跳过。


这次会话踩过的坑(不要再犯)

  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")]


详细参考