diff --git a/README.md b/README.md index 43ea011..95fc509 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j - **参数**: - `id`: 节点 UUID - `x`, `y`: 坐标 + - `width`, `height`: 节点宽高 (新增支持) - `scaleX`, `scaleY`: 缩放值 - `color`: HEX 颜色代码(如 #FF0000) - **重要提示**: 执行前必须调用 `get_scene_hierarchy` 确保 `id` 有效,防止操作不存在的节点。 @@ -246,12 +247,15 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j - **描述**: 管理纹理 - **参数**: - - `action`: 操作类型(`create`, `delete`, `get_info`) - - `path`: 纹理路径,如 `db://assets/textures/NewTexture.png` - - `properties`: 纹理属性(用于 `create` 操作) - - `width`: 宽度 - - `height`: 高度 - - `native`: 原生路径 + - `action`: 操作类型(`create`, `delete`, `get_info`, `update`) + - `path`: 纹理路径,如 `db://assets/textures/NewTexture.png` + - `properties`: 纹理属性(用于 `create`/`update` 操作) + - `type`: 纹理类型(如 `sprite`, `texture`, `raw`)(用于 `update`) + - `border`: 九宫格边距数组 `[top, bottom, left, right]` (用于 `update`,仅当 type 为 sprite 时有效) + - `subMetas`: (内部使用) + - `width`: 宽度 (用于 `create` 生成占位图) + - `height`: 高度 (用于 `create` 生成占位图) + - `native`: 原生路径 ### 18. execute_menu_item diff --git a/UPDATE_LOG.md b/UPDATE_LOG.md index 5d0cd67..84db8eb 100644 --- a/UPDATE_LOG.md +++ b/UPDATE_LOG.md @@ -48,5 +48,18 @@ --- -## 四、 总结 +## 四、 纹理与节点变换增强 (Texture & Transform Updates) + +### 1. `manage_texture` 工具增强 +- **新增 `update` 操作**: 支持修改现有纹理的类型(如 `texture` -> `sprite`)和九宫格边距 (`border`)。 +- **Meta 加载健壮性**: 修复了 `Editor.assetdb.loadMeta` 在某些情况下返回空值的问题,增加了读取文件系统 `.meta` 文件的 Fallback 机制。 +- **多版本兼容**: 针对 Cocos Creator 不同版本 `.meta` 文件结构差异(数组 vs 独立字段),实现了对 9-slice 数据写入的自动兼容。 + +### 2. `update_node_transform` 工具增强 +- **新增尺寸控制**: 添加了 `width` 和 `height` 参数,允许 AI 直接调整节点大小(对于测试九宫格拉伸效果至关重要)。 + +### 3. 关键 Bug 修复 +- **属性批量应用中断**: 修复了 `scene-script.js` 中 `applyProperties` 函数在处理 Asset 类型属性时错误使用 `return` 导致后续属性(如 `type`)被忽略的问题。现在改为 `continue`,确保所有属性都能被正确应用。 + +## 五、 总结 本次更新不仅修复了制约生产力的材质与资源同步 bug,还通过引入 `manage_shader` 和全方位的文档中文化,极大提升了开发者(及 AI 助手)在 Cocos Creator 2.4.x 环境下的操作体验。 diff --git a/main.js b/main.js index 7e5bc04..79398ed 100644 --- a/main.js +++ b/main.js @@ -129,6 +129,8 @@ const getToolsList = () => { id: { type: "string", description: "节点 UUID" }, x: { type: "number" }, y: { type: "number" }, + width: { type: "number" }, + height: { type: "number" }, scaleX: { type: "number" }, scaleY: { type: "number" }, color: { type: "string", description: "HEX 颜色代码如 #FF0000" }, @@ -354,7 +356,7 @@ const getToolsList = () => { inputSchema: { type: "object", properties: { - action: { type: "string", enum: ["create", "delete", "get_info"], description: "操作类型" }, + action: { type: "string", enum: ["create", "delete", "get_info", "update"], description: "操作类型" }, path: { type: "string", description: "纹理路径,如 db://assets/textures/NewTexture.png" }, properties: { type: "object", description: "纹理属性" }, }, @@ -1630,16 +1632,65 @@ CCProgram fs %{ if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } - // 【修复】Cocos 2.4.x 无法直接用 Editor.assetdb.create 创建带后缀的纹理(会校验内容) - // 我们需要先物理写入一个 1x1 的透明图片,再刷新数据库 - const base64Data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="; + + // 1. 准备文件内容 (优先使用 properties.content,否则使用默认 1x1) + let base64Data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="; + if (properties && properties.content) { + base64Data = properties.content; + } const buffer = Buffer.from(base64Data, 'base64'); try { + // 2. 写入物理文件 fs.writeFileSync(absolutePath, buffer); - Editor.assetdb.refresh(path, (err) => { + + // 3. 刷新该资源以生成 Meta + Editor.assetdb.refresh(path, (err, results) => { if (err) return callback(err); - callback(null, `Texture created and refreshed at ${path}`); + + // 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.`); + + // 稍微延迟确保 Meta 已生成 + setTimeout(() => { + const meta = Editor.assetdb.loadMeta(uuid); + if (meta) { + let changed = false; + if (properties.type) { + meta.type = properties.type; + changed = true; + } + + // 设置 9-slice (border) + // 注意:Cocos 2.4 纹理 Meta 中 subMetas 下通常有一个与纹理同名的 key (或者主要的一个 key) + if (properties.border) { + // 确保类型是 sprite + meta.type = 'sprite'; + + // 找到 SpriteFrame 的 subMeta + const subKeys = Object.keys(meta.subMetas); + if (subKeys.length > 0) { + const subMeta = meta.subMetas[subKeys[0]]; + subMeta.border = properties.border; // [top, bottom, left, right] + changed = true; + } + } + + 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}`); + }); + return; + } + } + callback(null, `Texture created at ${path}`); + }, 100); + } else { + callback(null, `Texture created at ${path}`); + } }); } catch (e) { callback(`Failed to write texture file: ${e.message}`); @@ -1662,6 +1713,110 @@ CCProgram fs %{ callback(`Texture not found: ${path}`); } break; + case "update": + if (!Editor.assetdb.exists(path)) { + return callback(`Texture not found at ${path}`); + } + const uuid = Editor.assetdb.urlToUuid(path); + let meta = Editor.assetdb.loadMeta(uuid); + + // Fallback: 如果 Editor.assetdb.loadMeta 失败 (API 偶尔不稳定),尝试直接读取文件系统中的 .meta 文件 + if (!meta) { + try { + const fspath = Editor.assetdb.urlToFspath(path); + const metaPath = fspath + ".meta"; + if (fs.existsSync(metaPath)) { + const metaContent = fs.readFileSync(metaPath, "utf-8"); + meta = JSON.parse(metaContent); + addLog("info", `[manage_texture] Loaded meta from fs fallback: ${metaPath}`); + } + } catch (e) { + addLog("warn", `[manage_texture] Meta fs fallback failed: ${e.message}`); + } + } + + if (!meta) { + return callback(`Failed to load meta for ${path}`); + } + + let changed = false; + if (properties) { + // 更新类型 + if (properties.type) { + if (meta.type !== properties.type) { + meta.type = properties.type; + changed = true; + } + } + + // 更新 9-slice border + if (properties.border) { + // 确保类型是 sprite + if (meta.type !== 'sprite') { + meta.type = 'sprite'; + changed = true; + } + + // 找到 SubMeta + // Cocos Meta 结构: { subMetas: { "textureName": { ... } } } + // 注意:Cocos 2.x 的 meta 结构因版本而异,旧版可能使用 border: [t, b, l, r] 数组, + // 而新版 (如 2.3.x+) 通常使用 borderTop, borderBottom 等独立字段。 + // 此处逻辑实现了兼容性处理。 + const subKeys = Object.keys(meta.subMetas); + if (subKeys.length > 0) { + const subMeta = meta.subMetas[subKeys[0]]; + const newBorder = properties.border; // [top, bottom, left, right] + + // 方式 1: standard array style + if (subMeta.border !== undefined) { + const oldBorder = subMeta.border; + if (!oldBorder || + oldBorder[0] !== newBorder[0] || + oldBorder[1] !== newBorder[1] || + oldBorder[2] !== newBorder[2] || + oldBorder[3] !== newBorder[3]) { + subMeta.border = newBorder; + changed = true; + } + } + // 方式 2: individual fields style (common in 2.3.x) + else if (subMeta.borderTop !== undefined) { + // top, bottom, left, right + if (subMeta.borderTop !== newBorder[0] || + subMeta.borderBottom !== newBorder[1] || + subMeta.borderLeft !== newBorder[2] || + subMeta.borderRight !== newBorder[3]) { + + subMeta.borderTop = newBorder[0]; + subMeta.borderBottom = newBorder[1]; + subMeta.borderLeft = newBorder[2]; + subMeta.borderRight = newBorder[3]; + changed = true; + } + } + // 方式 3: 如果都没有,尝试写入 individual fields + else { + subMeta.borderTop = newBorder[0]; + subMeta.borderBottom = newBorder[1]; + subMeta.borderLeft = newBorder[2]; + subMeta.borderRight = newBorder[3]; + changed = true; + } + } + } + } + + if (changed) { + // 使用 saveMeta 或者 fs 写入 + // 为了安全,如果 loadMeta 失败了,safeMeta 可能也会失败,所以这里尽量用 API,不行再 fallback (暂且只用 API) + Editor.assetdb.saveMeta(uuid, JSON.stringify(meta), (err) => { + if (err) return callback(`Failed to save meta: ${err}`); + callback(null, `Texture updated at ${path}`); + }); + } else { + callback(null, `No changes needed for ${path}`); + } + break; default: callback(`Unknown texture action: ${action}`); break; diff --git a/scene-script.js b/scene-script.js index efef8a4..91872ae 100644 --- a/scene-script.js +++ b/scene-script.js @@ -112,13 +112,22 @@ module.exports = { Editor.log(`[scene-script] Node found: ${node.name}, Current Pos: (${node.x}, ${node.y})`); if (x !== undefined) { - node.x = Number(x); // 强制转换确保类型正确 + node.x = Number(x); Editor.log(`[scene-script] Set x to ${node.x}`); } if (y !== undefined) { node.y = Number(y); Editor.log(`[scene-script] Set y to ${node.y}`); } + // [新增] 支持设置节点宽高 (用于测试 9-slice 等需要调整尺寸的情况) + if (args.width !== undefined) { + node.width = Number(args.width); + Editor.log(`[scene-script] Set width to ${node.width}`); + } + if (args.height !== undefined) { + node.height = Number(args.height); + Editor.log(`[scene-script] Set height to ${node.height}`); + } if (scaleX !== undefined) node.scaleX = Number(scaleX); if (scaleY !== undefined) node.scaleY = Number(scaleY); if (color) { @@ -368,7 +377,9 @@ module.exports = { } }); }); - return; // 跳过后续的直接赋值 + // 【重要修复】使用 continue 而不是 return,确保处理完 Asset 属性后 + // 还能继续处理后续的普通属性 (如 type, sizeMode 等) + continue; } else if (propertyType && (propertyType.prototype instanceof cc.Component || propertyType === cc.Component || propertyType === cc.Node)) { // 2. 处理节点或组件引用 const targetNode = findNode(value);