文档与功能更新 (Texture & Transform):增强 manage_texture 和 update_node_transform 工具,修复属性应用 bug,并更新相关文档

This commit is contained in:
火焰库拉
2026-02-10 14:00:02 +08:00
parent 256c91e9f5
commit 0997a3d98c
4 changed files with 198 additions and 15 deletions

View File

@@ -112,6 +112,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- **参数**: - **参数**:
- `id`: 节点 UUID - `id`: 节点 UUID
- `x`, `y`: 坐标 - `x`, `y`: 坐标
- `width`, `height`: 节点宽高 (新增支持)
- `scaleX`, `scaleY`: 缩放值 - `scaleX`, `scaleY`: 缩放值
- `color`: HEX 颜色代码(如 #FF0000 - `color`: HEX 颜色代码(如 #FF0000
- **重要提示**: 执行前必须调用 `get_scene_hierarchy` 确保 `id` 有效,防止操作不存在的节点。 - **重要提示**: 执行前必须调用 `get_scene_hierarchy` 确保 `id` 有效,防止操作不存在的节点。
@@ -246,12 +247,15 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- **描述**: 管理纹理 - **描述**: 管理纹理
- **参数**: - **参数**:
- `action`: 操作类型(`create`, `delete`, `get_info` - `action`: 操作类型(`create`, `delete`, `get_info`, `update`
- `path`: 纹理路径,如 `db://assets/textures/NewTexture.png` - `path`: 纹理路径,如 `db://assets/textures/NewTexture.png`
- `properties`: 纹理属性(用于 `create` 操作) - `properties`: 纹理属性(用于 `create`/`update` 操作)
- `width`: 宽度 - `type`: 纹理类型(如 `sprite`, `texture`, `raw`(用于 `update`)
- `height`: 高度 - `border`: 九宫格边距数组 `[top, bottom, left, right]` (用于 `update`,仅当 type 为 sprite 时有效)
- `native`: 原生路径 - `subMetas`: (内部使用)
- `width`: 宽度 (用于 `create` 生成占位图)
- `height`: 高度 (用于 `create` 生成占位图)
- `native`: 原生路径
### 18. execute_menu_item ### 18. execute_menu_item

View File

@@ -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 环境下的操作体验。 本次更新不仅修复了制约生产力的材质与资源同步 bug还通过引入 `manage_shader` 和全方位的文档中文化,极大提升了开发者(及 AI 助手)在 Cocos Creator 2.4.x 环境下的操作体验。

167
main.js
View File

@@ -129,6 +129,8 @@ const getToolsList = () => {
id: { type: "string", description: "节点 UUID" }, id: { type: "string", description: "节点 UUID" },
x: { type: "number" }, x: { type: "number" },
y: { type: "number" }, y: { type: "number" },
width: { type: "number" },
height: { type: "number" },
scaleX: { type: "number" }, scaleX: { type: "number" },
scaleY: { type: "number" }, scaleY: { type: "number" },
color: { type: "string", description: "HEX 颜色代码如 #FF0000" }, color: { type: "string", description: "HEX 颜色代码如 #FF0000" },
@@ -354,7 +356,7 @@ const getToolsList = () => {
inputSchema: { inputSchema: {
type: "object", type: "object",
properties: { 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" }, path: { type: "string", description: "纹理路径,如 db://assets/textures/NewTexture.png" },
properties: { type: "object", description: "纹理属性" }, properties: { type: "object", description: "纹理属性" },
}, },
@@ -1630,16 +1632,65 @@ CCProgram fs %{
if (!fs.existsSync(dirPath)) { if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true }); fs.mkdirSync(dirPath, { recursive: true });
} }
// 【修复】Cocos 2.4.x 无法直接用 Editor.assetdb.create 创建带后缀的纹理(会校验内容)
// 我们需要先物理写入一个 1x1 的透明图片,再刷新数据库 // 1. 准备文件内容 (优先使用 properties.content否则使用默认 1x1)
const base64Data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="; let base64Data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
if (properties && properties.content) {
base64Data = properties.content;
}
const buffer = Buffer.from(base64Data, 'base64'); const buffer = Buffer.from(base64Data, 'base64');
try { try {
// 2. 写入物理文件
fs.writeFileSync(absolutePath, buffer); fs.writeFileSync(absolutePath, buffer);
Editor.assetdb.refresh(path, (err) => {
// 3. 刷新该资源以生成 Meta
Editor.assetdb.refresh(path, (err, results) => {
if (err) return callback(err); 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) { } catch (e) {
callback(`Failed to write texture file: ${e.message}`); callback(`Failed to write texture file: ${e.message}`);
@@ -1662,6 +1713,110 @@ CCProgram fs %{
callback(`Texture not found: ${path}`); callback(`Texture not found: ${path}`);
} }
break; 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: default:
callback(`Unknown texture action: ${action}`); callback(`Unknown texture action: ${action}`);
break; break;

View File

@@ -112,13 +112,22 @@ module.exports = {
Editor.log(`[scene-script] Node found: ${node.name}, Current Pos: (${node.x}, ${node.y})`); Editor.log(`[scene-script] Node found: ${node.name}, Current Pos: (${node.x}, ${node.y})`);
if (x !== undefined) { if (x !== undefined) {
node.x = Number(x); // 强制转换确保类型正确 node.x = Number(x);
Editor.log(`[scene-script] Set x to ${node.x}`); Editor.log(`[scene-script] Set x to ${node.x}`);
} }
if (y !== undefined) { if (y !== undefined) {
node.y = Number(y); node.y = Number(y);
Editor.log(`[scene-script] Set y to ${node.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 (scaleX !== undefined) node.scaleX = Number(scaleX);
if (scaleY !== undefined) node.scaleY = Number(scaleY); if (scaleY !== undefined) node.scaleY = Number(scaleY);
if (color) { 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)) { } else if (propertyType && (propertyType.prototype instanceof cc.Component || propertyType === cc.Component || propertyType === cc.Node)) {
// 2. 处理节点或组件引用 // 2. 处理节点或组件引用
const targetNode = findNode(value); const targetNode = findNode(value);