新增 find_references 引用查找工具及 Texture2D 子资源自动解析
- 新增 find-references IPC 处理函数,递归遍历场景节点组件属性查找引用 - 新增 UUID 格式自动规范化(压缩/解压格式全量匹配) - 新增 Texture2D -> SpriteFrame 子资源 UUID 自动解析 - 更新 UPDATE_LOG.md、注意事项.md、README.md 文档
This commit is contained in:
25
README.md
25
README.md
@@ -32,6 +32,7 @@
|
|||||||
- **超时保护**: IPC 通信和指令队列均有超时兜底机制
|
- **超时保护**: IPC 通信和指令队列均有超时兜底机制
|
||||||
- **属性保护**: 组件核心属性黑名单机制,防止 AI 篡改 `node`/`uuid` 等引用导致崩溃
|
- **属性保护**: 组件核心属性黑名单机制,防止 AI 篡改 `node`/`uuid` 等引用导致崩溃
|
||||||
- **AI 容错**: 参数别名映射(`operation`→`action`、`save`→`update`/`write`),兼容大模型幻觉
|
- **AI 容错**: 参数别名映射(`operation`→`action`、`save`→`update`/`write`),兼容大模型幻觉
|
||||||
|
- **引用查找**: 查找场景中所有引用了指定节点或资源的位置,支持 Texture2D → SpriteFrame 子资源自动解析
|
||||||
- **工具说明**: 测试面板提供详细的工具描述和参数说明
|
- **工具说明**: 测试面板提供详细的工具描述和参数说明
|
||||||
|
|
||||||
## 安装与使用
|
## 安装与使用
|
||||||
@@ -367,6 +368,30 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/src/mcp-pro
|
|||||||
- **参数**:
|
- **参数**:
|
||||||
- `path`: 文件路径,如 `db://assets/scripts/Test.ts`
|
- `path`: 文件路径,如 `db://assets/scripts/Test.ts`
|
||||||
|
|
||||||
|
### 27. find_references
|
||||||
|
|
||||||
|
- **描述**: 查找当前场景中引用了指定节点或资源的所有位置。返回引用所在节点、组件类型、属性名等详细信息。
|
||||||
|
- **参数**:
|
||||||
|
- `targetId`: 要查找引用的目标 UUID(节点 UUID 或资源 UUID)
|
||||||
|
- `targetType`: 目标类型(可选,默认 `auto`)
|
||||||
|
- `node`: 查找节点引用
|
||||||
|
- `asset`: 查找资源引用
|
||||||
|
- `auto`: 自动检测类型
|
||||||
|
- **智能特性**:
|
||||||
|
1. **UUID 格式自动规范化**: 自动处理 22 位压缩和 36 位标准 UUID 格式差异。
|
||||||
|
2. **Texture2D 子资源解析**: 传入 Texture2D 的 UUID 时,自动读取 `.meta` 文件提取 SpriteFrame 子资源 UUID,也能查到 `cc.Sprite.spriteFrame` 的引用。
|
||||||
|
- **返回值**:
|
||||||
|
- `targetId`: 查找的目标 UUID
|
||||||
|
- `targetType`: 检测到的类型 (`node` 或 `asset`)
|
||||||
|
- `referenceCount`: 引用总数
|
||||||
|
- `references`: 引用详情数组,每项包含:
|
||||||
|
- `nodeId`: 引用所在节点 UUID
|
||||||
|
- `nodeName`: 节点名称
|
||||||
|
- `componentType`: 组件类型
|
||||||
|
- `componentIndex`: 组件索引
|
||||||
|
- `propertyName`: 属性名
|
||||||
|
- `propertyValue`: 属性值描述
|
||||||
|
|
||||||
## 技术实现
|
## 技术实现
|
||||||
|
|
||||||
### 架构设计
|
### 架构设计
|
||||||
|
|||||||
@@ -326,3 +326,26 @@
|
|||||||
|
|
||||||
- **问题**: `prefabManagement` 的 `create` 分支中,`targetDir` 变量在使用时未被定义,导致创建预制体时目录路径为 `undefined`。
|
- **问题**: `prefabManagement` 的 `create` 分支中,`targetDir` 变量在使用时未被定义,导致创建预制体时目录路径为 `undefined`。
|
||||||
- **修复**: 在使用前从 `prefabPath` 中正确提取目录路径:`const targetDir = prefabPath.substring(0, prefabPath.lastIndexOf("/"))`。
|
- **修复**: 在使用前从 `prefabPath` 中正确提取目录路径:`const targetDir = prefabPath.substring(0, prefabPath.lastIndexOf("/"))`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 引用查找与资源 UUID 自动解析 (2026-03-01)
|
||||||
|
|
||||||
|
### 1. 新增 `find_references` 工具
|
||||||
|
|
||||||
|
- **功能**: 查找当前场景中引用了指定节点或资源的所有位置。支持节点引用 (`cc.Node`) 和资源引用 (`cc.Prefab`, `cc.SpriteFrame`, `sp.SkeletonData` 等)。
|
||||||
|
- **实现**:
|
||||||
|
- 在 `src/scene-script.js` 中新增 `find-references` IPC 处理函数,递归遍历场景所有节点的所有组件属性,检查属性值是否引用了目标对象。
|
||||||
|
- 支持直接属性值、数组元素、`EventHandler.target` 的深层检查。
|
||||||
|
- 在 `src/main.js` 中新增工具定义和路由。
|
||||||
|
- **返回结构**: 包含 `targetId`、`targetType` (检测类型)、`referenceCount` (引用计数) 和详细的 `references` 数组,每项包含引用所在节点、组件类型、属性名等信息。
|
||||||
|
|
||||||
|
### 2. UUID 格式自动规范化
|
||||||
|
|
||||||
|
- **问题**: Cocos Creator 2.x 中 `cc.Asset._uuid` 使用 22 位压缩格式,而 `Editor.assetdb` 返回标准 36 位带连字符格式,直接字符串比较无法匹配。
|
||||||
|
- **修复**: 在 `find-references` 处理函数中通过 `Editor.Utils.UuidUtils` 预计算目标 UUID 的压缩和解压格式,将所有变体存入 `targetVariants` 数组进行全量匹配。
|
||||||
|
|
||||||
|
### 3. Texture2D -> SpriteFrame 子资源 UUID 自动解析
|
||||||
|
|
||||||
|
- **问题**: AI 大模型在查找图片引用时,通常只知道 Texture2D (原图) 的 UUID,而 `cc.Sprite.spriteFrame` 实际引用的是其子资源 SpriteFrame 的 UUID,导致查找结果为空。
|
||||||
|
- **修复**: 在 `src/main.js` 的 `find_references` 路由中,调用 scene-script 前自动读取目标 UUID 对应资源的 `.meta` 文件,提取所有 `subMetas` 中的子资源 UUID (如 SpriteFrame),作为 `additionalIds` 传递给 scene-script。scene-script 将这些额外 UUID 及其压缩/解压变体一并加入匹配列表,实现 "传入 Texture2D UUID 也能查到 SpriteFrame 引用" 的透明体验。
|
||||||
|
|||||||
23
docs/注意事项.md
23
docs/注意事项.md
@@ -158,3 +158,26 @@
|
|||||||
- `scene-script.js` 内部在执行 `manage_components(get)` 序列化时,对于**长度超过 10 的 Array** 会强制截断,返回字面量字符串 `"[Array(X)]"`。
|
- `scene-script.js` 内部在执行 `manage_components(get)` 序列化时,对于**长度超过 10 的 Array** 会强制截断,返回字面量字符串 `"[Array(X)]"`。
|
||||||
- 对于**长度大于 200 的长字符串**,也会强制缩略并追加 `...[Truncated, total length: X]`。
|
- 对于**长度大于 200 的长字符串**,也会强制缩略并追加 `...[Truncated, total length: X]`。
|
||||||
- **应对策略**:如果 AI 看到截断提示,这意味着此处为海量无语义数据,**请勿**尝试盲目通过 `update` 覆盖或还原被截断的字段,极易导致源数据被破坏。请仅修改自己能够完全看清的轻量级属性(如 `name`, `x`, `scale` 等)。
|
- **应对策略**:如果 AI 看到截断提示,这意味着此处为海量无语义数据,**请勿**尝试盲目通过 `update` 覆盖或还原被截断的字段,极易导致源数据被破坏。请仅修改自己能够完全看清的轻量级属性(如 `name`, `x`, `scale` 等)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 引用查找 (`find_references`) 与 UUID 自动解析
|
||||||
|
|
||||||
|
### 11.1 引用查找工具用途
|
||||||
|
|
||||||
|
- **功能**: `find_references` 工具可查找当前场景中所有引用了指定节点或资源的位置,返回引用所在节点、组件类型、属性名等详细信息。
|
||||||
|
- **应用场景**:
|
||||||
|
- 删除节点或预制体前,检查是否有组件属性引用了它,防止脚本报错。
|
||||||
|
- 替换资源(如更换 SpriteFrame)前,快速定位所有使用了旧资源的组件。
|
||||||
|
- 分析场景依赖关系,辅助重构。
|
||||||
|
|
||||||
|
### 11.2 UUID 格式差异与自动规范化
|
||||||
|
|
||||||
|
- **背景**: Cocos Creator 2.x 中 `cc.Asset._uuid` 使用 22 位压缩 UUID 格式,而 `Editor.assetdb` 返回标准 36 位带连字符 UUID,同一资源的两种表示在字符串层面不相等。
|
||||||
|
- **机制**: `find-references` 处理函数通过 `Editor.Utils.UuidUtils.compressUuid/decompressUuid` 自动预计算所有格式变体,存入 `targetVariants` 数组进行全量匹配。无需人工关心 UUID 格式。
|
||||||
|
|
||||||
|
### 11.3 Texture2D 与 SpriteFrame 子资源解析
|
||||||
|
|
||||||
|
- **问题**: AI 传入图片 (Texture2D) 的 UUID 时,`cc.Sprite.spriteFrame` 实际引用的是该图片的子资源 SpriteFrame(具有不同的 UUID),导致直接查找返回空结果。
|
||||||
|
- **解决方案**: `main.js` 在调用 scene-script 前,自动读取目标 UUID 对应的 `.meta` 文件,提取 `subMetas` 中所有子资源 UUID,作为 `additionalIds` 一并传递。scene-script 对这些额外 UUID 同样执行压缩/解压规范化后加入匹配列表。
|
||||||
|
- **效果**: 无论传入 Texture2D UUID 还是 SpriteFrame UUID,`find_references` 均能正确返回所有引用位置。
|
||||||
|
|||||||
49
src/main.js
49
src/main.js
@@ -759,6 +759,22 @@ const getToolsList = () => {
|
|||||||
required: ["action", "nodeId"],
|
required: ["action", "nodeId"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "find_references",
|
||||||
|
description: `查找当前场景中引用了指定节点或资源的所有位置。返回引用所在节点、组件类型、属性名等详细信息。支持查找节点引用(cc.Node)和资源引用(cc.Prefab, cc.SpriteFrame, sp.SkeletonData 等)。`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
targetId: { type: "string", description: "要查找引用的目标 UUID(节点 UUID 或资源 UUID)" },
|
||||||
|
targetType: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["node", "asset", "auto"],
|
||||||
|
description: "目标类型。'node' 查找节点引用,'asset' 查找资源引用,'auto' (默认) 自动检测",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["targetId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1324,6 +1340,39 @@ module.exports = {
|
|||||||
callSceneScriptWithTimeout("mcp-bridge", "manage-vfx", args, callback);
|
callSceneScriptWithTimeout("mcp-bridge", "manage-vfx", args, callback);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "find_references": {
|
||||||
|
// 自动解析 Texture2D → SpriteFrame 子资源 UUID
|
||||||
|
// 确保传入图片 UUID 也能查到使用对应 SpriteFrame 的组件
|
||||||
|
const additionalIds = [];
|
||||||
|
try {
|
||||||
|
const targetUrl = Editor.assetdb.uuidToUrl(args.targetId);
|
||||||
|
if (targetUrl) {
|
||||||
|
const targetFspath = Editor.assetdb.urlToFspath(targetUrl);
|
||||||
|
if (targetFspath) {
|
||||||
|
const metaPath = targetFspath + ".meta";
|
||||||
|
if (fs.existsSync(metaPath)) {
|
||||||
|
const meta = JSON.parse(fs.readFileSync(metaPath, "utf8"));
|
||||||
|
if (meta && meta.subMetas) {
|
||||||
|
for (const subKey of Object.keys(meta.subMetas)) {
|
||||||
|
const sub = meta.subMetas[subKey];
|
||||||
|
if (sub && sub.uuid) {
|
||||||
|
additionalIds.push(sub.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
addLog("warn", `[find_references] 解析子资源 UUID 失败: ${e.message}`);
|
||||||
|
}
|
||||||
|
if (additionalIds.length > 0) {
|
||||||
|
args.additionalIds = additionalIds;
|
||||||
|
}
|
||||||
|
callSceneScriptWithTimeout("mcp-bridge", "find-references", args, callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
callback(`Unknown tool: ${name}`);
|
callback(`Unknown tool: ${name}`);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -970,6 +970,185 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找场景中引用了指定节点或资源的所有位置
|
||||||
|
* @param {Object} event IPC 事件对象
|
||||||
|
* @param {Object} args 参数 (targetId, targetType)
|
||||||
|
*/
|
||||||
|
"find-references": function (event, args) {
|
||||||
|
const { targetId, targetType = "auto", additionalIds } = args;
|
||||||
|
if (!targetId) {
|
||||||
|
if (event.reply) event.reply(new Error("必须提供 targetId 参数"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene = cc.director.getScene();
|
||||||
|
if (!scene) {
|
||||||
|
if (event.reply) event.reply(new Error("场景尚未准备好"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断目标类型:尝试先当节点查找
|
||||||
|
let detectedType = targetType;
|
||||||
|
if (targetType === "auto") {
|
||||||
|
const targetNode = findNode(targetId);
|
||||||
|
detectedType = targetNode ? "node" : "asset";
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
// 规范化 UUID: 同时生成压缩(22位)和解压(36位)格式,确保资源匹配
|
||||||
|
const targetVariants = [targetId];
|
||||||
|
try {
|
||||||
|
if (typeof Editor !== "undefined" && Editor.Utils && Editor.Utils.UuidUtils) {
|
||||||
|
const compressed = Editor.Utils.UuidUtils.compressUuid(targetId);
|
||||||
|
const decompressed = Editor.Utils.UuidUtils.decompressUuid(targetId);
|
||||||
|
if (compressed && compressed !== targetId) targetVariants.push(compressed);
|
||||||
|
if (decompressed && decompressed !== targetId) targetVariants.push(decompressed);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/* 忽略 UUID 转换错误 */
|
||||||
|
}
|
||||||
|
// 合并 main.js 预解析的子资源 UUID (如 Texture2D 对应的 SpriteFrame)
|
||||||
|
if (Array.isArray(additionalIds)) {
|
||||||
|
additionalIds.forEach(function (aid) {
|
||||||
|
if (targetVariants.indexOf(aid) === -1) targetVariants.push(aid);
|
||||||
|
try {
|
||||||
|
if (typeof Editor !== "undefined" && Editor.Utils && Editor.Utils.UuidUtils) {
|
||||||
|
var c = Editor.Utils.UuidUtils.compressUuid(aid);
|
||||||
|
var d = Editor.Utils.UuidUtils.decompressUuid(aid);
|
||||||
|
if (c && targetVariants.indexOf(c) === -1) targetVariants.push(c);
|
||||||
|
if (d && targetVariants.indexOf(d) === -1) targetVariants.push(d);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/* 忽略 */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查一个属性值是否引用了目标
|
||||||
|
* @returns {string|null} 匹配时返回可读描述,否则返回 null
|
||||||
|
*/
|
||||||
|
function checkValue(val) {
|
||||||
|
if (!val || typeof val !== "object") return null;
|
||||||
|
|
||||||
|
if (detectedType === "node") {
|
||||||
|
// 检查节点引用
|
||||||
|
if (val instanceof cc.Node && val.uuid === targetId) {
|
||||||
|
return `节点(${val.name})`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 检查资源引用 (cc.Asset 的 _uuid 属性)
|
||||||
|
if (val instanceof cc.Asset) {
|
||||||
|
var assetUuid = val._uuid || "";
|
||||||
|
for (var vi = 0; vi < targetVariants.length; vi++) {
|
||||||
|
if (assetUuid === targetVariants[vi]) {
|
||||||
|
return `资源(${val.name || assetUuid})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归扫描节点及其子节点
|
||||||
|
*/
|
||||||
|
function scanNode(node) {
|
||||||
|
if (!node || !node.name) return;
|
||||||
|
if (typeof node.name === "string" && (node.name.startsWith("Editor Scene") || node.name === "gizmoRoot")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过目标节点自身
|
||||||
|
if (detectedType === "node" && node.uuid === targetId) {
|
||||||
|
// 不跳过,仍然扫描子节点,但不扫描自身的组件
|
||||||
|
} else {
|
||||||
|
// 遍历该节点的所有组件
|
||||||
|
const comps = node._components || [];
|
||||||
|
for (let ci = 0; ci < comps.length; ci++) {
|
||||||
|
const comp = comps[ci];
|
||||||
|
const compTypeName = cc.js.getClassName(comp) || comp.constructor.name || "Unknown";
|
||||||
|
|
||||||
|
for (const key in comp) {
|
||||||
|
if (typeof comp[key] === "function" || key.startsWith("_")) continue;
|
||||||
|
if (key === "node" || key === "uuid" || key === "name") continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const val = comp[key];
|
||||||
|
if (val === null || val === undefined) continue;
|
||||||
|
|
||||||
|
// 直接值检查
|
||||||
|
const directMatch = checkValue(val);
|
||||||
|
if (directMatch) {
|
||||||
|
results.push({
|
||||||
|
nodeId: node.uuid,
|
||||||
|
nodeName: node.name,
|
||||||
|
componentType: compTypeName,
|
||||||
|
componentIndex: ci,
|
||||||
|
propertyName: key,
|
||||||
|
propertyValue: directMatch,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数组检查 (如 EventHandler 数组、materials 等)
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
for (let ai = 0; ai < val.length; ai++) {
|
||||||
|
const item = val[ai];
|
||||||
|
const arrMatch = checkValue(item);
|
||||||
|
if (arrMatch) {
|
||||||
|
results.push({
|
||||||
|
nodeId: node.uuid,
|
||||||
|
nodeName: node.name,
|
||||||
|
componentType: compTypeName,
|
||||||
|
componentIndex: ci,
|
||||||
|
propertyName: `${key}[${ai}]`,
|
||||||
|
propertyValue: arrMatch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// EventHandler 的 target 属性
|
||||||
|
if (item && item instanceof cc.Component.EventHandler && item.target) {
|
||||||
|
const ehMatch = checkValue(item.target);
|
||||||
|
if (ehMatch) {
|
||||||
|
results.push({
|
||||||
|
nodeId: node.uuid,
|
||||||
|
nodeName: node.name,
|
||||||
|
componentType: compTypeName,
|
||||||
|
componentIndex: ci,
|
||||||
|
propertyName: `${key}[${ai}].target`,
|
||||||
|
propertyValue: ehMatch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 跳过无法访问的属性
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归子节点
|
||||||
|
for (let i = 0; i < node.childrenCount; i++) {
|
||||||
|
scanNode(node.children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanNode(scene);
|
||||||
|
|
||||||
|
if (event.reply) {
|
||||||
|
event.reply(null, {
|
||||||
|
targetId: targetId,
|
||||||
|
targetType: detectedType,
|
||||||
|
referenceCount: results.length,
|
||||||
|
references: results,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除指定的场景节点
|
* 删除指定的场景节点
|
||||||
* @param {Object} event IPC 事件对象
|
* @param {Object} event IPC 事件对象
|
||||||
|
|||||||
Reference in New Issue
Block a user