From c274bd9db49e8a0c635f50d6145a9dc9be073d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=AB=E7=84=B0=E5=BA=93=E6=8B=89?= Date: Sat, 7 Feb 2026 22:29:17 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=A0=B9=E6=8D=AE=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=A7=84=E5=88=99=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3=E5=92=8C?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=EF=BC=8C=E5=AE=8C=E5=96=84=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E5=8F=8A=E6=9C=80=E4=BD=B3=E5=AE=9E=E8=B7=B5?= =?UTF-8?q?=E5=BC=95=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVELOPMENT.md | 7 ++-- README.md | 16 ++++---- main.js | 28 +++++++------- scene-script.js | 100 +++++++++++++++++++++++++++++++++--------------- 4 files changed, 96 insertions(+), 55 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 98fcde5..2abbeea 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -449,9 +449,9 @@ manageAsset(args, callback) { | 任务 | 状态 | 描述 | |------|------|------| -| 全局文件搜索 | ✅ 完成 | 实现 find_in_file 工具 | -| 撤销/重做支持 | ✅ 完成 | 实现 manage_undo 工具,并重构核心操作支持撤销 | | 特效管理 | ✅ 完成 | 实现 manage_vfx 工具,支持粒子系统管理 | +| 文件哈希 | ✅ 完成 | 实现 get_sha 工具,支持文件 SHA-256 计算 | +| 动画管理 | ✅ 完成 | 实现 manage_animation 工具,支持动画播放与控制 | ### 11.4 第六阶段:可靠性与体验优化(已完成) @@ -490,7 +490,8 @@ manageAsset(args, callback) { | 脚本验证 | validate_script | ✅ 已实现 | | | 撤销/重做 | undo/redo | ✅ 已实现 | | | VFX 管理 | manage_vfx | ✅ 已实现 | | -| Git 集成 | get_sha | ❌ 未实现 | 低优先级 | +| Git 集成 | get_sha | ✅ 已实现 | 虽然优先级中等,但已根据需求完成 | +| 动画管理 | manage_animation | ✅ 已实现 | 支持播放、暂停、停止及信息获取 | | ScriptableObject | manage_so | ❌ 未实现 | 使用 AssetDB 替代 | ## 13. 风险评估 diff --git a/README.md b/README.md index f22d757..af21e33 100644 --- a/README.md +++ b/README.md @@ -117,13 +117,13 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j ### 6. open_scene -- **描述**: 在编辑器中打开指定的场景文件 +- **描述**: 在编辑器中打开指定的场景文件。这是一个异步且耗时的操作,打开后请等待几秒。**重要提示**:如果是新创建或空的场景,请务必先创建并初始化基础节点(Canvas/Camera)。 - **参数**: - `url`: 场景资源路径,如 `db://assets/NewScene.fire` ### 7. create_node -- **描述**: 在当前场景中创建一个新节点 +- **描述**: 在当前场景中创建一个新节点。对于 Canvas/Label 类型,会自动添加对应组件。 - **参数**: - `name`: 节点名称 - `parentId`: 父节点 UUID (可选,不传则挂在场景根部) @@ -131,13 +131,13 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j ### 8. manage_components -- **描述**: 管理节点组件 +- **描述**: 管理节点组件。**重要最佳实践**:在执行 `add` 操作前,建议先通过 `get` 操作检查节点上是否已存在同类型的组件,以避免重复添加。 - **参数**: - `nodeId`: 节点 UUID - - `action`: 操作类型(`add`, `remove`, `get`) - - `componentType`: 组件类型,如 `cc.Sprite`(用于 `add` 操作) - - `componentId`: 组件 ID(用于 `remove` 操作) - - `properties`: 组件属性(用于 `add` 操作)。**智能特性**:如果属性期望组件类型但传入节点UUID,插件会自动查找匹配组件。 + - `action`: 操作类型(`add`, `remove`, `get`, `update`) + - `componentType`: 组件类型,如 `cc.Sprite`(用于 `add`/`update` 操作) + - `componentId`: 组件 ID(用于 `remove`/`update` 操作) + - `properties`: 组件属性(用于 `add`/`update` 操作)。**智能特性**:如果属性期望组件类型但传入节点 UUID,插件会自动查找匹配组件。 ### 9. manage_script @@ -168,7 +168,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j ### 12. scene_management -- **描述**: 场景管理 +- **描述**: 场景管理。创建并通过 `open_scene` 打开后,请务必初始化基础节点(如 Canvas 和 Camera)。 - **参数**: - `action`: 操作类型(`create`, `delete`, `duplicate`, `get_info`) - `path`: 场景路径,如 `db://assets/scenes/NewScene.fire` diff --git a/main.js b/main.js index ddb975f..b96e1fc 100644 --- a/main.js +++ b/main.js @@ -121,7 +121,7 @@ const getToolsList = () => { }, { name: "create_scene", - description: "在 assets 目录下创建一个新的场景文件", + description: "在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera)。", inputSchema: { type: "object", properties: { @@ -144,7 +144,7 @@ const getToolsList = () => { }, { name: "open_scene", - description: "打开场景文件。注意:这是一个异步且耗时的操作,打开后请等待几秒再进行节点创建或保存操作。", + description: "打开场景文件。注意:这是一个异步且耗时的操作,打开后请等待几秒。重要:如果是新创建或空的场景,请务必先创建并初始化基础节点(Canvas/Camera)。", inputSchema: { type: "object", properties: { @@ -178,7 +178,7 @@ const getToolsList = () => { }, { name: "manage_components", - description: "管理节点组件", + description: "管理节点组件。重要提示:在执行 'add' 操作前,请务必先通过 'get' 操作检查节点上是否已存在同类型的组件,以避免重复添加导致逻辑异常。", inputSchema: { type: "object", properties: { @@ -713,7 +713,7 @@ module.exports = { handleMcpCall(name, args, callback) { if (isSceneBusy && (name === "save_scene" || name === "create_node")) { - return callback("Editor is busy (Processing Scene), please wait a moment."); + return callback("编辑器正忙(正在处理场景),请稍候。"); } switch (name) { case "get_selected_node": @@ -730,16 +730,16 @@ module.exports = { value: args.newName, isSubProp: false }); - callback(null, `Node name updated to ${args.newName}`); + callback(null, `节点名称已更新为 ${args.newName}`); break; case "save_scene": isSceneBusy = true; - addLog("info", "Preparing to save scene... Waiting for UI sync."); + addLog("info", "准备保存场景... 等待 UI 同步。"); Editor.Ipc.sendToPanel("scene", "scene:stash-and-save"); isSceneBusy = false; - addLog("info", "Safe Save completed."); - callback(null, "Scene saved successfully."); + addLog("info", "安全保存已完成。"); + callback(null, "场景保存成功。"); break; case "get_scene_hierarchy": @@ -753,7 +753,7 @@ module.exports = { addLog("error", `Transform update failed: ${err}`); callback(err); } else { - callback(null, "Transform updated"); + callback(null, "变换信息已更新"); } }); break; @@ -761,17 +761,17 @@ module.exports = { case "create_scene": const sceneUrl = `db://assets/${args.sceneName}.fire`; if (Editor.assetdb.exists(sceneUrl)) { - return callback("Scene already exists"); + return callback("场景已存在"); } Editor.assetdb.create(sceneUrl, getNewSceneTemplate(), (err) => { - callback(err, err ? null : `Standard Scene created at ${sceneUrl}`); + callback(err, err ? null : `标准场景已创建于 ${sceneUrl}`); }); break; case "create_prefab": const prefabUrl = `db://assets/${args.prefabName}.prefab`; Editor.Ipc.sendToMain("scene:create-prefab", args.nodeId, prefabUrl); - callback(null, `Command sent: Creating prefab '${args.prefabName}'`); + callback(null, `命令已发送:正在创建预制体 '${args.prefabName}'`); break; case "open_scene": @@ -781,11 +781,11 @@ module.exports = { Editor.Ipc.sendToMain("scene:open-by-uuid", openUuid); setTimeout(() => { isSceneBusy = false; - callback(null, `Success: Opening scene ${args.url}`); + callback(null, `成功:正在打开场景 ${args.url}`); }, 2000); } else { isSceneBusy = false; - callback(`Could not find asset with URL ${args.url}`); + callback(`找不到路径为 ${args.url} 的资源`); } break; diff --git a/scene-script.js b/scene-script.js index 0b9b235..2ee1f9b 100644 --- a/scene-script.js +++ b/scene-script.js @@ -26,11 +26,11 @@ module.exports = { }); if (event.reply) { - event.reply(null, `Node ${id} updated to ${value}`); + event.reply(null, `节点 ${id} 已更新为 ${value}`); } } else { if (event.reply) { - event.reply(new Error("Scene Script: Node not found " + id)); + event.reply(new Error("场景脚本:找不到节点 " + id)); } } }, @@ -102,10 +102,9 @@ module.exports = { Editor.Ipc.sendToAll("scene:node-changed", { uuid: id }); Editor.log(`[scene-script] Update complete. New Pos: (${node.x}, ${node.y})`); - if (event.reply) event.reply(null, "Transform updated"); + if (event.reply) event.reply(null, "变换信息已更新"); } else { - Editor.error(`[scene-script] Node not found: ${id}`); - if (event.reply) event.reply(new Error("Node not found")); + if (event.reply) event.reply(new Error("找不到节点")); } }, "create-node": function (event, args) { @@ -172,6 +171,50 @@ module.exports = { const compClass = component.constructor; for (const [key, value] of Object.entries(props)) { + // 【核心修复】专门处理各类事件属性 (ClickEvents, ScrollEvents 等) + const isEventProp = Array.isArray(value) && (key.toLowerCase().endsWith('events') || key === 'clickEvents'); + + if (isEventProp) { + const eventHandlers = []; + for (const item of value) { + if (typeof item === 'object' && (item.target || item.component || item.handler)) { + const handler = new cc.Component.EventHandler(); + + // 解析 Target Node + if (item.target) { + let targetNode = null; + if (typeof item.target === 'string') { + targetNode = cc.engine.getInstanceById(item.target); + if (!targetNode && Editor.Utils && Editor.Utils.UuidUtils) { + try { + const decompressed = Editor.Utils.UuidUtils.decompressUuid(item.target); + targetNode = cc.engine.getInstanceById(decompressed); + } catch (e) { } + } + } else if (item.target instanceof cc.Node) { + targetNode = item.target; + } + + if (targetNode) { + handler.target = targetNode; + Editor.log(`[scene-script] Resolved event target: ${targetNode.name}`); + } + } + + if (item.component) handler.component = item.component; + if (item.handler) handler.handler = item.handler; + if (item.customEventData !== undefined) handler.customEventData = String(item.customEventData); + + eventHandlers.push(handler); + } else { + // 如果不是对象,原样保留 + eventHandlers.push(item); + } + } + component[key] = eventHandlers; + continue; // 处理完事件数组,跳出本次循环 + } + // 检查属性是否存在 if (component[key] !== undefined) { let finalValue = value; @@ -207,7 +250,6 @@ module.exports = { // 尝试获取属性定义类型 let typeName = null; - // 优先尝试 getClassAttrs (Cocos 2.x editor environment) if (cc.Class.Attr.getClassAttrs) { const attrs = cc.Class.Attr.getClassAttrs(compClass); @@ -230,7 +272,6 @@ module.exports = { } if (typeName && (typeName.prototype instanceof cc.Component || typeName === cc.Component)) { - // 这是一个组件属性 const targetComp = targetNode.getComponent(typeName); if (targetComp) { @@ -260,16 +301,15 @@ module.exports = { } }; - if (!node) { - if (event.reply) event.reply(new Error("Node not found")); + if (event.reply) event.reply(new Error("找不到节点")); return; } switch (action) { case "add": if (!componentType) { - if (event.reply) event.reply(new Error("Component type is required")); + if (event.reply) event.reply(new Error("必须提供组件类型")); return; } @@ -285,7 +325,7 @@ module.exports = { } if (!compClass) { - if (event.reply) event.reply(new Error(`Component type not found: ${componentType}`)); + if (event.reply) event.reply(new Error(`找不到组件类型: ${componentType}`)); return; } @@ -300,15 +340,15 @@ module.exports = { Editor.Ipc.sendToMain("scene:dirty"); Editor.Ipc.sendToAll("scene:node-changed", { uuid: nodeId }); - if (event.reply) event.reply(null, `Component ${componentType} added`); + if (event.reply) event.reply(null, `组件 ${componentType} 已添加`); } catch (err) { - if (event.reply) event.reply(new Error(`Failed to add component: ${err.message}`)); + if (event.reply) event.reply(new Error(`添加组件失败: ${err.message}`)); } break; case "remove": if (!componentId) { - if (event.reply) event.reply(new Error("Component ID is required")); + if (event.reply) event.reply(new Error("必须提供组件 ID")); return; } @@ -328,12 +368,12 @@ module.exports = { node.removeComponent(component); Editor.Ipc.sendToMain("scene:dirty"); Editor.Ipc.sendToAll("scene:node-changed", { uuid: nodeId }); - if (event.reply) event.reply(null, "Component removed"); + if (event.reply) event.reply(null, "组件已移除"); } else { - if (event.reply) event.reply(new Error("Component not found")); + if (event.reply) event.reply(new Error("找不到组件")); } } catch (err) { - if (event.reply) event.reply(new Error(`Failed to remove component: ${err.message}`)); + if (event.reply) event.reply(new Error(`移除组件失败: ${err.message}`)); } break; @@ -387,7 +427,7 @@ module.exports = { if (event.reply) event.reply(new Error(`Component not found (Type: ${componentType}, ID: ${componentId})`)); } } catch (err) { - if (event.reply) event.reply(new Error(`Failed to update component: ${err.message}`)); + if (event.reply) event.reply(new Error(`更新组件失败: ${err.message}`)); } break; @@ -446,12 +486,12 @@ module.exports = { }); if (event.reply) event.reply(null, components); } catch (err) { - if (event.reply) event.reply(new Error(`Failed to get components: ${err.message}`)); + if (event.reply) event.reply(new Error(`获取组件失败: ${err.message}`)); } break; default: - if (event.reply) event.reply(new Error(`Unknown component action: ${action}`)); + if (event.reply) event.reply(new Error(`未知的组件操作类型: ${action}`)); break; } }, @@ -483,7 +523,7 @@ module.exports = { } if (!prefabUuid) { - if (event.reply) event.reply(new Error("Prefab UUID is required.")); + if (event.reply) event.reply(new Error("必须提供预制体 UUID。")); return; } @@ -491,14 +531,14 @@ module.exports = { // 如果是旧版,可能需要 cc.loader.load({uuid: ...}),但在 2.4 环境下 assetManager 更推荐 cc.assetManager.loadAny(prefabUuid, (err, prefab) => { if (err) { - if (event.reply) event.reply(new Error(`Failed to load prefab: ${err.message}`)); + if (event.reply) event.reply(new Error(`加载预制体失败: ${err.message}`)); return; } // 实例化预制体 const instance = cc.instantiate(prefab); if (!instance) { - if (event.reply) event.reply(new Error("Failed to instantiate prefab")); + if (event.reply) event.reply(new Error("实例化预制体失败")); return; } @@ -518,9 +558,9 @@ module.exports = { }); }, 10); - if (event.reply) event.reply(null, `Prefab instantiated successfully with UUID: ${instance.uuid}`); + if (event.reply) event.reply(null, `预制体实例化成功,UUID: ${instance.uuid}`); } else { - if (event.reply) event.reply(new Error("Parent node not found")); + if (event.reply) event.reply(new Error("找不到父节点")); } }); }, @@ -610,9 +650,9 @@ module.exports = { Editor.Ipc.sendToAll("scene:node-deleted", { uuid: uuid }); }, 10); - if (event.reply) event.reply(null, `Node ${uuid} deleted`); + if (event.reply) event.reply(null, `节点 ${uuid} 已删除`); } else { - if (event.reply) event.reply(new Error(`Node not found: ${uuid}`)); + if (event.reply) event.reply(new Error(`找不到节点: ${uuid}`)); } }, @@ -715,9 +755,9 @@ module.exports = { Editor.Ipc.sendToMain("scene:dirty"); Editor.Ipc.sendToAll("scene:node-changed", { uuid: nodeId }); - if (event.reply) event.reply(null, "VFX updated"); + if (event.reply) event.reply(null, "特效已更新"); } else { - if (event.reply) event.reply(new Error("Node not found")); + if (event.reply) event.reply(new Error("找不到节点")); } } else if (action === "get_info") { @@ -747,7 +787,7 @@ module.exports = { if (event.reply) event.reply(new Error("Node not found")); } } else { - if (event.reply) event.reply(new Error(`Unknown VFX action: ${action}`)); + if (event.reply) event.reply(new Error(`未知的特效操作类型: ${action}`)); } },