diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2abbeea..5390085 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -11,7 +11,8 @@ 2. **技术栈**: 新脚本必须使用 **TypeScript** (`.ts`)。禁止创建新的 `.js` 文件 (除非是构建脚本或测试配置)。 3. **文档**: 所有修改或创建的脚本必须包含详细的 JSDoc 格式注释。 4. **架构**: 严禁引入新的架构模式或重型外部库。必须复用现有的 Cocos Creator 管理器和工具类。 -5. **隔离原则**: 保持 `main.js` (主进程) 与 `scene-script.js` (渲染进程) 的严格职责分离。即使看似方便,也不要在 `main.js` 中直接操作场景节点对象。 +5. **隔离原则**: 保持 `main.js` (主进程) 与 `scene-script.js` (渲染进程) 的严格职责分离。即便是在主进程中,也应通过 IPC 与场景脚本交互。 +6. **主体校验规则 (Subject Validation Rule)**: 在对节点、组件或属性进行任何“写”操作之前,AI 必须先验证主体的存在性与类型正确性。严禁基于假设进行操作。 ## 1. 项目初始化 diff --git a/README.md b/README.md index 73a3868..b4e7482 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,8 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j - `id`: 节点 UUID - `x`, `y`: 坐标 - `scaleX`, `scaleY`: 缩放值 - - `color`: HEX 颜色代码(如 #FF0000)(支持撤销操作) + - `color`: HEX 颜色代码(如 #FF0000) +- **重要提示**: 执行前必须调用 `get_scene_hierarchy` 确保 `id` 有效,防止操作不存在的节点。 ### 6. open_scene @@ -149,7 +150,11 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j - `action`: 操作类型(`add`, `remove`, `get`, `update`) - `componentType`: 组件类型,如 `cc.Sprite`(用于 `add`/`update` 操作) - `componentId`: 组件 ID(用于 `remove`/`update` 操作) - - `properties`: 组件属性(用于 `add`/`update` 操作)。**智能特性**:如果属性期望组件类型但传入节点 UUID,插件会自动查找匹配组件。 + - `properties`: 组件属性(用于 `add`/`update` 操作)。 +- **智能特性**: + 1. 如果属性期望组件类型但传入节点 UUID,插件会自动查找匹配组件。 + 2. 对于资源类属性(如 `cc.Prefab`, `sp.SkeletonData`),传递资源的 UUID,插件会自动处理异步加载与序列化,确保不出现 Type Error。 +- **操作规则 (Subject Validation Rule)**:赋值或更新前必须确保目标属性在组件上真实存在。 ### 9. manage_script @@ -286,7 +291,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j ### 24. manage_vfx -- **描述**: 特效(粒子)管理 +- **描述**: 特效(粒子)管理。重要提示:操作前必须确认父节点或目标节点的有效性。 - **参数**: - `action`: 操作类型 (`create`, `update`, `get_info`) - `nodeId`: 节点 UUID (用于 `update`, `get_info`) @@ -368,6 +373,17 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j - 插件会自动标记场景为"已修改",请注意保存场景 - 不同版本的 Cocos Creator 可能会有 API 差异,请根据实际情况调整 +## AI 操作安全守则 (Subject Validation Rule) + +为了保证自动化操作的稳定性,AI 在使用本插件工具时必须遵循以下守则: + +1. **确定性优先**:任何对节点、组件、属性的操作,都必须建立在“主体已确认存在”的基础上。 +2. **校验流程**: + * **节点校验**:操作前必须使用 `get_scene_hierarchy` 确认节点。 + * **组件校验**:操作组件前必须使用 `get`(通过 `manage_components`)确认组件存在。 + * **属性校验**:更新属性前必须确认属性名准确无误。 +3. **禁止假设**:禁止盲目尝试对不存在的对象或属性进行修改。 + ## 贡献 diff --git a/main.js b/main.js index 86db7e6..487e281 100644 --- a/main.js +++ b/main.js @@ -91,15 +91,16 @@ const getNewSceneTemplate = () => { * @returns {Array} 工具定义数组 */ const getToolsList = () => { + const globalPrecautions = "【AI 安全守则】: 1. 执行任何写操作前必须先通过 get_scene_hierarchy 或 manage_components(get) 验证主体存在。 2. 严禁基于假设盲目猜测属性名。 3. 资源属性(如 cc.Prefab)必须通过 UUID 进行赋值。"; return [ { name: "get_selected_node", - description: "获取当前编辑器中选中的节点 ID。建议获取后立即调用 get_scene_hierarchy 确认该节点是否仍存在于当前场景中。", + description: `${globalPrecautions} 获取当前编辑器中选中的节点 ID。建议获取后立即调用 get_scene_hierarchy 确认该节点是否仍存在于当前场景中。`, inputSchema: { type: "object", properties: {} }, }, { name: "set_node_name", - description: "修改指定节点的名称", + description: `${globalPrecautions} 修改指定节点的名称`, inputSchema: { type: "object", properties: { @@ -111,17 +112,17 @@ const getToolsList = () => { }, { name: "save_scene", - description: "保存当前场景的修改", + description: `${globalPrecautions} 保存当前场景的修改`, inputSchema: { type: "object", properties: {} }, }, { name: "get_scene_hierarchy", - description: "获取当前场景的完整节点树结构(包括 UUID、名称和层级关系)", + description: `${globalPrecautions} 获取当前场景的完整节点树结构(包括 UUID、名称和层级关系)`, inputSchema: { type: "object", properties: {} }, }, { name: "update_node_transform", - description: "修改节点的坐标、缩放或颜色。执行前必须调用 get_scene_hierarchy 确保 node ID 有效。", + description: `${globalPrecautions} 修改节点的坐标、缩放或颜色。执行前必须调用 get_scene_hierarchy 确保 node ID 有效。`, inputSchema: { type: "object", properties: { @@ -137,7 +138,7 @@ const getToolsList = () => { }, { name: "create_scene", - description: "在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera)。", + description: `${globalPrecautions} 在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera)。`, inputSchema: { type: "object", properties: { @@ -148,7 +149,7 @@ const getToolsList = () => { }, { name: "create_prefab", - description: "将场景中的某个节点保存为预制体资源", + description: `${globalPrecautions} 将场景中的某个节点保存为预制体资源`, inputSchema: { type: "object", properties: { @@ -160,7 +161,7 @@ const getToolsList = () => { }, { name: "open_scene", - description: "打开场景文件。注意:这是一个异步且耗时的操作,打开后请等待几秒。重要:如果是新创建或空的场景,请务必先创建并初始化基础节点(Canvas/Camera)。", + description: `${globalPrecautions} 打开场景文件。注意:这是一个异步且耗时的操作,打开后请等待几秒。重要:如果是新创建或空的场景,请务必先创建并初始化基础节点(Canvas/Camera)。`, inputSchema: { type: "object", properties: { @@ -174,7 +175,7 @@ const getToolsList = () => { }, { name: "create_node", - description: "在当前场景中创建一个新节点。重要提示:1. 如果指定 parentId,必须先调用 get_scene_hierarchy 确保该父节点真实存在。2. 类型说明:'sprite' (100x100 尺寸 + 默认贴图), 'button' (150x50 尺寸 + 深色底图 + Button组件), 'label' (120x40 尺寸 + Label组件), 'empty' (纯空节点)。", + description: `${globalPrecautions} 在当前场景中创建一个新节点。重要提示:1. 如果指定 parentId,必须先通过 get_scene_hierarchy 确保该父节点真实存在且未被删除。2. 类型说明:'sprite' (100x100 尺寸 + 默认贴图), 'button' (150x50 尺寸 + 深色底图 + Button组件), 'label' (120x40 尺寸 + Label组件), 'empty' (纯空节点)。`, inputSchema: { type: "object", properties: { @@ -194,7 +195,7 @@ const getToolsList = () => { }, { name: "manage_components", - description: "管理节点组件。重要提示:1. 操作前必须调用 get_scene_hierarchy 确认 nodeId 对应的节点仍然存在。2. 添加前先用 'get' 检查是否已存在。3. 添加 cc.Sprite 后必须设置 spriteFrame 属性,否则节点不显示。4. 创建按钮时,请确保目标节点有足够的 width 和 height 作为点击区域。", + description: `${globalPrecautions} 管理节点组件。重要提示:1. 操作前必须调用 get_scene_hierarchy 确认 nodeId 对应的节点仍然存在。2. 添加前先用 'get' 检查是否已存在。3. 添加 cc.Sprite 后必须设置 spriteFrame 属性,否则节点不显示。4. 创建按钮时,请确保目标节点有足够的 width 和 height 作为点击区域。5. 赋值或更新属性前,必须确保目标属性在组件上真实存在,严禁盲目操作不存在的属性。6. 对于资源类属性(如 cc.Prefab, sp.SkeletonData),传递资源的 UUID。插件会自动进行异步加载并正确序列化,避免 Inspector 出现 Type Error。`, inputSchema: { type: "object", properties: { @@ -209,7 +210,7 @@ const getToolsList = () => { }, { name: "manage_script", - description: "管理脚本文件。注意:创建或修改脚本后,编辑器需要时间进行编译(通常几秒钟)。新脚本在编译完成前无法作为组件添加到节点。建议在 create 后调用 refresh_editor,或等待一段时间后再使用 manage_components。", + description: `${globalPrecautions} 管理脚本文件。注意:创建或修改脚本后,编辑器需要时间进行编译(通常几秒钟)。新脚本在编译完成前无法作为组件添加到节点。建议在 create 后调用 refresh_editor,或等待一段时间后再使用 manage_components。`, inputSchema: { type: "object", properties: { @@ -223,7 +224,7 @@ const getToolsList = () => { }, { name: "batch_execute", - description: "批处理执行多个操作", + description: `${globalPrecautions} 批处理执行多个操作`, inputSchema: { type: "object", properties: { @@ -245,7 +246,7 @@ const getToolsList = () => { }, { name: "manage_asset", - description: "管理资源", + description: `${globalPrecautions} 管理资源`, inputSchema: { type: "object", properties: { @@ -259,7 +260,7 @@ const getToolsList = () => { }, { name: "scene_management", - description: "场景管理", + description: `${globalPrecautions} 场景管理`, inputSchema: { type: "object", properties: { @@ -277,7 +278,7 @@ const getToolsList = () => { }, { name: "prefab_management", - description: "预制体管理", + description: `${globalPrecautions} 预制体管理`, inputSchema: { type: "object", properties: { @@ -295,7 +296,7 @@ const getToolsList = () => { }, { name: "manage_editor", - description: "管理编辑器", + description: `${globalPrecautions} 管理编辑器`, inputSchema: { type: "object", properties: { @@ -316,7 +317,7 @@ const getToolsList = () => { }, { name: "find_gameobjects", - description: "查找游戏对象", + description: `${globalPrecautions} 查找游戏对象`, inputSchema: { type: "object", properties: { @@ -328,7 +329,7 @@ const getToolsList = () => { }, { name: "manage_material", - description: "管理材质", + description: `${globalPrecautions} 管理材质`, inputSchema: { type: "object", properties: { @@ -341,7 +342,7 @@ const getToolsList = () => { }, { name: "manage_texture", - description: "管理纹理", + description: `${globalPrecautions} 管理纹理`, inputSchema: { type: "object", properties: { @@ -354,7 +355,7 @@ const getToolsList = () => { }, { name: "execute_menu_item", - description: "执行菜单项", + description: `${globalPrecautions} 执行菜单项`, inputSchema: { type: "object", properties: { @@ -365,7 +366,7 @@ const getToolsList = () => { }, { name: "apply_text_edits", - description: "应用文本编辑", + description: `${globalPrecautions} 应用文本编辑`, inputSchema: { type: "object", properties: { @@ -377,7 +378,7 @@ const getToolsList = () => { }, { name: "read_console", - description: "读取控制台", + description: `${globalPrecautions} 读取控制台`, inputSchema: { type: "object", properties: { @@ -388,7 +389,7 @@ const getToolsList = () => { }, { name: "validate_script", - description: "验证脚本", + description: `${globalPrecautions} 验证脚本`, inputSchema: { type: "object", properties: { @@ -399,7 +400,7 @@ const getToolsList = () => { }, { name: "find_in_file", - description: "在项目中全局搜索文本内容", + description: `${globalPrecautions} 在项目中全局搜索文本内容`, inputSchema: { type: "object", properties: { @@ -417,7 +418,7 @@ const getToolsList = () => { }, { name: "manage_undo", - description: "管理编辑器的撤销和重做历史", + description: `${globalPrecautions} 管理编辑器的撤销和重做历史`, inputSchema: { type: "object", properties: { @@ -433,7 +434,7 @@ const getToolsList = () => { }, { name: "manage_vfx", - description: "管理全场景特效 (粒子系统)", + description: `${globalPrecautions} 管理全场景特效 (粒子系统)。重要提示:在创建或更新前,必须通过 get_scene_hierarchy 或 manage_components 确认父节点或目标节点的有效性。严禁对不存在的对象进行操作。`, inputSchema: { type: "object", properties: { @@ -469,7 +470,7 @@ const getToolsList = () => { }, { name: "get_sha", - description: "获取指定文件的 SHA-256 哈希值", + description: `${globalPrecautions} 获取指定文件的 SHA-256 哈希值`, inputSchema: { type: "object", properties: { @@ -480,7 +481,7 @@ const getToolsList = () => { }, { name: "manage_animation", - description: "管理节点的动画组件", + description: `${globalPrecautions} 管理节点的动画组件。重要提示:在执行 play/pause 等操作前,必须先确认节点及其 Animation 组件存在。严禁操作空引用。`, inputSchema: { type: "object", properties: { @@ -1570,9 +1571,9 @@ export default class NewScript extends cc.Component { 'File/Save': 'scene:stash-and-save', // 别名 'Edit/Undo': 'scene:undo', 'Edit/Redo': 'scene:redo', - 'Edit/Delete': 'scene:delete-selected', - 'Delete': 'scene:delete-selected', - 'delete': 'scene:delete-selected', + 'Edit/Delete': 'scene:delete-nodes', + 'Delete': 'scene:delete-nodes', + 'delete': 'scene:delete-nodes', 'Node/Create Empty Node': 'scene:create-node-by-classid', // 简化的映射,通常需要参数 'Project/Build': 'app:build-project', }; @@ -1592,8 +1593,19 @@ export default class NewScript extends cc.Component { if (menuMap[menuPath]) { const ipcMsg = menuMap[menuPath]; try { - Editor.Ipc.sendToMain(ipcMsg); - callback(null, `Menu action triggered: ${menuPath} -> ${ipcMsg}`); + // 获取当前选中的节点进行删除(如果该消息是删除操作) + if (ipcMsg === 'scene:delete-nodes') { + const selection = Editor.Selection.curSelection("node"); + if (selection.length > 0) { + Editor.Ipc.sendToMain(ipcMsg, selection); + callback(null, `Menu action triggered: ${menuPath} -> ${ipcMsg} with ${selection.length} nodes`); + } else { + callback("No nodes selected for deletion"); + } + } else { + Editor.Ipc.sendToMain(ipcMsg); + callback(null, `Menu action triggered: ${menuPath} -> ${ipcMsg}`); + } } catch (err) { callback(`Failed to execute IPC ${ipcMsg}: ${err.message}`); } diff --git a/scene-script.js b/scene-script.js index da13c6d..e9e4b8c 100644 --- a/scene-script.js +++ b/scene-script.js @@ -298,71 +298,98 @@ module.exports = { if (component[key] !== undefined) { let finalValue = value; - // 【关键修复】智能组件引用赋值 - // 如果属性期望一个组件 (cc.Component子类) 但传入的是节点/UUID,尝试自动获取组件 + // 【核心逻辑】智能类型识别与赋值 try { - // 检查传入值是否是字符串 (可能是 UUID) 或 Node 对象 - let targetNode = null; - if (typeof value === 'string') { - targetNode = findNode(value); - - if (targetNode) { - Editor.log(`[scene-script] Resolved node: ${value} -> ${targetNode.name}`); - } else if (value.length > 20) { - Editor.warn(`[scene-script] Failed to resolve node: ${value}`); - } - } else if (value instanceof cc.Node) { - targetNode = value; + const attrs = (cc.Class.Attr.getClassAttrs && cc.Class.Attr.getClassAttrs(compClass)) || {}; + let propertyType = attrs[key] ? attrs[key].type : null; + if (!propertyType && attrs[key + '$_$ctor']) { + propertyType = attrs[key + '$_$ctor']; } - if (targetNode) { - // 尝试获取属性定义类型 - let typeName = null; + let isAsset = propertyType && (propertyType.prototype instanceof cc.Asset || propertyType === cc.Asset || propertyType === cc.Prefab || propertyType === cc.SpriteFrame); - // 优先尝试 getClassAttrs (Cocos 2.x editor environment) - if (cc.Class.Attr.getClassAttrs) { - const attrs = cc.Class.Attr.getClassAttrs(compClass); - // attrs 是整个属性字典 { name: { type: ... } } - if (attrs) { - if (attrs[key] && attrs[key].type) { - typeName = attrs[key].type; - } else if (attrs[key + '$_$ctor']) { - // 编辑器环境下,自定义组件类型可能存储在 $_$ctor 后缀中 - typeName = attrs[key + '$_$ctor']; + // 启发式:如果属性名包含 prefab/sprite/texture 等,且值为 UUID 且不是节点 + if (!isAsset && typeof value === 'string' && value.length > 20) { + const lowerKey = key.toLowerCase(); + const assetKeywords = ['prefab', 'sprite', 'texture', 'material', 'skeleton', 'spine', 'atlas', 'font', 'audio', 'data']; + if (assetKeywords.some(k => lowerKey.includes(k))) { + if (!findNode(value)) { + isAsset = true; + } + } + } + + if (isAsset) { + // 1. 处理资源引用 (cc.Prefab, cc.SpriteFrame 等) + if (typeof value === 'string' && value.length > 20) { + // 在场景进程中异步加载资源,这能确保 serialization 时是正确的老格式对象 + cc.AssetLibrary.loadAsset(value, (err, asset) => { + if (!err && asset) { + component[key] = asset; + Editor.log(`[scene-script] Successfully loaded and assigned asset for ${key}: ${asset.name}`); + // 通知编辑器 UI 更新 + const compIndex = node._components.indexOf(component); + if (compIndex !== -1) { + Editor.Ipc.sendToPanel('scene', 'scene:set-property', { + id: node.uuid, + path: `_components.${compIndex}.${key}`, + type: 'Object', + value: { uuid: value }, + isSubProp: false + }); + } + Editor.Ipc.sendToMain("scene:dirty"); + } else { + Editor.warn(`[scene-script] Failed to load asset ${value} for ${key}: ${err}`); + } + }); + return; // 跳过后续的直接赋值 + } + } else if (propertyType && (propertyType.prototype instanceof cc.Component || propertyType === cc.Component || propertyType === cc.Node)) { + // 2. 处理节点或组件引用 + const targetNode = findNode(value); + if (targetNode) { + if (propertyType === cc.Node) { + finalValue = targetNode; + } else { + const targetComp = targetNode.getComponent(propertyType); + if (targetComp) { + finalValue = targetComp; + } else { + Editor.warn(`[scene-script] Component ${propertyType.name} not found on node ${targetNode.name}`); } } + Editor.log(`[scene-script] Applied Reference for ${key}: ${targetNode.name}`); + } else if (value && value.length > 20) { + // 如果明确是组件/节点类型但找不到,才报错 + Editor.warn(`[scene-script] Failed to resolve target node/comp for ${key}: ${value}`); } - // 兼容性尝试 getClassAttributes - else if (cc.Class.Attr.getClassAttributes) { - const attrs = cc.Class.Attr.getClassAttributes(compClass, key); - if (attrs && attrs.type) { - typeName = attrs.type; - } - } - - if (typeName && (typeName.prototype instanceof cc.Component || typeName === cc.Component)) { - // 这是一个组件属性 - const targetComp = targetNode.getComponent(typeName); - if (targetComp) { - finalValue = targetComp; - Editor.log(`[scene-script] Auto-resolved component ${typeName.name} from node ${targetNode.name}`); + } else { + // 3. 通用启发式 (找不到类型时的 fallback) + if (typeof value === 'string' && value.length > 20) { + const targetNode = findNode(value); + if (targetNode) { + finalValue = targetNode; + Editor.log(`[scene-script] Heuristic resolved Node for ${key}: ${targetNode.name}`); } else { - Editor.warn(`[scene-script] Component ${typeName.name} not found on node ${targetNode.name}`); - } - } else if (!typeName) { - // 无法确切知道类型,尝试常见的组件类型推断 (heuristic) - const lowerKey = key.toLowerCase(); - if (lowerKey.includes('label')) { - const l = targetNode.getComponent(cc.Label); - if (l) finalValue = l; - } else if (lowerKey.includes('sprite')) { - const s = targetNode.getComponent(cc.Sprite); - if (s) finalValue = s; + // 找不到节点且是 UUID -> 视为资源 + const compIndex = node._components.indexOf(component); + if (compIndex !== -1) { + Editor.Ipc.sendToPanel('scene', 'scene:set-property', { + id: node.uuid, + path: `_components.${compIndex}.${key}`, + type: 'Object', + value: { uuid: value }, + isSubProp: false + }); + Editor.log(`[scene-script] Heuristic resolved Asset via IPC for ${key}: ${value}`); + } + return; } } } } catch (e) { - // 忽略类型检查错误 + Editor.warn(`[scene-script] Property resolution failed for ${key}: ${e.message}`); } component[key] = finalValue; diff --git a/注意事项.md b/注意事项.md new file mode 100644 index 0000000..9f45a67 --- /dev/null +++ b/注意事项.md @@ -0,0 +1,70 @@ +# 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` 操作。 +* **核心逻辑**:该工具会同步处理节点的序列化、db 路径映射以及资源刷新(Refresh),确保预制体及其配套的 `.meta` 文件原子化生成。 + +--- + +## 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**: 对于节点或组件引用,通过 `findNode` 查找实例并直接赋值实例对象,而非 UUID 字符串。 + +--- + +## 5. AI 操作安全守则 (Subject Validation Rule) + +### 5.1 确定性优先 +* **核心法则**:任何对节点、组件、属性的操作,都必须建立在 **“主体已确认存在”** 的基础上。 +* **具体流程**: + 1. **节点校验**:在操作前必须调用 `get_scene_hierarchy` 确认 `nodeId`。 + 2. **组件校验**:在 `update` 或 `remove` 前必须调用 `manage_components(action: 'get')` 确认目标组件存在。 + 3. **属性校验**:严禁猜测属性名。在 `update` 前,应通过读取脚本定义或 `get` 返回的现有属性列表来确定准确的属性名称。 +* **禁止行为**:禁止基于假设进行盲目赋值或删除。如果发现对象不存在,应立即报错或尝试重建,而非继续尝试修改。 + +--- + +## 6. 常见资源关键字 +* **资产识别启发式**:当通过 `manage_components` 赋值时,如果属性名包含以下关键字,插件会尝试将其作为 UUID 资源处理: + `prefab`, `sprite`, `texture`, `material`, `skeleton`, `spine`, `atlas`, `font`, `audio`, `data` +* **建议**:如果资源未正确加载,请检查属性名是否包含以上关键字,或手动确认该 UUID 不属于任何节点。