Files
mcp-bridge/main.js
火焰库拉 f901bcc38f ```
feat(mcp-bridge): 添加场景和节点创建功能

- 新增 getNewSceneTemplate 函数用于生成标准场景模板
- 实现 create_scene 工具用于在 assets 目录下创建新场景文件
- 实现 create_prefab 工具用于将节点保存为预制体资源
- 实现 open_scene 工具用于在编辑器中打开指定场景
- 实现 create_node 工具用于在当前场景中创建新节点
- 在 scene-script.js 中添加 create-node 处理逻辑
- 支持创建不同类型的节点(空节点、精灵、标签等)
- 添加 UUID 生成的多种兼容方案
```
2026-01-29 14:26:28 +08:00

357 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
const http = require("http");
const path = require("path");
const getNewSceneTemplate = () => {
// 尝试获取 UUID 生成函数
let newId = "";
if (Editor.Utils && Editor.Utils.uuid) {
newId = Editor.Utils.uuid();
} else if (Editor.Utils && Editor.Utils.UuidUtils && Editor.Utils.UuidUtils.uuid) {
newId = Editor.Utils.UuidUtils.uuid();
} else {
// 兜底方案:如果找不到编辑器 API生成一个随机字符串
newId = Math.random().toString(36).substring(2, 15);
}
const sceneData = [
{
__type__: "cc.SceneAsset",
_name: "",
_objFlags: 0,
_native: "",
scene: { __id__: 1 },
},
{
__id__: 1,
__type__: "cc.Scene",
_name: "",
_objFlags: 0,
_parent: null,
_children: [],
_active: true,
_level: 0,
_components: [],
autoReleaseAssets: false,
_id: newId,
},
];
return JSON.stringify(sceneData);
};
module.exports = {
"scene-script": "scene-script.js",
load() {
// 插件加载时启动一个微型服务器供 MCP 使用 (默认端口 3000)
this.startMcpServer();
},
unload() {
if (this.server) this.server.close();
},
// 暴露给 MCP 或面板的 API 封装
messages: {
"open-test-panel"() {
Editor.Panel.open("mcp-bridge");
},
// 获取当前选中节点信息
"get-selected-info"(event) {
let selection = Editor.Selection.curSelection("node");
if (event) event.reply(null, selection);
return selection;
},
// 修改场景中的节点(需要通过 scene-script
"set-node-property"(event, args) {
Editor.log("Calling scene script with:", args); // 打印日志确认 main 进程收到了面板的消息
// 确保第一个参数 'mcp-bridge' 和 package.json 的 name 一致
Editor.Scene.callSceneScript("mcp-bridge", "set-property", args, (err, result) => {
if (err) {
Editor.error("Scene Script Error:", err);
}
if (event && event.reply) {
event.reply(err, result);
}
});
},
},
// 简易 MCP 桥接服务器
startMcpServer() {
this.server = http.createServer((req, res) => {
// 设置 CORS 方便调试
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Content-Type", "application/json");
if (req.method === "OPTIONS") {
res.end();
return;
}
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
try {
// 简单的路由处理
if (req.url === "/list-tools" && req.method === "GET") {
// 1. 返回工具定义 (符合 MCP 规范)
const tools = [
{
name: "get_selected_node",
description: "获取当前编辑器中选中的节点 ID",
inputSchema: { type: "object", properties: {} },
},
{
name: "set_node_name",
description: "修改指定节点的名称",
inputSchema: {
type: "object",
properties: {
id: { type: "string", description: "节点的 UUID" },
newName: { type: "string", description: "新的节点名称" },
},
required: ["id", "newName"],
},
},
{
name: "save_scene",
description: "保存当前场景的修改",
inputSchema: { type: "object", properties: {} },
},
{
name: "get_scene_hierarchy",
description: "获取当前场景的完整节点树结构(包括 UUID、名称和层级关系",
inputSchema: { type: "object", properties: {} },
},
{
name: "update_node_transform",
description: "修改节点的坐标、缩放或颜色",
inputSchema: {
type: "object",
properties: {
id: { type: "string", description: "节点 UUID" },
x: { type: "number" },
y: { type: "number" },
scaleX: { type: "number" },
scaleY: { type: "number" },
color: { type: "string", description: "HEX 颜色代码如 #FF0000" },
},
required: ["id"],
},
},
{
name: "create_scene",
description: "在 assets 目录下创建一个新的场景文件",
inputSchema: {
type: "object",
properties: {
sceneName: { type: "string", description: "场景名称" },
},
required: ["sceneName"],
},
},
{
name: "create_prefab",
description: "将场景中的某个节点保存为预制体资源",
inputSchema: {
type: "object",
properties: {
nodeId: { type: "string", description: "节点 UUID" },
prefabName: { type: "string", description: "预制体名称" },
},
required: ["nodeId", "prefabName"],
},
},
{
name: "open_scene",
description: "在编辑器中打开指定的场景文件",
inputSchema: {
type: "object",
properties: {
url: {
type: "string",
description: "场景资源路径,如 db://assets/NewScene.fire",
},
},
required: ["url"],
},
},
{
name: "create_node",
description: "在当前场景中创建一个新节点",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "节点名称" },
parentId: {
type: "string",
description: "父节点 UUID (可选,不传则挂在场景根部)",
},
type: {
type: "string",
enum: ["empty", "sprite", "label"],
description: "节点预设类型",
},
},
required: ["name"],
},
},
];
return res.end(JSON.stringify({ tools }));
}
if (req.url === "/call-tool" && req.method === "POST") {
// 2. 执行工具逻辑
const { name, arguments: args } = JSON.parse(body);
if (name === "get_selected_node") {
let ids = Editor.Selection.curSelection("node");
res.end(JSON.stringify({ content: [{ type: "text", text: JSON.stringify(ids) }] }));
} else if (name === "set_node_name") {
Editor.Scene.callSceneScript(
"mcp-bridge",
"set-property",
{
id: args.id,
path: "name",
value: args.newName,
},
(err, result) => {
res.end(
JSON.stringify({
content: [
{ type: "text", text: err ? `Error: ${err}` : `Success: ${result}` },
],
}),
);
},
);
} else if (name === "save_scene") {
// 触发编辑器保存指令
Editor.Ipc.sendToMain("scene:save-scene");
res.end(JSON.stringify({ content: [{ type: "text", text: "Scene saved successfully" }] }));
} else if (name === "get_scene_hierarchy") {
Editor.Scene.callSceneScript("mcp-bridge", "get-hierarchy", (err, hierarchy) => {
if (err) {
res.end(
JSON.stringify({
content: [
{ type: "text", text: "Error fetching hierarchy: " + err.message },
],
}),
);
} else {
res.end(
JSON.stringify({
content: [{ type: "text", text: JSON.stringify(hierarchy, null, 2) }],
}),
);
}
});
} else if (name === "update_node_transform") {
Editor.Scene.callSceneScript("mcp-bridge", "update-node-transform", args, (err, result) => {
res.end(
JSON.stringify({
content: [{ type: "text", text: err ? `Error: ${err}` : result }],
}),
);
});
} else if (name === "create_scene") {
const url = `db://assets/${args.sceneName}.fire`;
if (Editor.assetdb.exists(url)) {
return res.end(
JSON.stringify({
content: [{ type: "text", text: "Error: Scene already exists" }],
}),
);
}
// 生成标准场景内容
const sceneJson = getNewSceneTemplate();
Editor.assetdb.create(url, sceneJson, (err, results) => {
if (err) {
res.end(
JSON.stringify({
content: [{ type: "text", text: "Error creating scene: " + err }],
}),
);
} else {
res.end(
JSON.stringify({
content: [{ type: "text", text: `Standard Scene created at ${url}` }],
}),
);
}
});
} else if (name === "create_prefab") {
const url = `db://assets/${args.prefabName}.prefab`;
// 2.4.x 创建预制体的 IPC 消息
Editor.Ipc.sendToMain("scene:create-prefab", args.nodeId, url);
res.end(
JSON.stringify({
content: [
{ type: "text", text: `Command sent: Creating prefab '${args.prefabName}'` },
],
}),
);
} else if (name === "open_scene") {
const url = args.url;
// 1. 将 db:// 路径转换为 UUID
const uuid = Editor.assetdb.urlToUuid(url);
if (uuid) {
// 2. 发送核心 IPC 消息给主进程
// scene:open-by-uuid 是编辑器内置的场景打开逻辑
Editor.Ipc.sendToMain("scene:open-by-uuid", uuid);
res.end(
JSON.stringify({
content: [
{ type: "text", text: `Success: Opening scene ${url} (UUID: ${uuid})` },
],
}),
);
} else {
res.end(
JSON.stringify({
content: [
{ type: "text", text: `Error: Could not find asset with URL ${url}` },
],
}),
);
}
} else if (name === "create_node") {
// 转发给场景脚本处理
Editor.Scene.callSceneScript("mcp-bridge", "create-node", args, (err, result) => {
res.end(
JSON.stringify({
content: [
{ type: "text", text: err ? `Error: ${err}` : `Node created: ${result}` },
],
}),
);
});
}
} else {
res.statusCode = 404;
res.end(JSON.stringify({ error: "Not Found" }));
}
} catch (e) {
res.statusCode = 500;
res.end(JSON.stringify({ error: e.message }));
}
});
});
this.server.listen(3456);
Editor.log("MCP Server standard interface listening on http://localhost:3456");
},
};