2026-01-29 13:47:38 +08:00
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
"set-property": function (event, args) {
|
|
|
|
|
|
const { id, path, value } = args;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 获取节点
|
|
|
|
|
|
let node = cc.engine.getInstanceById(id);
|
|
|
|
|
|
|
|
|
|
|
|
if (node) {
|
|
|
|
|
|
// 2. 修改属性
|
|
|
|
|
|
if (path === "name") {
|
|
|
|
|
|
node.name = value;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
node[path] = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 【解决报错的关键】告诉编辑器场景变脏了(需要保存)
|
|
|
|
|
|
// 在场景进程中,我们发送 IPC 给主进程
|
|
|
|
|
|
Editor.Ipc.sendToMain("scene:dirty");
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 【额外补丁】通知层级管理器(Hierarchy)同步更新节点名称
|
|
|
|
|
|
// 否则你修改了名字,层级管理器可能还是显示旧名字
|
|
|
|
|
|
Editor.Ipc.sendToAll("scene:node-changed", {
|
|
|
|
|
|
uuid: id,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (event.reply) {
|
|
|
|
|
|
event.reply(null, `Node ${id} updated to ${value}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (event.reply) {
|
|
|
|
|
|
event.reply(new Error("Scene Script: Node not found " + id));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"get-hierarchy": function (event) {
|
|
|
|
|
|
const scene = cc.director.getScene();
|
|
|
|
|
|
|
|
|
|
|
|
function dumpNodes(node) {
|
|
|
|
|
|
// 【优化】跳过编辑器内部的私有节点,减少数据量
|
|
|
|
|
|
if (node.name.startsWith("Editor Scene") || node.name === "gizmoRoot") {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let nodeData = {
|
|
|
|
|
|
name: node.name,
|
|
|
|
|
|
uuid: node.uuid,
|
|
|
|
|
|
active: node.active,
|
|
|
|
|
|
position: { x: Math.round(node.x), y: Math.round(node.y) },
|
|
|
|
|
|
scale: { x: node.scaleX, y: node.scaleY },
|
|
|
|
|
|
size: { width: node.width, height: node.height },
|
|
|
|
|
|
// 记录组件类型,让 AI 知道这是个什么节点
|
|
|
|
|
|
components: node._components.map((c) => c.__typename),
|
|
|
|
|
|
children: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < node.childrenCount; i++) {
|
|
|
|
|
|
let childData = dumpNodes(node.children[i]);
|
|
|
|
|
|
if (childData) nodeData.children.push(childData);
|
|
|
|
|
|
}
|
|
|
|
|
|
return nodeData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const hierarchy = dumpNodes(scene);
|
|
|
|
|
|
if (event.reply) event.reply(null, hierarchy);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
"update-node-transform": function (event, args) {
|
|
|
|
|
|
const { id, x, y, scaleX, scaleY, color } = args;
|
|
|
|
|
|
let node = cc.engine.getInstanceById(id);
|
|
|
|
|
|
|
|
|
|
|
|
if (node) {
|
|
|
|
|
|
if (x !== undefined) node.x = x;
|
|
|
|
|
|
if (y !== undefined) node.y = y;
|
|
|
|
|
|
if (scaleX !== undefined) node.scaleX = scaleX;
|
|
|
|
|
|
if (scaleY !== undefined) node.scaleY = scaleY;
|
|
|
|
|
|
if (color) {
|
|
|
|
|
|
// color 格式如 "#FF0000"
|
|
|
|
|
|
node.color = new cc.Color().fromHEX(color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Editor.Ipc.sendToMain("scene:dirty");
|
|
|
|
|
|
Editor.Ipc.sendToAll("scene:node-changed", { uuid: id });
|
|
|
|
|
|
|
|
|
|
|
|
if (event.reply) event.reply(null, "Transform updated");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (event.reply) event.reply(new Error("Node not found"));
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-01-29 14:26:28 +08:00
|
|
|
|
"create-node": function (event, args) {
|
|
|
|
|
|
const { name, parentId, type } = args;
|
|
|
|
|
|
const scene = cc.director.getScene();
|
2026-01-31 16:48:21 +08:00
|
|
|
|
if (!scene || !cc.director.getRunningScene()) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error("Scene not ready or loading."));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-01-29 14:26:28 +08:00
|
|
|
|
|
|
|
|
|
|
let newNode = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 特殊处理:如果是创建 Canvas,自动设置好适配
|
|
|
|
|
|
if (type === "canvas" || name === "Canvas") {
|
|
|
|
|
|
newNode = new cc.Node("Canvas");
|
|
|
|
|
|
let canvas = newNode.addComponent(cc.Canvas);
|
|
|
|
|
|
newNode.addComponent(cc.Widget);
|
|
|
|
|
|
// 设置默认设计分辨率
|
|
|
|
|
|
canvas.designResolution = cc.size(960, 640);
|
|
|
|
|
|
canvas.fitHeight = true;
|
|
|
|
|
|
// 自动在 Canvas 下创建一个 Camera
|
|
|
|
|
|
let camNode = new cc.Node("Main Camera");
|
|
|
|
|
|
camNode.addComponent(cc.Camera);
|
|
|
|
|
|
camNode.parent = newNode;
|
|
|
|
|
|
} else if (type === "sprite") {
|
|
|
|
|
|
newNode = new cc.Node(name || "New Sprite");
|
|
|
|
|
|
newNode.addComponent(cc.Sprite);
|
|
|
|
|
|
} else if (type === "label") {
|
|
|
|
|
|
newNode = new cc.Node(name || "New Label");
|
|
|
|
|
|
let l = newNode.addComponent(cc.Label);
|
|
|
|
|
|
l.string = "New Label";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newNode = new cc.Node(name || "New Node");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置层级
|
|
|
|
|
|
let parent = parentId ? cc.engine.getInstanceById(parentId) : scene;
|
2026-01-31 16:48:21 +08:00
|
|
|
|
if (parent) {
|
2026-01-29 14:26:28 +08:00
|
|
|
|
newNode.parent = parent;
|
|
|
|
|
|
|
2026-01-31 16:48:21 +08:00
|
|
|
|
// 【优化】通知主进程场景变脏
|
2026-01-29 14:26:28 +08:00
|
|
|
|
Editor.Ipc.sendToMain("scene:dirty");
|
2026-01-31 16:48:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 【关键】使用 setTimeout 延迟通知 UI 刷新,让出主循环
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
Editor.Ipc.sendToAll("scene:node-created", {
|
|
|
|
|
|
uuid: newNode.uuid,
|
|
|
|
|
|
parentUuid: parent.uuid,
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 10);
|
2026-01-29 14:26:28 +08:00
|
|
|
|
|
|
|
|
|
|
if (event.reply) event.reply(null, newNode.uuid);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-01-31 16:48:21 +08:00
|
|
|
|
|
|
|
|
|
|
"manage-components": function (event, args) {
|
|
|
|
|
|
const { nodeId, action, componentType, componentId, properties } = args;
|
|
|
|
|
|
let node = cc.engine.getInstanceById(nodeId);
|
|
|
|
|
|
|
|
|
|
|
|
if (!node) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error("Node not found"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
|
case "add":
|
|
|
|
|
|
if (!componentType) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error("Component type is required"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 解析组件类型
|
|
|
|
|
|
let compClass = null;
|
|
|
|
|
|
if (componentType.startsWith("cc.")) {
|
|
|
|
|
|
const className = componentType.replace("cc.", "");
|
|
|
|
|
|
compClass = cc[className];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 尝试获取自定义组件
|
|
|
|
|
|
compClass = cc.js.getClassByName(componentType);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!compClass) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error(`Component type not found: ${componentType}`));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加组件
|
|
|
|
|
|
const component = node.addComponent(compClass);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置属性
|
|
|
|
|
|
if (properties) {
|
|
|
|
|
|
for (const [key, value] of Object.entries(properties)) {
|
|
|
|
|
|
if (component[key] !== undefined) {
|
|
|
|
|
|
component[key] = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Editor.Ipc.sendToMain("scene:dirty");
|
|
|
|
|
|
Editor.Ipc.sendToAll("scene:node-changed", { uuid: nodeId });
|
|
|
|
|
|
|
|
|
|
|
|
if (event.reply) event.reply(null, `Component ${componentType} added`);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error(`Failed to add component: ${err.message}`));
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case "remove":
|
|
|
|
|
|
if (!componentId) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error("Component ID is required"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 查找并移除组件
|
|
|
|
|
|
const component = node.getComponentById(componentId);
|
|
|
|
|
|
if (component) {
|
|
|
|
|
|
node.removeComponent(component);
|
|
|
|
|
|
Editor.Ipc.sendToMain("scene:dirty");
|
|
|
|
|
|
Editor.Ipc.sendToAll("scene:node-changed", { uuid: nodeId });
|
|
|
|
|
|
if (event.reply) event.reply(null, "Component removed");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (event.reply) event.reply(new Error("Component not found"));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error(`Failed to remove component: ${err.message}`));
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case "get":
|
|
|
|
|
|
try {
|
|
|
|
|
|
const components = node._components.map((c) => {
|
|
|
|
|
|
// 获取组件属性
|
|
|
|
|
|
const properties = {};
|
|
|
|
|
|
for (const key in c) {
|
|
|
|
|
|
if (typeof c[key] !== "function" &&
|
|
|
|
|
|
!key.startsWith("_") &&
|
|
|
|
|
|
c[key] !== undefined) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
properties[key] = c[key];
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 忽略无法序列化的属性
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: c.__typename,
|
|
|
|
|
|
uuid: c.uuid,
|
|
|
|
|
|
properties: properties
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
if (event.reply) event.reply(null, components);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
if (event.reply) event.reply(new Error(`Failed to get components: ${err.message}`));
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
if (event.reply) event.reply(new Error(`Unknown component action: ${action}`));
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
"get-component-properties": function (component) {
|
|
|
|
|
|
const properties = {};
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历组件属性
|
|
|
|
|
|
for (const key in component) {
|
|
|
|
|
|
if (typeof component[key] !== "function" &&
|
|
|
|
|
|
!key.startsWith("_") &&
|
|
|
|
|
|
component[key] !== undefined) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
properties[key] = component[key];
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 忽略无法序列化的属性
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return properties;
|
|
|
|
|
|
},
|
2026-01-31 19:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
"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"));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2026-01-29 13:47:38 +08:00
|
|
|
|
};
|