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 +
- -
-
-
- Port: - - Start -
+
+
+
+ Port: + + Start +
+
+ Auto Start +
+
+ Clear + Copy All +
+
+
- -
- Auto Start -
+
+
+
+
+ + +
+ +
+
-
- Clear - Copy All -
+ +
- -
-
- - -
-
-
- -
-
- - -
-
-
- - -
-
- - -
- -
- 测试工具 - 获取工具列表 - 清空结果 -
- -
-

测试结果:

- -
-
-
-
-
+
+
+ + +
+
+ 测试工具 + 刷新列表 + 清空结果 +
+
+ + +
+
+
+
+ /* 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")); + } + }); + }, };