Files
mcp-bridge/docs/注意事项.md

199 lines
14 KiB
Markdown
Raw Permalink 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.
# Cocos Creator 插件开发注意事项
## 1. 资源与预制体管理
### 1.1 禁止手动干预 `.meta` 文件
- **问题**:手动使用 `manage_asset` 创建或修改以 `.meta` 结尾的文件会导致编辑器报错(如 `Invalid assetpath, must not use .meta as suffix`)。
- **原因**Cocos Creator 资源数据库会自动识别新资源并生成对应的 `.meta` 文件。外部强行介入会破坏资源索引一致性。
- **最佳实践**:始终使用高级资源工具(如 `prefab_management`)来处理资源,让编辑器自行管理 `.meta` 文件。
### 1.2 预制体的正确创建流程
- **推荐工具**:使用 `prefab_management``create` 操作,或 `create_prefab` 工具。
- **核心逻辑**:该工具已完全绕过 Cocos Creator 内置的 `scene:create-prefab` IPC该接口存在根节点 PrefabInfo 损坏等已知 Bug改为在场景进程中使用 `Editor.serialize(node)` 获取原始数据后,通过自定义 9 步后处理管线转换为标准预制体格式。
- **⚠️ 序列化格式要点**:正确的预制体文件必须满足以下结构:
1. 数组索引 0 必须是 `cc.Prefab` 包装器对象,其 `data` 字段指向根节点。
2. 根节点的 `_parent` 必须为 `null`(不能指向 `cc.Scene`)。
3. 每个 `cc.Node` 必须有 `_prefab` 引用指向对应的 `cc.PrefabInfo` 对象。
4. 每个 `cc.PrefabInfo` 必须包含 `root`(指向根节点)、`asset`(指向索引 0 的 `cc.Prefab`)和唯一的 `fileId`
5. 所有节点和组件的 `_id` 字段必须为空字符串(运行时由引擎分配)。
6. 文件中不能包含 `cc.Scene` 对象。
### 1.3 预制体界面的打开与保存
- **推荐工具**:使用 `open_prefab` `save_prefab`,和 `close_prefab` 工具。
- **痛点**:直觉上 AI 可能会推断应该通过 `scene:save-prefab``scene:close-prefab` 这样的 IPC 指令来实现“保存”与“退出预制体视图”,但实际上这类消息在引擎内部并不存在。
- **正规实现(封装在工具内)**
必须在负责渲染场景面板的渲染进程Scene Process直接 `require("scene://edit-mode")`,然后对当前的状态机进行控制。
- 保存:`require("scene://edit-mode").save()`
- 退出:`require("scene://edit-mode").pop()`
这彻底解决了进入预制体模式后出不去或者没法存盘的痛点。AI 在使用 `save_prefab``close_prefab` 工具时就是在触发这两行代码。
---
## 2. 脚本属性Inspector关联
### 2.1 路径与 UUID 的区别
- **问题**:在 Inspector 面板中,脚本属性(如 `cc.Prefab``cc.Node`)若通过路径关联,经常会出现 "Type Error"。
- **原因**:编辑器 Inspector 期望获得的是引擎内部的对象引用。虽然 `db://` 路径可以指向文件,但在属性赋值层面,编辑器无法自动将字符串路径转换为对应的类实例。
- **最佳实践**
1. 通过 `manage_asset -> get_info` 获取资源的 **UUID**
2. 在调用 `manage_components -> update` 时,**优先使用 UUID** 进行赋值。
3. UUID 是底层唯一标识,能确保编辑器精准识别资源类型并正确完成引用链接。
---
## 3. 场景同步与编辑器状态
### 3.1 刷新与编译
- **注意事项**:创建新脚本后,必须调用 `manage_editor -> refresh_editor` 或等待几秒钟以触发编译。
- **风险**:在脚本编译完成前尝试将其作为组件添加到节点,会导致 `添加组件失败: Cannot read property 'constructor' of null` 或找不到脚本组件的问题。
### 3.2 节点删除 (IPC 协议)
- **正确协议**:应使用 `Editor.Ipc.sendToMain('scene:delete-nodes', uuid_or_array)`
- **注意**:不要误用 `scene:delete-selected`,因为它在某些版本的编辑器底层不接受参数或行为不一致。
- **技巧**:在 `mcp-bridge` 中,调用 `execute_menu_item("delete-node:NODE_UUID")` 会走场景脚本的直连删除,而 `execute_menu_item("Delete")` 则会走主进程的 `scene:delete-nodes` 并自动带上选中的节点。
---
## 4. MCP Bridge 源码补丁说明
### 4.1 属性解析增强 (Asset 序列化修复)
- **改进点**:在 `scene-script.js``applyProperties` 中通过 `cc.AssetLibrary.loadAsset` 解决了资源属性在 Inspector 报错 "Type Error" 的问题。
- **原理**
- **问题根源**:在场景进程中直接将 UUID 字符串赋给资源属性(如 `comp.prefab = "uuid"`),会导致 `.fire` 文件将其保存为纯字符串而非对象格式。编辑器 Inspector 期望的是资源引用结构 `{ "__uuid__": "..." }`,类型不匹配产生 Type Error。
- **修复逻辑**
1. **真实加载**:使用 `cc.AssetLibrary.loadAsset(uuid, callback)` 在场景进程中异步加载真实的资源实例。
2. **实例赋值**:在回调中将加载到的 `asset` 对象赋予组件(`component[key] = asset`),这确保了场景保存时能生成正确的序列化对象。
3. **UI 同步**:同步发送 IPC `scene:set-property`,使用 `{ uuid: value }` 格式通知编辑器面板更新 Inspector UI。
- **Node/Component**: 对于节点 or 组件引用,通过 `findNode` 查找实例并直接赋值实例对象,而非 UUID 字符串。
---
## 5. AI 操作安全守则 (Subject Validation Rule)
### 5.1 确定性优先
- **核心法则**:任何对节点、组件、属性的操作,都必须建立在 **“主体已确认存在”** 的基础上。
- **具体流程**
1. **节点校验**:在操作前必须调用 `get_scene_hierarchy` 确认 `nodeId`
2. **组件校验**:在 `update``remove` 前必须调用 `manage_components(action: 'get')` 确认目标组件存在。
3. **属性校验**:严禁猜测属性名。在 `update` 前,应通过读取脚本定义或 `get` 返回的现有属性列表来确定准确的属性名称。
- **禁止行为**:禁止基于假设进行盲目赋值或删除。如果发现对象不存在,应立即报错或尝试重建,而非继续尝试修改。
### 5.2 传参精准与别名容错
- **传参风险**:大语言模型在生成 JSON 时可能会出现操作类型字段名“幻觉”(例如在调用 `manage_components` 时将本应是 `action` 的参数写为含义相近的 `operation`)。
- **优化机制**:底层脚本 (`scene-script.js`) 已经全面引入参数别名回落机制(如 `action = action || operation`)。
- **提示开发**:尽管底层具备一定的容错率,在维护 MCP 工具说明书Schema仍应严格要求 AI 书写标准参数名,避免纵容产生更大的幻觉偏移。
---
## 6. 常见资源关键字
- **资产识别启发式**:当通过 `manage_components` 赋值时,如果属性名包含以下关键字,插件会尝试将其作为 UUID 资源处理:
`prefab`, `sprite`, `texture`, `material`, `skeleton`, `spine`, `atlas`, `font`, `audio`, `data`
- **建议**:如果资源未正确加载,请检查属性名是否包含以上关键字,或手动确认该 UUID 不属于任何节点。
---
## 7. 搜索工具 (search_project) 使用建议
- **性能建议**:尽量指定 `path` 参数缩小搜索范围(如 `db://assets/scripts`),避免全项目大面积搜索,尤其是在包含大量旧资源的 Library 目录(虽然插件已过滤)。
- **正则表达式**:在使用 `useRegex` 时,确保正则模式的语法正确。如果正则匹配失败,工具会返回详细的错误提示。
- **模式选择**
- 查找具体逻辑代码:使用 `matchType: "content"`
- 定位资源文件:使用 `matchType: "file_name"` 并配合 `extensions` 过滤。
- 重构目录结构前:使用 `matchType: "dir_name"` 检查目录名冲突。
---
## 8. Undo/Redo (撤销/重做) 使用指南
### 8.1 事务分组
- **背景**:连续执行多次修改(如同时移动并缩放)时,通常希望一次“撤销”能回滚所有更改。
- **最佳实践**
1. 调用 `manage_undo(action: 'begin_group', description: '操作描述')`
2. 执行多次修改(如调用 `update_node_transform`)。
3. 调用 `manage_undo(action: 'end_group')`
- **注意**`undo-record` 需要在 `begin_group` 时明确关联节点 ID否则可能导致撤销栈无法精准匹配对象。
### 8.2 属性修改方式
- **`update-node-transform` 的做法**:出于解决异步 IPC 竞态条件的考量,`update-node-transform` 中所有 13 个属性x, y, rotation, width, height, scaleX, scaleY, anchorX, anchorY, color, opacity, skewX, skewY均使用**直接赋值**(如 `node.x = 100`)。直接赋值是同步操作,确保属性在函数返回前已生效,但**不支持 Undo**。
- **需要 Undo 的场景**:如果业务要求支持撤销,应改用 `Editor.Ipc.sendToPanel('scene', 'scene:set-property', ...)`,但需注意异步竞态风险。
- **`get_scene_hierarchy``includeDetails`**:现在返回与编辑器属性面板完全一致的节点数据,包括 position, rotation, scale, anchor, size, color, opacity, skew, group。
---
## 9. 并发安全与防卡死机制
### 9.1 指令队列 (CommandQueue)
- **背景**AI 客户端可能在短时间内连续发送多个 MCP 请求(如 `delete-node``refresh_editor``search_project`),如果并发执行,`AssetDB.refresh()` 等异步重操作会与后续请求产生 I/O 和 IPC 通道冲突,导致编辑器主线程阻塞。
- **解决方案**:在 `/call-tool` HTTP 入口处引入 `enqueueCommand` 机制,将所有 MCP 工具调用**串行化**执行。前一个指令的回调完成后,才会出队并处理下一个指令。
- **注意事项**
1. 队列在 `processNextCommand``catch` 块中有防死锁保护,即使某个指令抛出异常也不会永久阻塞后续指令。
2. `batchExecute` 内部也已从并行 `forEach` 改为串行链式执行。
3. 队列长度会在日志中显示(`REQ -> [toolName] (队列长度: N)`),可用于排查积压问题。
4. **队列长度限制**:队列上限为 100 条。超限时新请求会被直接拒绝并返回 HTTP 429 状态码,防止极端情况下内存无限增长。
### 9.2 `refresh_editor` 路径要求
- **⚠️ 必须带路径**:在调用 `manage_editor``refresh_editor` 时,务必通过 `properties.path` 指定精确的文件或目录(如 `db://assets/scripts/Test.ts`)。严禁空参数进行全局刷新,在生产项目中会导致数分钟的编辑器卡死。
### 9.3 IPC 超时保护 (callSceneScriptWithTimeout)
- **背景**`Editor.Scene.callSceneScript` 的回调依赖 Scene 面板响应 IPC 消息。如果主线程阻塞Scene 面板无法处理消息,导致 callback 永远不返回HTTP 连接堆积。
- **解决方案**:所有 `callSceneScript` 调用均通过 `callSceneScriptWithTimeout` 包装,默认 15 秒超时。超时后自动返回错误,释放 HTTP 连接和队列位置。
- **日志标识**:超时会记录 `[超时] callSceneScript "方法名" 超过 15000ms 未响应`
---
## 10. Token 消耗与长数据保护防爆机制
### 10.1 `get_scene_hierarchy` 深度与层级限制
- **背景**:在一两千个节点的大型 UI 场景中,无限制地获取全场景树会瞬间消耗十万以上的 Token导致 AI 丢失上下文甚至触发截断报错。
- **最佳实践**
- **默认使用 `depth: 2`** (默认限制) 来逐步探查。
- **结合 `nodeId` 参数**:找到关键模块(例如 `Canvas/LoginPanel`)的 UUID 后,再单独向该 `nodeId` 请求下一层的结构,而非每次从根部拉取。
- **子节点数量上限**:每层最多返回 50 个子节点。如果某节点的子节点数超过 50返回数据中会包含 `childrenTruncated` 字段标注被截断的数量。此时可通过 `nodeId` 参数指定该节点单独获取完整子节点列表。
### 10.2 大对象与长数组截断
- **背景**在读取某些特定组件数据如多边形顶点坐标、Sprite 曲线数据或序列化的内联 Base64 图片JSON 可能会异常庞大。
- **保护机制**
- `scene-script.js` 内部在执行 `manage_components(get)` 序列化时,对于**长度超过 10 的 Array** 会强制截断,返回字面量字符串 `"[Array(X)]"`
- 对于**长度大于 200 的长字符串**,也会强制缩略并追加 `...[Truncated, total length: X]`
- **应对策略**:如果 AI 看到截断提示,这意味着此处为海量无语义数据,**请勿**尝试盲目通过 `update` 覆盖或还原被截断的字段,极易导致源数据被破坏。请仅修改自己能够完全看清的轻量级属性(如 `name`, `x`, `scale` 等)。
---
## 11. 引用查找 (`find_references`) 与 UUID 自动解析
### 11.1 引用查找工具用途
- **功能**: `find_references` 工具可查找当前场景中所有引用了指定节点或资源的位置,返回引用所在节点、组件类型、属性名等详细信息。
- **应用场景**:
- 删除节点或预制体前,检查是否有组件属性引用了它,防止脚本报错。
- 替换资源(如更换 SpriteFrame快速定位所有使用了旧资源的组件。
- 分析场景依赖关系,辅助重构。
### 11.2 UUID 格式差异与自动规范化
- **背景**: Cocos Creator 2.x 中 `cc.Asset._uuid` 使用 22 位压缩 UUID 格式,而 `Editor.assetdb` 返回标准 36 位带连字符 UUID同一资源的两种表示在字符串层面不相等。
- **机制**: `find-references` 处理函数通过 `Editor.Utils.UuidUtils.compressUuid/decompressUuid` 自动预计算所有格式变体,存入 `targetVariants` 数组进行全量匹配。无需人工关心 UUID 格式。
### 11.3 Texture2D 与 SpriteFrame 子资源解析
- **问题**: AI 传入图片 (Texture2D) 的 UUID 时,`cc.Sprite.spriteFrame` 实际引用的是该图片的子资源 SpriteFrame具有不同的 UUID导致直接查找返回空结果。
- **解决方案**: `main.js` 在调用 scene-script 前,自动读取目标 UUID 对应的 `.meta` 文件,提取 `subMetas` 中所有子资源 UUID作为 `additionalIds` 一并传递。scene-script 对这些额外 UUID 同样执行压缩/解压规范化后加入匹配列表。
- **效果**: 无论传入 Texture2D UUID 还是 SpriteFrame UUID`find_references` 均能正确返回所有引用位置。