465 lines
17 KiB
TypeScript
465 lines
17 KiB
TypeScript
import { ToolDefinition, ToolResponse, ToolExecutor, SceneInfo } from '../types';
|
|
|
|
export class SceneTools implements ToolExecutor {
|
|
getTools(): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: 'get_current_scene',
|
|
description: 'Get current scene information',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'get_scene_list',
|
|
description: 'Get all scenes in the project',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'open_scene',
|
|
description: 'Open a scene by path',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
scenePath: {
|
|
type: 'string',
|
|
description: 'The scene file path'
|
|
}
|
|
},
|
|
required: ['scenePath']
|
|
}
|
|
},
|
|
{
|
|
name: 'save_scene',
|
|
description: 'Save current scene',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'create_scene',
|
|
description: 'Create a new scene asset',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
sceneName: {
|
|
type: 'string',
|
|
description: 'Name of the new scene'
|
|
},
|
|
savePath: {
|
|
type: 'string',
|
|
description: 'Path to save the scene (e.g., db://assets/scenes/NewScene.scene)'
|
|
}
|
|
},
|
|
required: ['sceneName', 'savePath']
|
|
}
|
|
},
|
|
{
|
|
name: 'save_scene_as',
|
|
description: 'Save scene as new file',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
path: {
|
|
type: 'string',
|
|
description: 'Path to save the scene'
|
|
}
|
|
},
|
|
required: ['path']
|
|
}
|
|
},
|
|
{
|
|
name: 'close_scene',
|
|
description: 'Close current scene',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'get_scene_hierarchy',
|
|
description: 'Get the complete hierarchy of current scene',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
includeComponents: {
|
|
type: 'boolean',
|
|
description: 'Include component information',
|
|
default: false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
|
switch (toolName) {
|
|
case 'get_current_scene':
|
|
return await this.getCurrentScene();
|
|
case 'get_scene_list':
|
|
return await this.getSceneList();
|
|
case 'open_scene':
|
|
return await this.openScene(args.scenePath);
|
|
case 'save_scene':
|
|
return await this.saveScene();
|
|
case 'create_scene':
|
|
return await this.createScene(args.sceneName, args.savePath);
|
|
case 'save_scene_as':
|
|
return await this.saveSceneAs(args.path);
|
|
case 'close_scene':
|
|
return await this.closeScene();
|
|
case 'get_scene_hierarchy':
|
|
return await this.getSceneHierarchy(args.includeComponents);
|
|
default:
|
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
}
|
|
}
|
|
|
|
private async getCurrentScene(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
// 直接使用 query-node-tree 来获取场景信息(这个方法已经验证可用)
|
|
Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
|
|
if (tree && tree.uuid) {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
name: tree.name || 'Current Scene',
|
|
uuid: tree.uuid,
|
|
type: tree.type || 'cc.Scene',
|
|
active: tree.active !== undefined ? tree.active : true,
|
|
nodeCount: tree.children ? tree.children.length : 0
|
|
}
|
|
});
|
|
} else {
|
|
resolve({ success: false, error: 'No scene data available' });
|
|
}
|
|
}).catch((err: Error) => {
|
|
// 备用方案:使用场景脚本
|
|
const options = {
|
|
name: 'cocos-mcp-server',
|
|
method: 'getCurrentSceneInfo',
|
|
args: []
|
|
};
|
|
|
|
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
|
resolve(result);
|
|
}).catch((err2: Error) => {
|
|
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
private async getSceneList(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('asset-db', 'query-assets', {
|
|
pattern: 'db://assets/**/*.scene'
|
|
}).then((results: any[]) => {
|
|
const scenes: SceneInfo[] = results.map(asset => ({
|
|
name: asset.name,
|
|
path: asset.url,
|
|
uuid: asset.uuid
|
|
}));
|
|
resolve({ success: true, data: scenes });
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async openScene(scenePath: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
// 首先获取场景的UUID
|
|
Editor.Message.request('asset-db', 'query-uuid', scenePath).then((uuid: string | null) => {
|
|
if (!uuid) {
|
|
throw new Error('Scene not found');
|
|
}
|
|
|
|
// 使用正确的 scene API 打开场景 (需要UUID)
|
|
return Editor.Message.request('scene', 'open-scene', uuid);
|
|
}).then(() => {
|
|
resolve({ success: true, message: `Scene opened: ${scenePath}` });
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async saveScene(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'save-scene').then(() => {
|
|
resolve({ success: true, message: 'Scene saved successfully' });
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async createScene(sceneName: string, savePath: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
// 确保路径以.scene结尾
|
|
const fullPath = savePath.endsWith('.scene') ? savePath : `${savePath}/${sceneName}.scene`;
|
|
|
|
// 使用正确的Cocos Creator 3.8场景格式
|
|
const sceneContent = JSON.stringify([
|
|
{
|
|
"__type__": "cc.SceneAsset",
|
|
"_name": sceneName,
|
|
"_objFlags": 0,
|
|
"__editorExtras__": {},
|
|
"_native": "",
|
|
"scene": {
|
|
"__id__": 1
|
|
}
|
|
},
|
|
{
|
|
"__type__": "cc.Scene",
|
|
"_name": sceneName,
|
|
"_objFlags": 0,
|
|
"__editorExtras__": {},
|
|
"_parent": null,
|
|
"_children": [],
|
|
"_active": true,
|
|
"_components": [],
|
|
"_prefab": null,
|
|
"_lpos": {
|
|
"__type__": "cc.Vec3",
|
|
"x": 0,
|
|
"y": 0,
|
|
"z": 0
|
|
},
|
|
"_lrot": {
|
|
"__type__": "cc.Quat",
|
|
"x": 0,
|
|
"y": 0,
|
|
"z": 0,
|
|
"w": 1
|
|
},
|
|
"_lscale": {
|
|
"__type__": "cc.Vec3",
|
|
"x": 1,
|
|
"y": 1,
|
|
"z": 1
|
|
},
|
|
"_mobility": 0,
|
|
"_layer": 1073741824,
|
|
"_euler": {
|
|
"__type__": "cc.Vec3",
|
|
"x": 0,
|
|
"y": 0,
|
|
"z": 0
|
|
},
|
|
"autoReleaseAssets": false,
|
|
"_globals": {
|
|
"__id__": 2
|
|
},
|
|
"_id": "scene"
|
|
},
|
|
{
|
|
"__type__": "cc.SceneGlobals",
|
|
"ambient": {
|
|
"__id__": 3
|
|
},
|
|
"skybox": {
|
|
"__id__": 4
|
|
},
|
|
"fog": {
|
|
"__id__": 5
|
|
},
|
|
"octree": {
|
|
"__id__": 6
|
|
}
|
|
},
|
|
{
|
|
"__type__": "cc.AmbientInfo",
|
|
"_skyColorHDR": {
|
|
"__type__": "cc.Vec4",
|
|
"x": 0.2,
|
|
"y": 0.5,
|
|
"z": 0.8,
|
|
"w": 0.520833
|
|
},
|
|
"_skyColor": {
|
|
"__type__": "cc.Vec4",
|
|
"x": 0.2,
|
|
"y": 0.5,
|
|
"z": 0.8,
|
|
"w": 0.520833
|
|
},
|
|
"_skyIllumHDR": 20000,
|
|
"_skyIllum": 20000,
|
|
"_groundAlbedoHDR": {
|
|
"__type__": "cc.Vec4",
|
|
"x": 0.2,
|
|
"y": 0.2,
|
|
"z": 0.2,
|
|
"w": 1
|
|
},
|
|
"_groundAlbedo": {
|
|
"__type__": "cc.Vec4",
|
|
"x": 0.2,
|
|
"y": 0.2,
|
|
"z": 0.2,
|
|
"w": 1
|
|
}
|
|
},
|
|
{
|
|
"__type__": "cc.SkyboxInfo",
|
|
"_envLightingType": 0,
|
|
"_envmapHDR": null,
|
|
"_envmap": null,
|
|
"_envmapLodCount": 0,
|
|
"_diffuseMapHDR": null,
|
|
"_diffuseMap": null,
|
|
"_enabled": false,
|
|
"_useHDR": true,
|
|
"_editableMaterial": null,
|
|
"_reflectionHDR": null,
|
|
"_reflectionMap": null,
|
|
"_rotationAngle": 0
|
|
},
|
|
{
|
|
"__type__": "cc.FogInfo",
|
|
"_type": 0,
|
|
"_fogColor": {
|
|
"__type__": "cc.Color",
|
|
"r": 200,
|
|
"g": 200,
|
|
"b": 200,
|
|
"a": 255
|
|
},
|
|
"_enabled": false,
|
|
"_fogDensity": 0.3,
|
|
"_fogStart": 0.5,
|
|
"_fogEnd": 300,
|
|
"_fogAtten": 5,
|
|
"_fogTop": 1.5,
|
|
"_fogRange": 1.2,
|
|
"_accurate": false
|
|
},
|
|
{
|
|
"__type__": "cc.OctreeInfo",
|
|
"_enabled": false,
|
|
"_minPos": {
|
|
"__type__": "cc.Vec3",
|
|
"x": -1024,
|
|
"y": -1024,
|
|
"z": -1024
|
|
},
|
|
"_maxPos": {
|
|
"__type__": "cc.Vec3",
|
|
"x": 1024,
|
|
"y": 1024,
|
|
"z": 1024
|
|
},
|
|
"_depth": 8
|
|
}
|
|
], null, 2);
|
|
|
|
Editor.Message.request('asset-db', 'create-asset', fullPath, sceneContent).then((result: any) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
uuid: result.uuid,
|
|
url: result.url,
|
|
name: sceneName,
|
|
message: `Scene '${sceneName}' created successfully`
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async getSceneHierarchy(includeComponents: boolean = false): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
// 优先尝试使用 Editor API 查询场景节点树
|
|
Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
|
|
if (tree) {
|
|
const hierarchy = this.buildHierarchy(tree, includeComponents);
|
|
resolve({
|
|
success: true,
|
|
data: hierarchy
|
|
});
|
|
} else {
|
|
resolve({ success: false, error: 'No scene hierarchy available' });
|
|
}
|
|
}).catch((err: Error) => {
|
|
// 备用方案:使用场景脚本
|
|
const options = {
|
|
name: 'cocos-mcp-server',
|
|
method: 'getSceneHierarchy',
|
|
args: [includeComponents]
|
|
};
|
|
|
|
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
|
resolve(result);
|
|
}).catch((err2: Error) => {
|
|
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
private buildHierarchy(node: any, includeComponents: boolean): any {
|
|
const nodeInfo: any = {
|
|
uuid: node.uuid,
|
|
name: node.name,
|
|
type: node.type,
|
|
active: node.active,
|
|
children: []
|
|
};
|
|
|
|
if (includeComponents && node.__comps__) {
|
|
nodeInfo.components = node.__comps__.map((comp: any) => ({
|
|
type: comp.__type__ || 'Unknown',
|
|
enabled: comp.enabled !== undefined ? comp.enabled : true
|
|
}));
|
|
}
|
|
|
|
if (node.children) {
|
|
nodeInfo.children = node.children.map((child: any) =>
|
|
this.buildHierarchy(child, includeComponents)
|
|
);
|
|
}
|
|
|
|
return nodeInfo;
|
|
}
|
|
|
|
private async saveSceneAs(path: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
// save-as-scene API 不接受路径参数,会弹出对话框让用户选择
|
|
(Editor.Message.request as any)('scene', 'save-as-scene').then(() => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
path: path,
|
|
message: `Scene save-as dialog opened`
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async closeScene(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'close-scene').then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Scene closed successfully'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
} |