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) => {
// Note: query-assets API corrected with proper parameters
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) => {
// Verify scene creation by checking if it exists
this.getSceneList().then((sceneList) => {
const createdScene = sceneList.data?.find((scene: any) => scene.uuid === result.uuid);
resolve({
success: true,
data: {
uuid: result.uuid,
url: result.url,
name: sceneName,
message: `Scene '${sceneName}' created successfully`,
sceneVerified: !!createdScene
},
verificationData: createdScene
});
}).catch(() => {
resolve({
success: true,
data: {
uuid: result.uuid,
url: result.url,
name: sceneName,
message: `Scene '${sceneName}' created successfully (verification failed)`
}
});
});
}).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 });
});
});
}
}