From 09817ac79daa612a0acb0f78509cd410c801e093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=AB=E7=84=B0=E5=BA=93=E6=8B=89?= Date: Wed, 11 Feb 2026 01:09:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=85=A8=E9=87=8F=E6=B1=89=E5=8C=96=E3=80=81=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=8F=8A=20Undo=20=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=80=A7=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVELOPMENT.md | 8 +++ README.md | 3 +- main.js | 145 +++++++++++++++++++++++++----------------------- scene-script.js | 46 ++++++++------- 注意事项.md | 38 +++++++++---- 5 files changed, 133 insertions(+), 107 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index dd9e551..179408f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -101,6 +101,14 @@ startServer(port) { } ``` +## 4. 开发历程与里程碑 + +### 2026-02-10: Undo 系统深度修复与规范化 +- **问题分析**: 修复了 `TypeError: Cannot read property '_name' of null`。该错误是由于直接修改节点属性(绕过 Undo 系统)与分组事务混用导致的。 +- **重构要点**: 将 `update-node-transform` 中所有的直接赋值替换为 `scene:set-property` IPC 调用,确保所有变换修改均受撤销系统监控。 +- **缺陷修正**: 修复了 `manage_undo` 在 `begin_group` 时传递错误参数导致 "Unknown object to record" 的问题。 +- **全量汉化与文档同步**: 完成了 `main.js` 和 `scene-script.js` 的 100% 简体中文翻译。同步更新了 `README.md`、`DEVELOPMENT.md` 及 `注意事项.md`。 + ### 3.2 MCP 工具注册 在 `/list-tools` 接口中注册工具: diff --git a/README.md b/README.md index ba657ce..5747e0e 100644 --- a/README.md +++ b/README.md @@ -317,12 +317,11 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j } ``` -### 23. manage_undo - - **描述**: 撤销/重做管理 - **参数**: - `action`: 操作类型 (`undo`, `redo`, `begin_group`, `end_group`, `cancel_group`) - `description`: 撤销组描述 (用于 `begin_group`) + - `id`: 节点 UUID (用于 `begin_group` 时的 `undo-record`,可选) ### 24. manage_vfx diff --git a/main.js b/main.js index fc8c4be..cae0017 100644 --- a/main.js +++ b/main.js @@ -1040,9 +1040,9 @@ export default class NewScript extends cc.Component { // 否则后续立即挂载脚本的操作(manage_components)会因找不到脚本 UUID 而失败。 Editor.assetdb.refresh(scriptPath, (refreshErr) => { if (refreshErr) { - addLog("warn", `Refresh failed after script creation: ${refreshErr}`); + addLog("warn", `脚本创建后刷新失败: ${refreshErr}`); } - callback(null, `Script created at ${scriptPath}`); + callback(null, `脚本已创建: ${scriptPath}`); }); } }, @@ -1076,7 +1076,7 @@ export default class NewScript extends cc.Component { // 使用 fs 写入 + refresh,确保覆盖成功 const writeFsPath = Editor.assetdb.urlToFspath(scriptPath); if (!writeFsPath) { - return callback(`Invalid path: ${scriptPath}`); + return callback(`路径无效: ${scriptPath}`); } try { @@ -1133,7 +1133,7 @@ export default class NewScript extends cc.Component { switch (action) { case "create": if (Editor.assetdb.exists(path)) { - return callback(`Asset already exists at ${path}`); + return callback(`资源已存在: ${path}`); } // 确保父目录存在 const fs = require("fs"); @@ -1144,16 +1144,16 @@ export default class NewScript extends cc.Component { fs.mkdirSync(dirPath, { recursive: true }); } Editor.assetdb.create(path, content || "", (err) => { - callback(err, err ? null : `Asset created at ${path}`); + callback(err, err ? null : `资源已创建: ${path}`); }); break; case "delete": if (!Editor.assetdb.exists(path)) { - return callback(`Asset not found at ${path}`); + return callback(`找不到资源: ${path}`); } Editor.assetdb.delete([path], (err) => { - callback(err, err ? null : `Asset deleted at ${path}`); + callback(err, err ? null : `资源已删除: ${path}`); }); break; @@ -1173,7 +1173,7 @@ export default class NewScript extends cc.Component { case "get_info": try { if (!Editor.assetdb.exists(path)) { - return callback(`Asset not found: ${path}`); + return callback(`找不到资源: ${path}`); } const uuid = Editor.assetdb.urlToUuid(path); const info = Editor.assetdb.assetInfoByUuid(uuid); @@ -1184,7 +1184,7 @@ export default class NewScript extends cc.Component { callback(null, { url: path, uuid: uuid, exists: true }); } } catch (e) { - callback(`Error getting asset info: ${e.message}`); + callback(`获取资源信息失败: ${e.message}`); } break; @@ -1205,7 +1205,7 @@ export default class NewScript extends cc.Component { switch (action) { case "create": if (Editor.assetdb.exists(path)) { - return callback(`Scene already exists at ${path}`); + return callback(`场景已存在: ${path}`); } // 确保父目录存在 const absolutePath = Editor.assetdb.urlToFspath(path); @@ -1214,35 +1214,35 @@ export default class NewScript extends cc.Component { fs.mkdirSync(dirPath, { recursive: true }); } Editor.assetdb.create(path, getNewSceneTemplate(), (err) => { - callback(err, err ? null : `Scene created at ${path}`); + callback(err, err ? null : `场景已创建: ${path}`); }); break; case "delete": if (!Editor.assetdb.exists(path)) { - return callback(`Scene not found at ${path}`); + return callback(`找不到场景: ${path}`); } Editor.assetdb.delete([path], (err) => { - callback(err, err ? null : `Scene deleted at ${path}`); + callback(err, err ? null : `场景已删除: ${path}`); }); break; case "duplicate": if (!Editor.assetdb.exists(path)) { - return callback(`Scene not found at ${path}`); + return callback(`找不到场景: ${path}`); } if (!targetPath) { - return callback(`Target path is required for duplicate operation`); + return callback("复制操作需要目标路径"); } if (Editor.assetdb.exists(targetPath)) { - return callback(`Target scene already exists at ${targetPath}`); + return callback(`目标场景已存在: ${targetPath}`); } // 【修复】Cocos 2.4.x 主进程中 Editor.assetdb 没有 loadAny // 直接使用 fs 读取物理文件 try { const sourceFsPath = Editor.assetdb.urlToFspath(path); if (!sourceFsPath || !fs.existsSync(sourceFsPath)) { - return callback(`Failed to locate source scene file: ${path}`); + return callback(`定位源场景文件失败: ${path}`); } const content = fs.readFileSync(sourceFsPath, "utf-8"); @@ -1257,7 +1257,7 @@ export default class NewScript extends cc.Component { if (err) return callback(err); // 【增加】关键刷新,确保数据库能查到新文件 Editor.assetdb.refresh(targetPath, (refreshErr) => { - callback(refreshErr, refreshErr ? null : `Scene duplicated from ${path} to ${targetPath}`); + callback(refreshErr, refreshErr ? null : `场景已从 ${path} 复制到 ${targetPath}`); }); }); } catch (e) { @@ -1271,7 +1271,7 @@ export default class NewScript extends cc.Component { const info = Editor.assetdb.assetInfoByUuid(uuid); callback(null, info || { url: path, uuid: uuid, exists: true }); } else { - callback(`Scene not found: ${path}`); + return callback(`找不到场景: ${path}`); } break; @@ -1288,10 +1288,10 @@ export default class NewScript extends cc.Component { switch (action) { case "create": if (!nodeId) { - return callback(`Node ID is required for create operation`); + return callback("创建预制体需要节点 ID"); } if (Editor.assetdb.exists(prefabPath)) { - return callback(`Prefab already exists at ${prefabPath}`); + return callback(`预制体已存在: ${prefabPath}`); } // 确保父目录存在 const absolutePath = Editor.assetdb.urlToFspath(prefabPath); @@ -1321,19 +1321,19 @@ export default class NewScript extends cc.Component { Editor.Ipc.sendToPanel("scene", "scene:create-prefab", [nodeId], targetDir); }, 100); // 稍微延迟以确保重命名生效 - callback(null, `Command sent: Creating prefab from node ${nodeId} at ${targetDir} as ${prefabName}`); + callback(null, `指令已发送: 从节点 ${nodeId} 在目录 ${targetDir} 创建名为 ${prefabName} 的预制体`); break; case "update": if (!nodeId) { - return callback(`Node ID is required for update operation`); + return callback("更新预制体需要节点 ID"); } if (!Editor.assetdb.exists(prefabPath)) { - return callback(`Prefab not found at ${prefabPath}`); + return callback(`找不到预制体: ${prefabPath}`); } // 更新预制体 Editor.Ipc.sendToPanel("scene", "scene:update-prefab", nodeId, prefabPath); - callback(null, `Command sent: Updating prefab ${prefabPath} from node ${nodeId}`); + callback(null, `指令已发送: 从节点 ${nodeId} 更新预制体 ${prefabPath}`); break; case "instantiate": @@ -1362,7 +1362,7 @@ export default class NewScript extends cc.Component { result.exists = true; callback(null, result); } else { - callback(`Prefab not found: ${prefabPath}`); + return callback(`找不到预制体: ${prefabPath}`); } break; @@ -1405,10 +1405,10 @@ export default class NewScript extends cc.Component { const refreshPath = (properties && properties.path) ? properties.path : 'db://assets/scripts'; Editor.assetdb.refresh(refreshPath, (err) => { if (err) { - addLog("error", `Refresh failed: ${err}`); + addLog("error", `刷新失败: ${err}`); callback(err); } else { - callback(null, `Editor refreshed: ${refreshPath}`); + callback(null, `编辑器已刷新: ${refreshPath}`); } }); break; @@ -1425,7 +1425,7 @@ export default class NewScript extends cc.Component { switch (action) { case "create": if (Editor.assetdb.exists(effectPath)) { - return callback(`Effect already exists at ${effectPath}`); + return callback(`Effect 已存在: ${effectPath}`); } // 确保父目录存在 const absolutePath = Editor.assetdb.urlToFspath(effectPath); @@ -1476,21 +1476,21 @@ CCProgram fs %{ Editor.assetdb.create(effectPath, content || defaultEffect, (err) => { if (err) return callback(err); Editor.assetdb.refresh(effectPath, (refreshErr) => { - callback(refreshErr, refreshErr ? null : `Effect created at ${effectPath}`); + callback(refreshErr, refreshErr ? null : `Effect 已创建: ${effectPath}`); }); }); break; case "read": if (!Editor.assetdb.exists(effectPath)) { - return callback(`Effect not found: ${effectPath}`); + return callback(`找不到 Effect: ${effectPath}`); } const fspath = Editor.assetdb.urlToFspath(effectPath); try { const data = fs.readFileSync(fspath, "utf-8"); callback(null, data); } catch (e) { - callback(`Failed to read effect: ${e.message}`); + callback(`读取 Effect 失败: ${e.message}`); } break; @@ -1502,19 +1502,19 @@ CCProgram fs %{ try { fs.writeFileSync(writeFsPath, content, "utf-8"); Editor.assetdb.refresh(effectPath, (err) => { - callback(err, err ? null : `Effect updated at ${effectPath}`); + callback(err, err ? null : `Effect 已更新: ${effectPath}`); }); } catch (e) { - callback(`Failed to write effect: ${e.message}`); + callback(`更新 Effect 失败: ${e.message}`); } break; case "delete": if (!Editor.assetdb.exists(effectPath)) { - return callback(`Effect not found: ${effectPath}`); + return callback(`找不到 Effect: ${effectPath}`); } Editor.assetdb.delete([effectPath], (err) => { - callback(err, err ? null : `Effect deleted: ${effectPath}`); + callback(err, err ? null : `Effect 已删除: ${effectPath}`); }); break; @@ -1524,7 +1524,7 @@ CCProgram fs %{ const info = Editor.assetdb.assetInfoByUuid(uuid); callback(null, info || { url: effectPath, uuid: uuid, exists: true }); } else { - callback(`Effect not found: ${effectPath}`); + callback(`找不到 Effect: ${effectPath}`); } break; @@ -1541,7 +1541,7 @@ CCProgram fs %{ switch (action) { case "create": if (Editor.assetdb.exists(matPath)) { - return callback(`Material already exists at ${matPath}`); + return callback(`材质已存在: ${matPath}`); } // 确保父目录存在 const absolutePath = Editor.assetdb.urlToFspath(matPath); @@ -1569,14 +1569,14 @@ CCProgram fs %{ Editor.assetdb.create(matPath, JSON.stringify(materialData, null, 2), (err) => { if (err) return callback(err); Editor.assetdb.refresh(matPath, (refreshErr) => { - callback(refreshErr, refreshErr ? null : `Material created at ${matPath}`); + callback(refreshErr, refreshErr ? null : `材质已创建: ${matPath}`); }); }); break; case "update": if (!Editor.assetdb.exists(matPath)) { - return callback(`Material not found at ${matPath}`); + return callback(`找不到材质: ${matPath}`); } const fspath = Editor.assetdb.urlToFspath(matPath); try { @@ -1605,19 +1605,19 @@ CCProgram fs %{ fs.writeFileSync(fspath, JSON.stringify(matData, null, 2), "utf-8"); Editor.assetdb.refresh(matPath, (err) => { - callback(err, err ? null : `Material updated at ${matPath}`); + callback(err, err ? null : `材质已更新: ${matPath}`); }); } catch (e) { - callback(`Failed to update material: ${e.message}`); + callback(`更新材质失败: ${e.message}`); } break; case "delete": if (!Editor.assetdb.exists(matPath)) { - return callback(`Material not found at ${matPath}`); + return callback(`找不到材质: ${matPath}`); } Editor.assetdb.delete([matPath], (err) => { - callback(err, err ? null : `Material deleted at ${matPath}`); + callback(err, err ? null : `材质已删除: ${matPath}`); }); break; @@ -1627,7 +1627,7 @@ CCProgram fs %{ const info = Editor.assetdb.assetInfoByUuid(uuid); callback(null, info || { url: matPath, uuid: uuid, exists: true }); } else { - callback(`Material not found: ${matPath}`); + callback(`找不到材质: ${matPath}`); } break; @@ -1644,7 +1644,7 @@ CCProgram fs %{ switch (action) { case "create": if (Editor.assetdb.exists(path)) { - return callback(`Texture already exists at ${path}`); + return callback(`纹理已存在: ${path}`); } // 确保父目录存在 const absolutePath = Editor.assetdb.urlToFspath(path); @@ -1671,7 +1671,7 @@ CCProgram fs %{ // 4. 如果有 9-slice 设置,更新 Meta if (properties && (properties.border || properties.type)) { const uuid = Editor.assetdb.urlToUuid(path); - if (!uuid) return callback(null, `Texture created but UUID not found immediately.`); + if (!uuid) return callback(null, `纹理已创建,但未能立即获取 UUID。`); // 稍微延迟确保 Meta 已生成 setTimeout(() => { @@ -1700,28 +1700,28 @@ CCProgram fs %{ if (changed) { Editor.assetdb.saveMeta(uuid, JSON.stringify(meta), (err) => { - if (err) Editor.warn(`Failed to save meta for ${path}: ${err}`); - callback(null, `Texture created and meta updated at ${path}`); + if (err) Editor.warn(`保存资源 Meta 失败 ${path}: ${err}`); + callback(null, `纹理已创建并更新 Meta: ${path}`); }); return; } } - callback(null, `Texture created at ${path}`); + callback(null, `纹理已创建: ${path}`); }, 100); } else { - callback(null, `Texture created at ${path}`); + callback(null, `纹理已创建: ${path}`); } }); } catch (e) { - callback(`Failed to write texture file: ${e.message}`); + callback(`写入纹理文件失败: ${e.message}`); } break; case "delete": if (!Editor.assetdb.exists(path)) { - return callback(`Texture not found at ${path}`); + return callback(`找不到纹理: ${path}`); } Editor.assetdb.delete([path], (err) => { - callback(err, err ? null : `Texture deleted at ${path}`); + callback(err, err ? null : `纹理已删除: ${path}`); }); break; case "get_info": @@ -1730,12 +1730,12 @@ CCProgram fs %{ const info = Editor.assetdb.assetInfoByUuid(uuid); callback(null, info || { url: path, uuid: uuid, exists: true }); } else { - callback(`Texture not found: ${path}`); + callback(`找不到纹理: ${path}`); } break; case "update": if (!Editor.assetdb.exists(path)) { - return callback(`Texture not found at ${path}`); + return callback(`找不到纹理: ${path}`); } const uuid = Editor.assetdb.urlToUuid(path); let meta = Editor.assetdb.loadMeta(uuid); @@ -1756,7 +1756,7 @@ CCProgram fs %{ } if (!meta) { - return callback(`Failed to load meta for ${path}`); + return callback(`加载资源 Meta 失败: ${path}`); } let changed = false; @@ -1838,7 +1838,7 @@ CCProgram fs %{ } break; default: - callback(`Unknown texture action: ${action}`); + callback(`未知的纹理操作类型: ${action}`); break; } }, @@ -2002,12 +2002,12 @@ CCProgram fs %{ // 1. 获取文件系统路径 const fspath = Editor.assetdb.urlToFspath(filePath); if (!fspath) { - return callback(`File not found or invalid URL: ${filePath}`); + return callback(`找不到文件或 URL 无效: ${filePath}`); } // 2. 检查文件是否存在 if (!fs.existsSync(fspath)) { - return callback(`File does not exist: ${fspath}`); + return callback(`文件不存在: ${fspath}`); } // 3. 读取内容并验证 @@ -2016,7 +2016,7 @@ CCProgram fs %{ // 检查空文件 if (!content || content.trim().length === 0) { - return callback(null, { valid: false, message: "Script is empty" }); + return callback(null, { valid: false, message: "脚本内容为空" }); } // 对于 JavaScript 脚本,使用 Function 构造器进行语法验证 @@ -2024,7 +2024,7 @@ CCProgram fs %{ const wrapper = `(function() { ${content} })`; try { new Function(wrapper); - callback(null, { valid: true, message: "JavaScript syntax is valid" }); + callback(null, { valid: true, message: "JavaScript 语法验证通过" }); } catch (syntaxErr) { return callback(null, { valid: false, message: syntaxErr.message }); } @@ -2037,7 +2037,7 @@ CCProgram fs %{ // 检查是否有 class 定义 (简单的启发式检查) if (!content.includes('class ') && !content.includes('interface ') && !content.includes('enum ') && !content.includes('export ')) { - return callback(null, { valid: true, message: "Warning: TypeScript file seems to lack standard definitions (class/interface), but basic syntax check is skipped due to missing compiler." }); + return callback(null, { valid: true, message: "警告: TypeScript 文件似乎缺少标准定义 (class/interface/export),但由于缺少编译器,已跳过基础语法检查。" }); } callback(null, { valid: true, message: "TypeScript 基础检查通过。(完整编译验证需要通过编辑器构建流程)" }); @@ -2079,7 +2079,7 @@ CCProgram fs %{ // 修改场景中的节点(需要通过 scene-script) "set-node-property"(event, args) { - addLog("mcp", `Creating node: ${args.name} (${args.type})`); + addLog("mcp", `设置节点属性: ${args.name} (${args.type})`); // 确保第一个参数 'mcp-bridge' 和 package.json 的 name 一致 Editor.Scene.callSceneScript("mcp-bridge", "set-property", args, (err, result) => { if (err) { @@ -2091,10 +2091,10 @@ CCProgram fs %{ }); }, "create-node"(event, args) { - addLog("mcp", `Creating node: ${args.name} (${args.type})`); + addLog("mcp", `创建节点: ${args.name} (${args.type})`); Editor.Scene.callSceneScript("mcp-bridge", "create-node", args, (err, result) => { - if (err) addLog("error", `CreateNode Failed: ${err}`); - else addLog("success", `Node Created: ${result}`); + if (err) addLog("error", `创建节点失败: ${err}`); + else addLog("success", `节点已创建: ${result}`); event.reply(err, result); }); }, @@ -2376,9 +2376,14 @@ CCProgram fs %{ break; case "begin_group": // scene:undo-record [id] - // 这里的 id 好像是可选的,或者用于区分不同的事务 - Editor.Ipc.sendToPanel("scene", "scene:undo-record", description || "MCP Action"); - callback(null, `Undo group started: ${description || "MCP Action"}`); + // 注意:在 2.4.x 中,undo-record 通常需要一个有效的 uuid + // 如果没有提供 uuid,不应将 description 作为 ID 发送,否则会报 Unknown object to record + addLog("info", `开始撤销组: ${description || "MCP 动作"}`); + // 如果有参数包含 id,则记录该节点 + if (args.id) { + Editor.Ipc.sendToPanel("scene", "scene:undo-record", args.id); + } + callback(null, `撤销组已启动: ${description || "MCP 动作"}`); break; case "end_group": Editor.Ipc.sendToPanel("scene", "scene:undo-commit"); diff --git a/scene-script.js b/scene-script.js index 3477e1f..6c20697 100644 --- a/scene-script.js +++ b/scene-script.js @@ -109,44 +109,42 @@ module.exports = { let node = findNode(id); if (node) { - Editor.log(`[scene-script] Node found: ${node.name}, Current Pos: (${node.x}, ${node.y})`); + Editor.log(`[scene-script] 找到节点: ${node.name}, 当前坐标: (${node.x}, ${node.y})`); + // 使用 scene:set-property 实现支持 Undo 的属性修改 + // 注意:IPC 消息需要发送到 'scene' 面板 if (x !== undefined) { - node.x = Number(x); - Editor.log(`[scene-script] Set x to ${node.x}`); + Editor.Ipc.sendToPanel("scene", "scene:set-property", { id, path: "x", type: "Number", value: Number(x) }); } if (y !== undefined) { - node.y = Number(y); - Editor.log(`[scene-script] Set y to ${node.y}`); + Editor.Ipc.sendToPanel("scene", "scene:set-property", { id, path: "y", type: "Number", value: Number(y) }); } - // [新增] 支持设置节点宽高 (用于测试 9-slice 等需要调整尺寸的情况) if (args.width !== undefined) { - node.width = Number(args.width); - Editor.log(`[scene-script] Set width to ${node.width}`); + Editor.Ipc.sendToPanel("scene", "scene:set-property", { id, path: "width", type: "Number", value: Number(args.width) }); } if (args.height !== undefined) { - node.height = Number(args.height); - Editor.log(`[scene-script] Set height to ${node.height}`); + Editor.Ipc.sendToPanel("scene", "scene:set-property", { id, path: "height", type: "Number", value: Number(args.height) }); + } + if (scaleX !== undefined) { + Editor.Ipc.sendToPanel("scene", "scene:set-property", { id, path: "scaleX", type: "Number", value: Number(scaleX) }); + } + if (scaleY !== undefined) { + Editor.Ipc.sendToPanel("scene", "scene:set-property", { id, path: "scaleY", type: "Number", value: Number(scaleY) }); } - if (scaleX !== undefined) node.scaleX = Number(scaleX); - if (scaleY !== undefined) node.scaleY = Number(scaleY); if (color) { const c = new cc.Color().fromHEX(color); - // 使用 scene:set-property 实现支持 Undo 的颜色修改 - // 注意:IPC 消息需要发送到场景面板 Editor.Ipc.sendToPanel("scene", "scene:set-property", { id: id, path: "color", type: "Color", value: { r: c.r, g: c.g, b: c.b, a: c.a } }); - // 既然走了 IPC,就不需要手动 set node.color 了,也不需要重复 dirty } Editor.Ipc.sendToMain("scene:dirty"); Editor.Ipc.sendToAll("scene:node-changed", { uuid: id }); - Editor.log(`[scene-script] Update complete. New Pos: (${node.x}, ${node.y})`); + Editor.log(`[scene-script] 更新完成。新坐标: (${node.x}, ${node.y})`); if (event.reply) event.reply(null, "变换信息已更新"); } else { if (event.reply) event.reply(new Error("找不到节点")); @@ -349,9 +347,9 @@ module.exports = { loadedCount++; if (!err && asset) { loadedAssets[idx] = asset; - Editor.log(`[scene-script] Successfully loaded asset for ${key}[${idx}]: ${asset.name}`); + Editor.log(`[scene-script] 成功为 ${key}[${idx}] 加载资源: ${asset.name}`); } else { - Editor.warn(`[scene-script] Failed to load asset ${uuid} for ${key}[${idx}]: ${err}`); + Editor.warn(`[scene-script] 未能为 ${key}[${idx}] 加载资源 ${uuid}: ${err}`); } if (loadedCount === uuids.length) { @@ -391,13 +389,13 @@ module.exports = { if (targetComp) { finalValue = targetComp; } else { - Editor.warn(`[scene-script] Component ${propertyType.name} not found on node ${targetNode.name}`); + Editor.warn(`[scene-script] 在节点 ${targetNode.name} 上找不到组件 ${propertyType.name}`); } } - Editor.log(`[scene-script] Applied Reference for ${key}: ${targetNode.name}`); + Editor.log(`[scene-script] 已应用 ${key} 的引用: ${targetNode.name}`); } else if (value && value.length > 20) { // 如果明确是组件/节点类型但找不到,才报错 - Editor.warn(`[scene-script] Failed to resolve target node/comp for ${key}: ${value}`); + Editor.warn(`[scene-script] 无法解析 ${key} 的目标节点/组件: ${value}`); } } else { // 3. 通用启发式 (找不到类型时的 fallback) @@ -405,7 +403,7 @@ module.exports = { const targetNode = findNode(value); if (targetNode) { finalValue = targetNode; - Editor.log(`[scene-script] Heuristic resolved Node for ${key}: ${targetNode.name}`); + Editor.log(`[scene-script] 启发式解析 ${key} 的节点: ${targetNode.name}`); } else { // 找不到节点且是 UUID -> 视为资源 const compIndex = node._components.indexOf(component); @@ -417,14 +415,14 @@ module.exports = { value: { uuid: value }, isSubProp: false }); - Editor.log(`[scene-script] Heuristic resolved Asset via IPC for ${key}: ${value}`); + Editor.log(`[scene-script] 通过 IPC 启发式解析 ${key} 的资源: ${value}`); } return; } } } } catch (e) { - Editor.warn(`[scene-script] Property resolution failed for ${key}: ${e.message}`); + Editor.warn(`[scene-script] 解析属性 ${key} 失败: ${e.message}`); } component[key] = finalValue; diff --git a/注意事项.md b/注意事项.md index d2b98c3..c2eb79a 100644 --- a/注意事项.md +++ b/注意事项.md @@ -48,7 +48,7 @@ 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 字符串。 + * **Node/Component**: 对于节点 or 组件引用,通过 `findNode` 查找实例并直接赋值实例对象,而非 UUID 字符串。 --- @@ -68,13 +68,29 @@ * **资产识别启发式**:当通过 `manage_components` 赋值时,如果属性名包含以下关键字,插件会尝试将其作为 UUID 资源处理: `prefab`, `sprite`, `texture`, `material`, `skeleton`, `spine`, `atlas`, `font`, `audio`, `data` * **建议**:如果资源未正确加载,请检查属性名是否包含以上关键字,或手动确认该 UUID 不属于任何节点。 -69: -70: --- -71: -72: ## 7. 搜索工具 (search_project) 使用建议 -73: * **性能建议**:尽量指定 `path` 参数缩小搜索范围(如 `db://assets/scripts`),避免全项目大面积搜索,尤其是在包含大量旧资源的 Library 目录(虽然插件已过滤)。 -74: * **正则表达式**:在使用 `useRegex` 时,确保正则模式的语法正确。如果正则匹配失败,工具会返回详细的错误提示。 -75: * **模式选择**: -76: * 查找具体逻辑代码:使用 `matchType: "content"`。 -77: * 定位资源文件:使用 `matchType: "file_name"` 并配合 `extensions` 过滤。 -78: * 重构目录结构前:使用 `matchType: "dir_name"` 检查目录名冲突。 + +--- + +## 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 属性修改方式 +* **核心规则**:在 `scene-script.js` 中严禁直接使用 `node.x = 100`。 +* **正确做法**:必须通过 `Editor.Ipc.sendToPanel('scene', 'scene:set-property', ...)`。只有这样,修改才会被 Cocos Creator 的 UndoManager 捕获,从而支持撤销。