docs: 完善 MCP 工具说明,注入 AI 安全守则并优化资产操作规范
This commit is contained in:
@@ -11,7 +11,8 @@
|
|||||||
2. **技术栈**: 新脚本必须使用 **TypeScript** (`.ts`)。禁止创建新的 `.js` 文件 (除非是构建脚本或测试配置)。
|
2. **技术栈**: 新脚本必须使用 **TypeScript** (`.ts`)。禁止创建新的 `.js` 文件 (除非是构建脚本或测试配置)。
|
||||||
3. **文档**: 所有修改或创建的脚本必须包含详细的 JSDoc 格式注释。
|
3. **文档**: 所有修改或创建的脚本必须包含详细的 JSDoc 格式注释。
|
||||||
4. **架构**: 严禁引入新的架构模式或重型外部库。必须复用现有的 Cocos Creator 管理器和工具类。
|
4. **架构**: 严禁引入新的架构模式或重型外部库。必须复用现有的 Cocos Creator 管理器和工具类。
|
||||||
5. **隔离原则**: 保持 `main.js` (主进程) 与 `scene-script.js` (渲染进程) 的严格职责分离。即使看似方便,也不要在 `main.js` 中直接操作场景节点对象。
|
5. **隔离原则**: 保持 `main.js` (主进程) 与 `scene-script.js` (渲染进程) 的严格职责分离。即便是在主进程中,也应通过 IPC 与场景脚本交互。
|
||||||
|
6. **主体校验规则 (Subject Validation Rule)**: 在对节点、组件或属性进行任何“写”操作之前,AI 必须先验证主体的存在性与类型正确性。严禁基于假设进行操作。
|
||||||
|
|
||||||
## 1. 项目初始化
|
## 1. 项目初始化
|
||||||
|
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -113,7 +113,8 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
|
|||||||
- `id`: 节点 UUID
|
- `id`: 节点 UUID
|
||||||
- `x`, `y`: 坐标
|
- `x`, `y`: 坐标
|
||||||
- `scaleX`, `scaleY`: 缩放值
|
- `scaleX`, `scaleY`: 缩放值
|
||||||
- `color`: HEX 颜色代码(如 #FF0000)(支持撤销操作)
|
- `color`: HEX 颜色代码(如 #FF0000)
|
||||||
|
- **重要提示**: 执行前必须调用 `get_scene_hierarchy` 确保 `id` 有效,防止操作不存在的节点。
|
||||||
|
|
||||||
### 6. open_scene
|
### 6. open_scene
|
||||||
|
|
||||||
@@ -149,7 +150,11 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
|
|||||||
- `action`: 操作类型(`add`, `remove`, `get`, `update`)
|
- `action`: 操作类型(`add`, `remove`, `get`, `update`)
|
||||||
- `componentType`: 组件类型,如 `cc.Sprite`(用于 `add`/`update` 操作)
|
- `componentType`: 组件类型,如 `cc.Sprite`(用于 `add`/`update` 操作)
|
||||||
- `componentId`: 组件 ID(用于 `remove`/`update` 操作)
|
- `componentId`: 组件 ID(用于 `remove`/`update` 操作)
|
||||||
- `properties`: 组件属性(用于 `add`/`update` 操作)。**智能特性**:如果属性期望组件类型但传入节点 UUID,插件会自动查找匹配组件。
|
- `properties`: 组件属性(用于 `add`/`update` 操作)。
|
||||||
|
- **智能特性**:
|
||||||
|
1. 如果属性期望组件类型但传入节点 UUID,插件会自动查找匹配组件。
|
||||||
|
2. 对于资源类属性(如 `cc.Prefab`, `sp.SkeletonData`),传递资源的 UUID,插件会自动处理异步加载与序列化,确保不出现 Type Error。
|
||||||
|
- **操作规则 (Subject Validation Rule)**:赋值或更新前必须确保目标属性在组件上真实存在。
|
||||||
|
|
||||||
### 9. manage_script
|
### 9. manage_script
|
||||||
|
|
||||||
@@ -286,7 +291,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
|
|||||||
|
|
||||||
### 24. manage_vfx
|
### 24. manage_vfx
|
||||||
|
|
||||||
- **描述**: 特效(粒子)管理
|
- **描述**: 特效(粒子)管理。重要提示:操作前必须确认父节点或目标节点的有效性。
|
||||||
- **参数**:
|
- **参数**:
|
||||||
- `action`: 操作类型 (`create`, `update`, `get_info`)
|
- `action`: 操作类型 (`create`, `update`, `get_info`)
|
||||||
- `nodeId`: 节点 UUID (用于 `update`, `get_info`)
|
- `nodeId`: 节点 UUID (用于 `update`, `get_info`)
|
||||||
@@ -368,6 +373,17 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
|
|||||||
- 插件会自动标记场景为"已修改",请注意保存场景
|
- 插件会自动标记场景为"已修改",请注意保存场景
|
||||||
- 不同版本的 Cocos Creator 可能会有 API 差异,请根据实际情况调整
|
- 不同版本的 Cocos Creator 可能会有 API 差异,请根据实际情况调整
|
||||||
|
|
||||||
|
## AI 操作安全守则 (Subject Validation Rule)
|
||||||
|
|
||||||
|
为了保证自动化操作的稳定性,AI 在使用本插件工具时必须遵循以下守则:
|
||||||
|
|
||||||
|
1. **确定性优先**:任何对节点、组件、属性的操作,都必须建立在“主体已确认存在”的基础上。
|
||||||
|
2. **校验流程**:
|
||||||
|
* **节点校验**:操作前必须使用 `get_scene_hierarchy` 确认节点。
|
||||||
|
* **组件校验**:操作组件前必须使用 `get`(通过 `manage_components`)确认组件存在。
|
||||||
|
* **属性校验**:更新属性前必须确认属性名准确无误。
|
||||||
|
3. **禁止假设**:禁止盲目尝试对不存在的对象或属性进行修改。
|
||||||
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
|
|||||||
78
main.js
78
main.js
@@ -91,15 +91,16 @@ const getNewSceneTemplate = () => {
|
|||||||
* @returns {Array<Object>} 工具定义数组
|
* @returns {Array<Object>} 工具定义数组
|
||||||
*/
|
*/
|
||||||
const getToolsList = () => {
|
const getToolsList = () => {
|
||||||
|
const globalPrecautions = "【AI 安全守则】: 1. 执行任何写操作前必须先通过 get_scene_hierarchy 或 manage_components(get) 验证主体存在。 2. 严禁基于假设盲目猜测属性名。 3. 资源属性(如 cc.Prefab)必须通过 UUID 进行赋值。";
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: "get_selected_node",
|
name: "get_selected_node",
|
||||||
description: "获取当前编辑器中选中的节点 ID。建议获取后立即调用 get_scene_hierarchy 确认该节点是否仍存在于当前场景中。",
|
description: `${globalPrecautions} 获取当前编辑器中选中的节点 ID。建议获取后立即调用 get_scene_hierarchy 确认该节点是否仍存在于当前场景中。`,
|
||||||
inputSchema: { type: "object", properties: {} },
|
inputSchema: { type: "object", properties: {} },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "set_node_name",
|
name: "set_node_name",
|
||||||
description: "修改指定节点的名称",
|
description: `${globalPrecautions} 修改指定节点的名称`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -111,17 +112,17 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "save_scene",
|
name: "save_scene",
|
||||||
description: "保存当前场景的修改",
|
description: `${globalPrecautions} 保存当前场景的修改`,
|
||||||
inputSchema: { type: "object", properties: {} },
|
inputSchema: { type: "object", properties: {} },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get_scene_hierarchy",
|
name: "get_scene_hierarchy",
|
||||||
description: "获取当前场景的完整节点树结构(包括 UUID、名称和层级关系)",
|
description: `${globalPrecautions} 获取当前场景的完整节点树结构(包括 UUID、名称和层级关系)`,
|
||||||
inputSchema: { type: "object", properties: {} },
|
inputSchema: { type: "object", properties: {} },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "update_node_transform",
|
name: "update_node_transform",
|
||||||
description: "修改节点的坐标、缩放或颜色。执行前必须调用 get_scene_hierarchy 确保 node ID 有效。",
|
description: `${globalPrecautions} 修改节点的坐标、缩放或颜色。执行前必须调用 get_scene_hierarchy 确保 node ID 有效。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -137,7 +138,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_scene",
|
name: "create_scene",
|
||||||
description: "在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera)。",
|
description: `${globalPrecautions} 在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera)。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -148,7 +149,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_prefab",
|
name: "create_prefab",
|
||||||
description: "将场景中的某个节点保存为预制体资源",
|
description: `${globalPrecautions} 将场景中的某个节点保存为预制体资源`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -160,7 +161,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "open_scene",
|
name: "open_scene",
|
||||||
description: "打开场景文件。注意:这是一个异步且耗时的操作,打开后请等待几秒。重要:如果是新创建或空的场景,请务必先创建并初始化基础节点(Canvas/Camera)。",
|
description: `${globalPrecautions} 打开场景文件。注意:这是一个异步且耗时的操作,打开后请等待几秒。重要:如果是新创建或空的场景,请务必先创建并初始化基础节点(Canvas/Camera)。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -174,7 +175,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_node",
|
name: "create_node",
|
||||||
description: "在当前场景中创建一个新节点。重要提示:1. 如果指定 parentId,必须先调用 get_scene_hierarchy 确保该父节点真实存在。2. 类型说明:'sprite' (100x100 尺寸 + 默认贴图), 'button' (150x50 尺寸 + 深色底图 + Button组件), 'label' (120x40 尺寸 + Label组件), 'empty' (纯空节点)。",
|
description: `${globalPrecautions} 在当前场景中创建一个新节点。重要提示:1. 如果指定 parentId,必须先通过 get_scene_hierarchy 确保该父节点真实存在且未被删除。2. 类型说明:'sprite' (100x100 尺寸 + 默认贴图), 'button' (150x50 尺寸 + 深色底图 + Button组件), 'label' (120x40 尺寸 + Label组件), 'empty' (纯空节点)。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -194,7 +195,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_components",
|
name: "manage_components",
|
||||||
description: "管理节点组件。重要提示:1. 操作前必须调用 get_scene_hierarchy 确认 nodeId 对应的节点仍然存在。2. 添加前先用 'get' 检查是否已存在。3. 添加 cc.Sprite 后必须设置 spriteFrame 属性,否则节点不显示。4. 创建按钮时,请确保目标节点有足够的 width 和 height 作为点击区域。",
|
description: `${globalPrecautions} 管理节点组件。重要提示:1. 操作前必须调用 get_scene_hierarchy 确认 nodeId 对应的节点仍然存在。2. 添加前先用 'get' 检查是否已存在。3. 添加 cc.Sprite 后必须设置 spriteFrame 属性,否则节点不显示。4. 创建按钮时,请确保目标节点有足够的 width 和 height 作为点击区域。5. 赋值或更新属性前,必须确保目标属性在组件上真实存在,严禁盲目操作不存在的属性。6. 对于资源类属性(如 cc.Prefab, sp.SkeletonData),传递资源的 UUID。插件会自动进行异步加载并正确序列化,避免 Inspector 出现 Type Error。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -209,7 +210,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_script",
|
name: "manage_script",
|
||||||
description: "管理脚本文件。注意:创建或修改脚本后,编辑器需要时间进行编译(通常几秒钟)。新脚本在编译完成前无法作为组件添加到节点。建议在 create 后调用 refresh_editor,或等待一段时间后再使用 manage_components。",
|
description: `${globalPrecautions} 管理脚本文件。注意:创建或修改脚本后,编辑器需要时间进行编译(通常几秒钟)。新脚本在编译完成前无法作为组件添加到节点。建议在 create 后调用 refresh_editor,或等待一段时间后再使用 manage_components。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -223,7 +224,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "batch_execute",
|
name: "batch_execute",
|
||||||
description: "批处理执行多个操作",
|
description: `${globalPrecautions} 批处理执行多个操作`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -245,7 +246,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_asset",
|
name: "manage_asset",
|
||||||
description: "管理资源",
|
description: `${globalPrecautions} 管理资源`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -259,7 +260,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "scene_management",
|
name: "scene_management",
|
||||||
description: "场景管理",
|
description: `${globalPrecautions} 场景管理`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -277,7 +278,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prefab_management",
|
name: "prefab_management",
|
||||||
description: "预制体管理",
|
description: `${globalPrecautions} 预制体管理`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -295,7 +296,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_editor",
|
name: "manage_editor",
|
||||||
description: "管理编辑器",
|
description: `${globalPrecautions} 管理编辑器`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -316,7 +317,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find_gameobjects",
|
name: "find_gameobjects",
|
||||||
description: "查找游戏对象",
|
description: `${globalPrecautions} 查找游戏对象`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -328,7 +329,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_material",
|
name: "manage_material",
|
||||||
description: "管理材质",
|
description: `${globalPrecautions} 管理材质`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -341,7 +342,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_texture",
|
name: "manage_texture",
|
||||||
description: "管理纹理",
|
description: `${globalPrecautions} 管理纹理`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -354,7 +355,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "execute_menu_item",
|
name: "execute_menu_item",
|
||||||
description: "执行菜单项",
|
description: `${globalPrecautions} 执行菜单项`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -365,7 +366,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "apply_text_edits",
|
name: "apply_text_edits",
|
||||||
description: "应用文本编辑",
|
description: `${globalPrecautions} 应用文本编辑`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -377,7 +378,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read_console",
|
name: "read_console",
|
||||||
description: "读取控制台",
|
description: `${globalPrecautions} 读取控制台`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -388,7 +389,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "validate_script",
|
name: "validate_script",
|
||||||
description: "验证脚本",
|
description: `${globalPrecautions} 验证脚本`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -399,7 +400,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find_in_file",
|
name: "find_in_file",
|
||||||
description: "在项目中全局搜索文本内容",
|
description: `${globalPrecautions} 在项目中全局搜索文本内容`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -417,7 +418,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_undo",
|
name: "manage_undo",
|
||||||
description: "管理编辑器的撤销和重做历史",
|
description: `${globalPrecautions} 管理编辑器的撤销和重做历史`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -433,7 +434,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_vfx",
|
name: "manage_vfx",
|
||||||
description: "管理全场景特效 (粒子系统)",
|
description: `${globalPrecautions} 管理全场景特效 (粒子系统)。重要提示:在创建或更新前,必须通过 get_scene_hierarchy 或 manage_components 确认父节点或目标节点的有效性。严禁对不存在的对象进行操作。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -469,7 +470,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get_sha",
|
name: "get_sha",
|
||||||
description: "获取指定文件的 SHA-256 哈希值",
|
description: `${globalPrecautions} 获取指定文件的 SHA-256 哈希值`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -480,7 +481,7 @@ const getToolsList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "manage_animation",
|
name: "manage_animation",
|
||||||
description: "管理节点的动画组件",
|
description: `${globalPrecautions} 管理节点的动画组件。重要提示:在执行 play/pause 等操作前,必须先确认节点及其 Animation 组件存在。严禁操作空引用。`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -1570,9 +1571,9 @@ export default class NewScript extends cc.Component {
|
|||||||
'File/Save': 'scene:stash-and-save', // 别名
|
'File/Save': 'scene:stash-and-save', // 别名
|
||||||
'Edit/Undo': 'scene:undo',
|
'Edit/Undo': 'scene:undo',
|
||||||
'Edit/Redo': 'scene:redo',
|
'Edit/Redo': 'scene:redo',
|
||||||
'Edit/Delete': 'scene:delete-selected',
|
'Edit/Delete': 'scene:delete-nodes',
|
||||||
'Delete': 'scene:delete-selected',
|
'Delete': 'scene:delete-nodes',
|
||||||
'delete': 'scene:delete-selected',
|
'delete': 'scene:delete-nodes',
|
||||||
'Node/Create Empty Node': 'scene:create-node-by-classid', // 简化的映射,通常需要参数
|
'Node/Create Empty Node': 'scene:create-node-by-classid', // 简化的映射,通常需要参数
|
||||||
'Project/Build': 'app:build-project',
|
'Project/Build': 'app:build-project',
|
||||||
};
|
};
|
||||||
@@ -1592,8 +1593,19 @@ export default class NewScript extends cc.Component {
|
|||||||
if (menuMap[menuPath]) {
|
if (menuMap[menuPath]) {
|
||||||
const ipcMsg = menuMap[menuPath];
|
const ipcMsg = menuMap[menuPath];
|
||||||
try {
|
try {
|
||||||
Editor.Ipc.sendToMain(ipcMsg);
|
// 获取当前选中的节点进行删除(如果该消息是删除操作)
|
||||||
callback(null, `Menu action triggered: ${menuPath} -> ${ipcMsg}`);
|
if (ipcMsg === 'scene:delete-nodes') {
|
||||||
|
const selection = Editor.Selection.curSelection("node");
|
||||||
|
if (selection.length > 0) {
|
||||||
|
Editor.Ipc.sendToMain(ipcMsg, selection);
|
||||||
|
callback(null, `Menu action triggered: ${menuPath} -> ${ipcMsg} with ${selection.length} nodes`);
|
||||||
|
} else {
|
||||||
|
callback("No nodes selected for deletion");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Editor.Ipc.sendToMain(ipcMsg);
|
||||||
|
callback(null, `Menu action triggered: ${menuPath} -> ${ipcMsg}`);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
callback(`Failed to execute IPC ${ipcMsg}: ${err.message}`);
|
callback(`Failed to execute IPC ${ipcMsg}: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
133
scene-script.js
133
scene-script.js
@@ -298,71 +298,98 @@ module.exports = {
|
|||||||
if (component[key] !== undefined) {
|
if (component[key] !== undefined) {
|
||||||
let finalValue = value;
|
let finalValue = value;
|
||||||
|
|
||||||
// 【关键修复】智能组件引用赋值
|
// 【核心逻辑】智能类型识别与赋值
|
||||||
// 如果属性期望一个组件 (cc.Component子类) 但传入的是节点/UUID,尝试自动获取组件
|
|
||||||
try {
|
try {
|
||||||
// 检查传入值是否是字符串 (可能是 UUID) 或 Node 对象
|
const attrs = (cc.Class.Attr.getClassAttrs && cc.Class.Attr.getClassAttrs(compClass)) || {};
|
||||||
let targetNode = null;
|
let propertyType = attrs[key] ? attrs[key].type : null;
|
||||||
if (typeof value === 'string') {
|
if (!propertyType && attrs[key + '$_$ctor']) {
|
||||||
targetNode = findNode(value);
|
propertyType = attrs[key + '$_$ctor'];
|
||||||
|
|
||||||
if (targetNode) {
|
|
||||||
Editor.log(`[scene-script] Resolved node: ${value} -> ${targetNode.name}`);
|
|
||||||
} else if (value.length > 20) {
|
|
||||||
Editor.warn(`[scene-script] Failed to resolve node: ${value}`);
|
|
||||||
}
|
|
||||||
} else if (value instanceof cc.Node) {
|
|
||||||
targetNode = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetNode) {
|
let isAsset = propertyType && (propertyType.prototype instanceof cc.Asset || propertyType === cc.Asset || propertyType === cc.Prefab || propertyType === cc.SpriteFrame);
|
||||||
// 尝试获取属性定义类型
|
|
||||||
let typeName = null;
|
|
||||||
|
|
||||||
// 优先尝试 getClassAttrs (Cocos 2.x editor environment)
|
// 启发式:如果属性名包含 prefab/sprite/texture 等,且值为 UUID 且不是节点
|
||||||
if (cc.Class.Attr.getClassAttrs) {
|
if (!isAsset && typeof value === 'string' && value.length > 20) {
|
||||||
const attrs = cc.Class.Attr.getClassAttrs(compClass);
|
const lowerKey = key.toLowerCase();
|
||||||
// attrs 是整个属性字典 { name: { type: ... } }
|
const assetKeywords = ['prefab', 'sprite', 'texture', 'material', 'skeleton', 'spine', 'atlas', 'font', 'audio', 'data'];
|
||||||
if (attrs) {
|
if (assetKeywords.some(k => lowerKey.includes(k))) {
|
||||||
if (attrs[key] && attrs[key].type) {
|
if (!findNode(value)) {
|
||||||
typeName = attrs[key].type;
|
isAsset = true;
|
||||||
} else if (attrs[key + '$_$ctor']) {
|
}
|
||||||
// 编辑器环境下,自定义组件类型可能存储在 $_$ctor 后缀中
|
}
|
||||||
typeName = attrs[key + '$_$ctor'];
|
}
|
||||||
|
|
||||||
|
if (isAsset) {
|
||||||
|
// 1. 处理资源引用 (cc.Prefab, cc.SpriteFrame 等)
|
||||||
|
if (typeof value === 'string' && value.length > 20) {
|
||||||
|
// 在场景进程中异步加载资源,这能确保 serialization 时是正确的老格式对象
|
||||||
|
cc.AssetLibrary.loadAsset(value, (err, asset) => {
|
||||||
|
if (!err && asset) {
|
||||||
|
component[key] = asset;
|
||||||
|
Editor.log(`[scene-script] Successfully loaded and assigned asset for ${key}: ${asset.name}`);
|
||||||
|
// 通知编辑器 UI 更新
|
||||||
|
const compIndex = node._components.indexOf(component);
|
||||||
|
if (compIndex !== -1) {
|
||||||
|
Editor.Ipc.sendToPanel('scene', 'scene:set-property', {
|
||||||
|
id: node.uuid,
|
||||||
|
path: `_components.${compIndex}.${key}`,
|
||||||
|
type: 'Object',
|
||||||
|
value: { uuid: value },
|
||||||
|
isSubProp: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Editor.Ipc.sendToMain("scene:dirty");
|
||||||
|
} else {
|
||||||
|
Editor.warn(`[scene-script] Failed to load asset ${value} for ${key}: ${err}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return; // 跳过后续的直接赋值
|
||||||
|
}
|
||||||
|
} else if (propertyType && (propertyType.prototype instanceof cc.Component || propertyType === cc.Component || propertyType === cc.Node)) {
|
||||||
|
// 2. 处理节点或组件引用
|
||||||
|
const targetNode = findNode(value);
|
||||||
|
if (targetNode) {
|
||||||
|
if (propertyType === cc.Node) {
|
||||||
|
finalValue = targetNode;
|
||||||
|
} else {
|
||||||
|
const targetComp = targetNode.getComponent(propertyType);
|
||||||
|
if (targetComp) {
|
||||||
|
finalValue = targetComp;
|
||||||
|
} else {
|
||||||
|
Editor.warn(`[scene-script] Component ${propertyType.name} not found on node ${targetNode.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Editor.log(`[scene-script] Applied Reference for ${key}: ${targetNode.name}`);
|
||||||
|
} else if (value && value.length > 20) {
|
||||||
|
// 如果明确是组件/节点类型但找不到,才报错
|
||||||
|
Editor.warn(`[scene-script] Failed to resolve target node/comp for ${key}: ${value}`);
|
||||||
}
|
}
|
||||||
// 兼容性尝试 getClassAttributes
|
} else {
|
||||||
else if (cc.Class.Attr.getClassAttributes) {
|
// 3. 通用启发式 (找不到类型时的 fallback)
|
||||||
const attrs = cc.Class.Attr.getClassAttributes(compClass, key);
|
if (typeof value === 'string' && value.length > 20) {
|
||||||
if (attrs && attrs.type) {
|
const targetNode = findNode(value);
|
||||||
typeName = attrs.type;
|
if (targetNode) {
|
||||||
}
|
finalValue = targetNode;
|
||||||
}
|
Editor.log(`[scene-script] Heuristic resolved Node for ${key}: ${targetNode.name}`);
|
||||||
|
|
||||||
if (typeName && (typeName.prototype instanceof cc.Component || typeName === cc.Component)) {
|
|
||||||
// 这是一个组件属性
|
|
||||||
const targetComp = targetNode.getComponent(typeName);
|
|
||||||
if (targetComp) {
|
|
||||||
finalValue = targetComp;
|
|
||||||
Editor.log(`[scene-script] Auto-resolved component ${typeName.name} from node ${targetNode.name}`);
|
|
||||||
} else {
|
} else {
|
||||||
Editor.warn(`[scene-script] Component ${typeName.name} not found on node ${targetNode.name}`);
|
// 找不到节点且是 UUID -> 视为资源
|
||||||
}
|
const compIndex = node._components.indexOf(component);
|
||||||
} else if (!typeName) {
|
if (compIndex !== -1) {
|
||||||
// 无法确切知道类型,尝试常见的组件类型推断 (heuristic)
|
Editor.Ipc.sendToPanel('scene', 'scene:set-property', {
|
||||||
const lowerKey = key.toLowerCase();
|
id: node.uuid,
|
||||||
if (lowerKey.includes('label')) {
|
path: `_components.${compIndex}.${key}`,
|
||||||
const l = targetNode.getComponent(cc.Label);
|
type: 'Object',
|
||||||
if (l) finalValue = l;
|
value: { uuid: value },
|
||||||
} else if (lowerKey.includes('sprite')) {
|
isSubProp: false
|
||||||
const s = targetNode.getComponent(cc.Sprite);
|
});
|
||||||
if (s) finalValue = s;
|
Editor.log(`[scene-script] Heuristic resolved Asset via IPC for ${key}: ${value}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 忽略类型检查错误
|
Editor.warn(`[scene-script] Property resolution failed for ${key}: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
component[key] = finalValue;
|
component[key] = finalValue;
|
||||||
|
|||||||
70
注意事项.md
Normal file
70
注意事项.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Cocos Creator 插件开发注意事项
|
||||||
|
|
||||||
|
## 1. 资源与预制体管理
|
||||||
|
|
||||||
|
### 1.1 禁止手动干预 `.meta` 文件
|
||||||
|
* **问题**:手动使用 `manage_asset` 创建或修改以 `.meta` 结尾的文件会导致编辑器报错(如 `Invalid assetpath, must not use .meta as suffix`)。
|
||||||
|
* **原因**:Cocos Creator 资源数据库会自动识别新资源并生成对应的 `.meta` 文件。外部强行介入会破坏资源索引一致性。
|
||||||
|
* **最佳实践**:始终使用高级资源工具(如 `prefab_management`)来处理资源,让编辑器自行管理 `.meta` 文件。
|
||||||
|
|
||||||
|
### 1.2 预制体的正确创建流程
|
||||||
|
* **推荐工具**:使用 `prefab_management` 的 `create` 操作。
|
||||||
|
* **核心逻辑**:该工具会同步处理节点的序列化、db 路径映射以及资源刷新(Refresh),确保预制体及其配套的 `.meta` 文件原子化生成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 脚本属性(Inspector)关联
|
||||||
|
|
||||||
|
### 2.1 路径与 UUID 的区别
|
||||||
|
* **问题**:在 Inspector 面板中,脚本属性(如 `cc.Prefab` 或 `cc.Node`)若通过路径关联,经常会出现 "Type Error"。
|
||||||
|
* **原因**:编辑器 Inspector 期望获得的是引擎内部的对象引用。虽然 `db://` 路径可以指向文件,但在属性赋值层面,编辑器无法自动将字符串路径转换为对应的类实例。
|
||||||
|
* **最佳实践**:
|
||||||
|
1. 通过 `manage_asset -> get_info` 获取资源的 **UUID**。
|
||||||
|
2. 在调用 `manage_components -> update` 时,**优先使用 UUID** 进行赋值。
|
||||||
|
3. UUID 是底层唯一标识,能确保编辑器精准识别资源类型并正确完成引用链接。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 场景同步与编辑器状态
|
||||||
|
|
||||||
|
### 3.1 刷新与编译
|
||||||
|
* **注意事项**:创建新脚本后,必须调用 `manage_editor -> refresh_editor` 或等待几秒钟以触发编译。
|
||||||
|
* **风险**:在脚本编译完成前尝试将其作为组件添加到节点,会导致 `添加组件失败: Cannot read property 'constructor' of null` 或找不到脚本组件的问题。
|
||||||
|
|
||||||
|
### 3.2 节点删除 (IPC 协议)
|
||||||
|
* **正确协议**:应使用 `Editor.Ipc.sendToMain('scene:delete-nodes', uuid_or_array)`。
|
||||||
|
* **注意**:不要误用 `scene:delete-selected`,因为它在某些版本的编辑器底层不接受参数或行为不一致。
|
||||||
|
* **技巧**:在 `mcp-bridge` 中,调用 `execute_menu_item("delete-node:NODE_UUID")` 会走场景脚本的直连删除,而 `execute_menu_item("Delete")` 则会走主进程的 `scene:delete-nodes` 并自动带上选中的节点。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. MCP Bridge 源码补丁说明
|
||||||
|
|
||||||
|
### 4.1 属性解析增强 (Asset 序列化修复)
|
||||||
|
* **改进点**:在 `scene-script.js` 的 `applyProperties` 中通过 `cc.AssetLibrary.loadAsset` 解决了资源属性在 Inspector 报错 "Type Error" 的问题。
|
||||||
|
* **原理**:
|
||||||
|
* **问题根源**:在场景进程中直接将 UUID 字符串赋给资源属性(如 `comp.prefab = "uuid"`),会导致 `.fire` 文件将其保存为纯字符串而非对象格式。编辑器 Inspector 期望的是资源引用结构 `{ "__uuid__": "..." }`,类型不匹配产生 Type Error。
|
||||||
|
* **修复逻辑**:
|
||||||
|
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 字符串。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. AI 操作安全守则 (Subject Validation Rule)
|
||||||
|
|
||||||
|
### 5.1 确定性优先
|
||||||
|
* **核心法则**:任何对节点、组件、属性的操作,都必须建立在 **“主体已确认存在”** 的基础上。
|
||||||
|
* **具体流程**:
|
||||||
|
1. **节点校验**:在操作前必须调用 `get_scene_hierarchy` 确认 `nodeId`。
|
||||||
|
2. **组件校验**:在 `update` 或 `remove` 前必须调用 `manage_components(action: 'get')` 确认目标组件存在。
|
||||||
|
3. **属性校验**:严禁猜测属性名。在 `update` 前,应通过读取脚本定义或 `get` 返回的现有属性列表来确定准确的属性名称。
|
||||||
|
* **禁止行为**:禁止基于假设进行盲目赋值或删除。如果发现对象不存在,应立即报错或尝试重建,而非继续尝试修改。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 常见资源关键字
|
||||||
|
* **资产识别启发式**:当通过 `manage_components` 赋值时,如果属性名包含以下关键字,插件会尝试将其作为 UUID 资源处理:
|
||||||
|
`prefab`, `sprite`, `texture`, `material`, `skeleton`, `spine`, `atlas`, `font`, `audio`, `data`
|
||||||
|
* **建议**:如果资源未正确加载,请检查属性名是否包含以上关键字,或手动确认该 UUID 不属于任何节点。
|
||||||
Reference in New Issue
Block a user