diff --git a/README.md b/README.md
index 7d5e974..9a8fec7 100644
--- a/README.md
+++ b/README.md
@@ -103,26 +103,13 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `scaleX`, `scaleY`: 缩放值
- `color`: HEX 颜色代码(如 #FF0000)
-### 6. create_scene
-
-- **描述**: 在 assets 目录下创建一个新的场景文件
-- **参数**:
- - `sceneName`: 场景名称
-
-### 7. create_prefab
-
-- **描述**: 将场景中的某个节点保存为预制体资源
-- **参数**:
- - `nodeId`: 节点 UUID
- - `prefabName`: 预制体名称
-
-### 8. open_scene
+### 6. open_scene
- **描述**: 在编辑器中打开指定的场景文件
- **参数**:
- `url`: 场景资源路径,如 `db://assets/NewScene.fire`
-### 9. create_node
+### 7. create_node
- **描述**: 在当前场景中创建一个新节点
- **参数**:
@@ -130,7 +117,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `parentId`: 父节点 UUID (可选,不传则挂在场景根部)
- `type`: 节点预设类型(`empty`, `sprite`, `label`, `canvas`)
-### 10. manage_components
+### 8. manage_components
- **描述**: 管理节点组件
- **参数**:
@@ -140,7 +127,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `componentId`: 组件 ID(用于 `remove` 操作)
- `properties`: 组件属性(用于 `add` 操作)
-### 11. manage_script
+### 9. manage_script
- **描述**: 管理脚本文件,默认创建 TypeScript 脚本
- **参数**:
@@ -150,7 +137,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `name`: 脚本名称(用于 `create` 操作)
- **默认模板**: 当未提供 content 时,会使用 TypeScript 格式的默认模板
-### 12. batch_execute
+### 10. batch_execute
- **描述**: 批处理执行多个操作
- **参数**:
@@ -158,7 +145,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `tool`: 工具名称
- `params`: 工具参数
-### 13. manage_asset
+### 11. manage_asset
- **描述**: 管理资源
- **参数**:
@@ -167,6 +154,24 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `targetPath`: 目标路径(用于 `move` 操作)
- `content`: 资源内容(用于 `create` 操作)
+### 12. scene_management
+
+- **描述**: 场景管理
+- **参数**:
+ - `action`: 操作类型(`create`, `delete`, `duplicate`, `get_info`)
+ - `path`: 场景路径,如 `db://assets/scenes/NewScene.fire`
+ - `targetPath`: 目标路径(用于 `duplicate` 操作)
+ - `name`: 场景名称(用于 `create` 操作)
+
+### 13. prefab_management
+
+- **描述**: 预制体管理
+- **参数**:
+ - `action`: 操作类型(`create`, `update`, `instantiate`, `get_info`)
+ - `path`: 预制体路径,如 `db://assets/prefabs/NewPrefab.prefab`
+ - `nodeId`: 节点 ID(用于 `create` 和 `update` 操作)
+ - `parentId`: 父节点 ID(用于 `instantiate` 操作)
+
## 技术实现
### 架构设计
diff --git a/main.js b/main.js
index 2204b86..b83122b 100644
--- a/main.js
+++ b/main.js
@@ -5,6 +5,7 @@ const path = require("path");
let logBuffer = []; // 存储所有日志
let mcpServer = null;
+let isSceneBusy = false;
let serverConfig = {
port: 3456,
active: false,
@@ -230,9 +231,36 @@ const getToolsList = () => {
required: ["action", "path"],
},
},
+ {
+ name: "scene_management",
+ description: "场景管理",
+ inputSchema: {
+ type: "object",
+ properties: {
+ action: { type: "string", enum: ["create", "delete", "duplicate", "get_info"], description: "操作类型" },
+ path: { type: "string", description: "场景路径,如 db://assets/scenes/NewScene.fire" },
+ targetPath: { type: "string", description: "目标路径 (用于 duplicate 操作)" },
+ name: { type: "string", description: "场景名称 (用于 create 操作)" },
+ },
+ required: ["action", "path"],
+ },
+ },
+ {
+ name: "prefab_management",
+ description: "预制体管理",
+ inputSchema: {
+ type: "object",
+ properties: {
+ action: { type: "string", enum: ["create", "update", "instantiate", "get_info"], description: "操作类型" },
+ path: { type: "string", description: "预制体路径,如 db://assets/prefabs/NewPrefab.prefab" },
+ nodeId: { type: "string", description: "节点 ID (用于 create 操作)" },
+ parentId: { type: "string", description: "父节点 ID (用于 instantiate 操作)" },
+ },
+ required: ["action", "path"],
+ },
+ },
];
};
-let isSceneBusy = false;
module.exports = {
"scene-script": "scene-script.js",
@@ -441,6 +469,14 @@ module.exports = {
this.manageAsset(args, callback);
break;
+ case "scene_management":
+ this.sceneManagement(args, callback);
+ break;
+
+ case "prefab_management":
+ this.prefabManagement(args, callback);
+ break;
+
default:
callback(`Unknown tool: ${name}`);
break;
@@ -592,9 +628,142 @@ export default class NewScript extends cc.Component {
default:
callback(`Unknown asset action: ${action}`);
break;
- }
- },
- // 暴露给 MCP 或面板的 API 封装
+ }
+ },
+
+ // 场景管理
+ sceneManagement(args, callback) {
+ const { action, path, targetPath, name } = args;
+
+ switch (action) {
+ case "create":
+ if (Editor.assetdb.exists(path)) {
+ return callback(`Scene already exists at ${path}`);
+ }
+ // 确保父目录存在
+ const fs = require('fs');
+ const pathModule = require('path');
+ const absolutePath = Editor.assetdb.urlToFspath(path);
+ const dirPath = pathModule.dirname(absolutePath);
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ Editor.assetdb.create(path, getNewSceneTemplate(), (err) => {
+ callback(err, err ? null : `Scene created at ${path}`);
+ });
+ break;
+
+ case "delete":
+ if (!Editor.assetdb.exists(path)) {
+ return callback(`Scene not found at ${path}`);
+ }
+ Editor.assetdb.delete([path], (err) => {
+ callback(err, err ? null : `Scene deleted at ${path}`);
+ });
+ break;
+
+ case "duplicate":
+ if (!Editor.assetdb.exists(path)) {
+ return callback(`Scene not found at ${path}`);
+ }
+ if (!targetPath) {
+ return callback(`Target path is required for duplicate operation`);
+ }
+ if (Editor.assetdb.exists(targetPath)) {
+ return callback(`Target scene already exists at ${targetPath}`);
+ }
+ // 读取原场景内容
+ Editor.assetdb.loadAny(path, (err, content) => {
+ if (err) {
+ return callback(`Failed to read scene: ${err}`);
+ }
+ // 确保目标目录存在
+ const fs = require('fs');
+ const pathModule = require('path');
+ const targetAbsolutePath = Editor.assetdb.urlToFspath(targetPath);
+ const targetDirPath = pathModule.dirname(targetAbsolutePath);
+ if (!fs.existsSync(targetDirPath)) {
+ fs.mkdirSync(targetDirPath, { recursive: true });
+ }
+ // 创建复制的场景
+ Editor.assetdb.create(targetPath, content, (err) => {
+ callback(err, err ? null : `Scene duplicated from ${path} to ${targetPath}`);
+ });
+ });
+ break;
+
+ case "get_info":
+ Editor.assetdb.queryInfoByUuid(Editor.assetdb.urlToUuid(path), (err, info) => {
+ callback(err, err ? null : info);
+ });
+ break;
+
+ default:
+ callback(`Unknown scene action: ${action}`);
+ break;
+ }
+ },
+
+ // 预制体管理
+ prefabManagement(args, callback) {
+ const { action, path, nodeId, parentId } = args;
+
+ switch (action) {
+ case "create":
+ if (!nodeId) {
+ return callback(`Node ID is required for create operation`);
+ }
+ if (Editor.assetdb.exists(path)) {
+ return callback(`Prefab already exists at ${path}`);
+ }
+ // 确保父目录存在
+ const fs = require('fs');
+ const pathModule = require('path');
+ const absolutePath = Editor.assetdb.urlToFspath(path);
+ const dirPath = pathModule.dirname(absolutePath);
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ // 从节点创建预制体
+ Editor.Ipc.sendToMain("scene:create-prefab", nodeId, path);
+ callback(null, `Command sent: Creating prefab from node ${nodeId} at ${path}`);
+ break;
+
+ case "update":
+ if (!nodeId) {
+ return callback(`Node ID is required for update operation`);
+ }
+ if (!Editor.assetdb.exists(path)) {
+ return callback(`Prefab not found at ${path}`);
+ }
+ // 更新预制体
+ Editor.Ipc.sendToMain("scene:update-prefab", nodeId, path);
+ callback(null, `Command sent: Updating prefab ${path} from node ${nodeId}`);
+ break;
+
+ case "instantiate":
+ if (!Editor.assetdb.exists(path)) {
+ return callback(`Prefab not found at ${path}`);
+ }
+ // 实例化预制体
+ Editor.Scene.callSceneScript("mcp-bridge", "instantiate-prefab", {
+ prefabPath: path,
+ parentId: parentId
+ }, callback);
+ break;
+
+ case "get_info":
+ Editor.assetdb.queryInfoByUuid(Editor.assetdb.urlToUuid(path), (err, info) => {
+ callback(err, err ? null : info);
+ });
+ break;
+
+ default:
+ callback(`Unknown prefab action: ${action}`);
+ break;
+ }
+ },
+ // 暴露给 MCP 或面板的 API 封装
messages: {
"open-test-panel"() {
Editor.Panel.open("mcp-bridge");
diff --git a/panel/index.html b/panel/index.html
index 34f718c..23176a4 100644
--- a/panel/index.html
+++ b/panel/index.html
@@ -1,359 +1,93 @@
-
-
- Main
- Tool Test
-
+
+ Main
+ Tool Test
+
-
-
+ /* Test Panel */
+ .test-layout { display: flex; flex: 1; min-height: 0; }
+ .left-panel { width: 250px; min-width: 150px; max-width: 500px; display: flex; flex-direction: column; flex-shrink: 0; min-height: 0; }
+ .resizer { width: 6px; cursor: col-resize; background: #1a1a1a; flex-shrink: 0; }
+ .resizer:hover { background: #4CAF50; }
+ .right-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; padding-left: 5px; }
+ .tools-list { flex: 1; background: #222; border: 1px solid #444; overflow-y: auto; margin-top: 5px; }
+ .tool-item { padding: 6px; border-bottom: 1px solid #333; cursor: pointer; font-size: 11px; }
+ .tool-item:hover { background: #444; }
+ .flex-v { display: flex; flex-direction: column; flex: 1; min-height: 0; }
+ textarea { width: 100%; background: #222; color: #ccc; border: 1px solid #444; padding: 5px; font-family: monospace; resize: none; }
+ #toolParams { height: 120px; flex-shrink: 0; }
+ #resultContent { flex: 1; }
+ .button-group { display: flex; gap: 5px; padding: 5px 0; }
+ label { font-size: 11px; color: #888; margin: 4px 0; }
+
\ No newline at end of file
diff --git a/panel/index.js b/panel/index.js
index f427e82..29ad429 100644
--- a/panel/index.js
+++ b/panel/index.js
@@ -1,333 +1,152 @@
"use strict";
-
const fs = require("fs");
Editor.Panel.extend({
- style: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
- template: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
+ style: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
+ template: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
- messages: {
- "mcp-bridge:on-log"(event, log) {
- this.renderLog(log);
- },
- "mcp-bridge:state-changed"(event, config) {
- this.updateUI(config.active);
- },
- },
+ messages: {
+ "mcp-bridge:on-log"(event, log) { this.renderLog(log); },
+ "mcp-bridge:state-changed"(event, config) { this.updateUI(config.active); }
+ },
- ready() {
- const portInput = this.shadowRoot.querySelector("#portInput");
- const btnToggle = this.shadowRoot.querySelector("#btnToggle");
- const autoStartCheck = this.shadowRoot.querySelector("#autoStartCheck");
- const btnClear = this.shadowRoot.querySelector("#btnClear");
- const btnCopy = this.shadowRoot.querySelector("#btnCopy");
- const logView = this.shadowRoot.querySelector("#logConsole");
+ ready() {
+ const root = this.shadowRoot;
+ // 获取元素
+ const els = {
+ port: root.querySelector("#portInput"),
+ btnToggle: root.querySelector("#btnToggle"),
+ autoStart: root.querySelector("#autoStartCheck"),
+ logView: root.querySelector("#logConsole"),
+ tabMain: root.querySelector("#tabMain"),
+ tabTest: root.querySelector("#tabTest"),
+ panelMain: root.querySelector("#panelMain"),
+ panelTest: root.querySelector("#panelTest"),
+ toolName: root.querySelector("#toolName"),
+ toolParams: root.querySelector("#toolParams"),
+ toolsList: root.querySelector("#toolsList"),
+ testBtn: root.querySelector("#testBtn"),
+ listBtn: root.querySelector("#listToolsBtn"),
+ clearBtn: root.querySelector("#clearTestBtn"),
+ result: root.querySelector("#resultContent"),
+ left: root.querySelector("#testLeftPanel"),
+ resizer: root.querySelector("#testResizer")
+ };
- // 标签页元素
- const tabMain = this.shadowRoot.querySelector("#tabMain");
- const tabTest = this.shadowRoot.querySelector("#tabTest");
- const panelMain = this.shadowRoot.querySelector("#panelMain");
- const panelTest = this.shadowRoot.querySelector("#panelTest");
+ // 1. 初始化状态
+ Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
+ if (data) {
+ els.port.value = data.config.port;
+ els.autoStart.value = data.autoStart;
+ this.updateUI(data.config.active);
+ els.logView.innerHTML = "";
+ data.logs.forEach(l => this.renderLog(l));
+ }
+ });
- // 测试面板元素
- const toolNameInput = this.shadowRoot.querySelector("#toolName");
- const toolParamsTextarea = this.shadowRoot.querySelector("#toolParams");
- const toolsList = this.shadowRoot.querySelector("#toolsList");
- const testBtn = this.shadowRoot.querySelector("#testBtn");
- const listToolsBtn = this.shadowRoot.querySelector("#listToolsBtn");
- const clearBtn = this.shadowRoot.querySelector("#clearBtn");
- const resultContent = this.shadowRoot.querySelector("#resultContent");
+ // 2. 标签切换
+ els.tabMain.addEventListener("confirm", () => {
+ els.tabMain.classList.add("active"); els.tabTest.classList.remove("active");
+ els.panelMain.classList.add("active"); els.panelTest.classList.remove("active");
+ });
+ els.tabTest.addEventListener("confirm", () => {
+ els.tabTest.classList.add("active"); els.tabMain.classList.remove("active");
+ els.panelTest.classList.add("active"); els.panelMain.classList.remove("active");
+ this.fetchTools(els);
+ });
- let tools = [];
- const API_BASE = 'http://localhost:3456';
+ // 3. 基础功能
+ els.btnToggle.addEventListener("confirm", () => {
+ Editor.Ipc.sendToMain("mcp-bridge:toggle-server", parseInt(els.port.value));
+ });
+ root.querySelector("#btnClear").addEventListener("confirm", () => {
+ els.logView.innerHTML = ""; Editor.Ipc.sendToMain("mcp-bridge:clear-logs");
+ });
+ root.querySelector("#btnCopy").addEventListener("confirm", () => {
+ require("electron").clipboard.writeText(els.logView.innerText);
+ Editor.success("Logs Copied");
+ });
+ els.autoStart.addEventListener("change", (e) => {
+ Editor.Ipc.sendToMain("mcp-bridge:set-auto-start", e.target.value);
+ });
- // 初始化
- Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
- if (data) {
- portInput.value = data.config.port;
- this.updateUI(data.config.active);
- data.logs.forEach((log) => this.renderLog(log));
- }
- });
+ // 4. 测试页功能
+ els.listBtn.addEventListener("confirm", () => this.fetchTools(els));
+ els.clearBtn.addEventListener("confirm", () => { els.result.value = ""; });
+ els.testBtn.addEventListener("confirm", () => this.runTest(els));
- // 标签页切换
- tabMain.addEventListener("confirm", () => {
- tabMain.classList.add("active");
- tabTest.classList.remove("active");
- panelMain.classList.add("active");
- panelTest.classList.remove("active");
- });
+ // 5. 【修复】拖拽逻辑
+ if (els.resizer && els.left) {
+ els.resizer.addEventListener('mousedown', (e) => {
+ e.preventDefault();
+ const startX = e.clientX;
+ const startW = els.left.offsetWidth;
+ const onMove = (ev) => { els.left.style.width = (startW + (ev.clientX - startX)) + "px"; };
+ const onUp = () => {
+ document.removeEventListener('mousemove', onMove);
+ document.removeEventListener('mouseup', onUp);
+ document.body.style.cursor = 'default';
+ };
+ document.addEventListener('mousemove', onMove);
+ document.addEventListener('mouseup', onUp);
+ document.body.style.cursor = 'col-resize';
+ });
+ }
+ },
- tabTest.addEventListener("confirm", () => {
- tabTest.classList.add("active");
- tabMain.classList.remove("active");
- panelTest.classList.add("active");
- panelMain.classList.remove("active");
- // 自动获取工具列表
- this.getToolsList();
- });
+ fetchTools(els) {
+ const url = `http://localhost:${els.port.value}/list-tools`;
+ fetch(url).then(r => r.json()).then(data => {
+ els.toolsList.innerHTML = "";
+ data.tools.forEach(t => {
+ const item = document.createElement('div');
+ item.className = 'tool-item';
+ item.textContent = t.name;
+ item.onclick = () => {
+ els.toolName.value = t.name;
+ els.toolParams.value = JSON.stringify(this.getExample(t.name), null, 2);
+ };
+ els.toolsList.appendChild(item);
+ });
+ els.result.value = `Loaded ${data.tools.length} tools.`;
+ }).catch(e => { els.result.value = "Error: " + e.message; });
+ },
- btnToggle.addEventListener("confirm", () => {
- Editor.Ipc.sendToMain("mcp-bridge:toggle-server", parseInt(portInput.value));
- });
+ runTest(els) {
+ const url = `http://localhost:${els.port.value}/call-tool`;
+ const body = { name: els.toolName.value, arguments: JSON.parse(els.toolParams.value || "{}") };
+ els.result.value = "Testing...";
+ fetch(url, { method: 'POST', body: JSON.stringify(body) })
+ .then(r => r.json())
+ .then(d => { els.result.value = JSON.stringify(d, null, 2); })
+ .catch(e => { els.result.value = "Error: " + e.message; });
+ },
- btnClear.addEventListener("confirm", () => {
- logView.innerHTML = "";
- Editor.Ipc.sendToMain("mcp-bridge:clear-logs");
- });
+ getExample(name) {
+ const examples = {
+ "set_node_name": { "id": "UUID", "newName": "Hello" },
+ "update_node_transform": { "id": "UUID", "x": 0, "y": 0, "color": "#FF0000" },
+ "create_node": { "name": "Node", "type": "sprite", "parentId": "" },
+ "open_scene": { "url": "db://assets/Scene.fire" }
+ };
+ return examples[name] || {};
+ },
- btnCopy.addEventListener("confirm", () => {
- require("electron").clipboard.writeText(logView.innerText);
- Editor.success("All logs copied!");
- });
+ renderLog(log) {
+ const view = this.shadowRoot.querySelector("#logConsole");
+ if (!view) return;
+ const atBottom = view.scrollHeight - view.scrollTop <= view.clientHeight + 50;
+ const el = document.createElement("div");
+ el.className = `log-item ${log.type}`;
+ el.innerHTML = `${log.time}${log.content}`;
+ view.appendChild(el);
+ if (atBottom) view.scrollTop = view.scrollHeight;
+ },
- Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
- if (data) {
- portInput.value = data.config.port;
- this.updateUI(data.config.active);
-
- // 设置自动启动复选框状态
- autoStartCheck.value = data.autoStart;
-
- data.logs.forEach((log) => this.renderLog(log));
- }
- });
-
- autoStartCheck.addEventListener("change", (event) => {
- // event.target.value 在 ui-checkbox 中是布尔值
- Editor.Ipc.sendToMain("mcp-bridge:set-auto-start", event.target.value);
- });
-
- // 测试面板事件
- testBtn.addEventListener("confirm", () => this.testTool());
- listToolsBtn.addEventListener("confirm", () => this.getToolsList());
- clearBtn.addEventListener("confirm", () => this.clearResult());
-
- // 获取工具列表
- this.getToolsList = function() {
- this.showResult('获取工具列表中...');
-
- fetch(`${API_BASE}/list-tools`)
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- if (data.tools) {
- tools = data.tools;
- this.displayToolsList(tools);
- this.showResult(`成功获取 ${tools.length} 个工具`, 'success');
- } else {
- this.showResult('获取工具列表失败:未找到工具数据', 'error');
- }
- })
- .catch(error => {
- this.showResult(`获取工具列表失败:${error.message}`, 'error');
- });
- };
-
- // 显示工具列表
- this.displayToolsList = function(tools) {
- toolsList.innerHTML = '';
-
- tools.forEach(tool => {
- const toolItem = document.createElement('div');
- toolItem.className = 'tool-item';
- toolItem.textContent = `${tool.name} - ${tool.description}`;
- toolItem.addEventListener('click', () => {
- toolNameInput.value = tool.name;
- // 尝试填充示例参数
- this.fillExampleParams(tool);
- });
- toolsList.appendChild(toolItem);
- });
- };
-
- // 填充示例参数
- this.fillExampleParams = function(tool) {
- let exampleParams = {};
-
- switch (tool.name) {
- case 'get_selected_node':
- case 'save_scene':
- case 'get_scene_hierarchy':
- exampleParams = {};
- break;
-
- case 'set_node_name':
- exampleParams = {
- "id": "节点UUID",
- "newName": "新节点名称"
- };
- break;
-
- case 'update_node_transform':
- exampleParams = {
- "id": "节点UUID",
- "x": 100,
- "y": 100,
- "scaleX": 1,
- "scaleY": 1
- };
- break;
-
- case 'create_scene':
- exampleParams = {
- "sceneName": "NewScene"
- };
- break;
-
- case 'create_prefab':
- exampleParams = {
- "nodeId": "节点UUID",
- "prefabName": "NewPrefab"
- };
- break;
-
- case 'open_scene':
- exampleParams = {
- "url": "db://assets/NewScene.fire"
- };
- break;
-
- case 'create_node':
- exampleParams = {
- "name": "NewNode",
- "parentId": "父节点UUID",
- "type": "empty"
- };
- break;
-
- case 'manage_components':
- exampleParams = {
- "nodeId": "节点UUID",
- "action": "add",
- "componentType": "cc.Button"
- };
- break;
-
- case 'manage_script':
- exampleParams = {
- "action": "create",
- "path": "db://assets/scripts/TestScript.ts",
- "content": "const { ccclass, property } = cc._decorator;\n\n@ccclass\nexport default class TestScript extends cc.Component {\n // LIFE-CYCLE CALLBACKS:\n\n onLoad () {}\n\n start () {}\n\n update (dt) {}\n}"
- };
- break;
-
- case 'batch_execute':
- exampleParams = {
- "operations": [
- {
- "tool": "get_selected_node",
- "params": {}
- }
- ]
- };
- break;
-
- case 'manage_asset':
- exampleParams = {
- "action": "create",
- "path": "db://assets/test.txt",
- "content": "Hello, MCP!"
- };
- break;
- }
-
- toolParamsTextarea.value = JSON.stringify(exampleParams, null, 2);
- };
-
- // 测试工具
- this.testTool = function() {
- const toolName = toolNameInput.value.trim();
- const toolParamsStr = toolParamsTextarea.value.trim();
-
- if (!toolName) {
- this.showResult('请输入工具名称', 'error');
- return;
- }
-
- let toolParams;
- try {
- toolParams = toolParamsStr ? JSON.parse(toolParamsStr) : {};
- } catch (error) {
- this.showResult(`参数格式错误:${error.message}`, 'error');
- return;
- }
-
- this.showResult('测试工具中...');
-
- fetch(`${API_BASE}/call-tool`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- name: toolName,
- arguments: toolParams
- })
- })
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- if (data.error) {
- this.showResult(`测试失败:${data.error}`, 'error');
- } else {
- this.showResult(JSON.stringify(data, null, 2), 'success');
- }
- })
- .catch(error => {
- this.showResult(`测试失败:${error.message}`, 'error');
- });
- };
-
- // 显示结果
- this.showResult = function(message, type = 'info') {
- resultContent.value = message;
-
- // 移除旧样式
- resultContent.className = '';
-
- // 添加新样式
- if (type === 'error' || type === 'success') {
- resultContent.className = type;
- }
- };
-
- // 清空结果
- this.clearResult = function() {
- this.showResult('点击"测试工具"按钮开始测试');
- };
- },
-
- renderLog(log) {
- const logView = this.shadowRoot.querySelector("#logConsole");
- if (!logView) return;
-
- // 记录当前滚动条位置
- const isAtBottom = logView.scrollHeight - logView.scrollTop <= logView.clientHeight + 50;
-
- const el = document.createElement("div");
- el.className = `log-item ${log.type}`;
- el.innerHTML = `${log.time}${log.content}`;
- logView.appendChild(el);
-
- // 如果用户正在向上翻看,不自动滚动;否则自动滚到底部
- if (isAtBottom) {
- logView.scrollTop = logView.scrollHeight;
- }
- },
-
- updateUI(isActive) {
- const btnToggle = this.shadowRoot.querySelector("#btnToggle");
- if (!btnToggle) return;
- btnToggle.innerText = isActive ? "Stop" : "Start";
- btnToggle.style.backgroundColor = isActive ? "#aa4444" : "#44aa44";
- },
-});
+ updateUI(active) {
+ const btn = this.shadowRoot.querySelector("#btnToggle");
+ if (!btn) return;
+ btn.innerText = active ? "Stop" : "Start";
+ btn.style.backgroundColor = active ? "#aa4444" : "#44aa44";
+ }
+});
\ No newline at end of file
diff --git a/scene-script.js b/scene-script.js
index f28bddb..28a0f01 100644
--- a/scene-script.js
+++ b/scene-script.js
@@ -268,4 +268,50 @@ module.exports = {
return properties;
},
+
+ "instantiate-prefab": function (event, args) {
+ const { prefabPath, parentId } = args;
+ const scene = cc.director.getScene();
+
+ if (!scene || !cc.director.getRunningScene()) {
+ if (event.reply) event.reply(new Error("Scene not ready or loading."));
+ return;
+ }
+
+ // 加载预制体资源
+ cc.loader.loadRes(prefabPath.replace("db://assets/", "").replace(".prefab", ""), cc.Prefab, (err, prefab) => {
+ if (err) {
+ if (event.reply) event.reply(new Error(`Failed to load prefab: ${err.message}`));
+ return;
+ }
+
+ // 实例化预制体
+ const instance = cc.instantiate(prefab);
+ if (!instance) {
+ if (event.reply) event.reply(new Error("Failed to instantiate prefab"));
+ return;
+ }
+
+ // 设置父节点
+ let parent = parentId ? cc.engine.getInstanceById(parentId) : scene;
+ if (parent) {
+ instance.parent = parent;
+
+ // 通知场景变脏
+ Editor.Ipc.sendToMain("scene:dirty");
+
+ // 通知 UI 刷新
+ setTimeout(() => {
+ Editor.Ipc.sendToAll("scene:node-created", {
+ uuid: instance.uuid,
+ parentUuid: parent.uuid,
+ });
+ }, 10);
+
+ if (event.reply) event.reply(null, `Prefab instantiated successfully with UUID: ${instance.uuid}`);
+ } else {
+ if (event.reply) event.reply(new Error("Parent node not found"));
+ }
+ });
+ },
};