Files
cocos-mcp/source/tools/prefab-tools.ts

1491 lines
57 KiB
TypeScript
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.

import { ToolDefinition, ToolResponse, ToolExecutor, PrefabInfo } from '../types';
export class PrefabTools implements ToolExecutor {
getTools(): ToolDefinition[] {
return [
{
name: 'get_prefab_list',
description: 'Get all prefabs in the project',
inputSchema: {
type: 'object',
properties: {
folder: {
type: 'string',
description: 'Folder path to search (optional)',
default: 'db://assets'
}
}
}
},
{
name: 'load_prefab',
description: 'Load a prefab by path',
inputSchema: {
type: 'object',
properties: {
prefabPath: {
type: 'string',
description: 'Prefab asset path'
}
},
required: ['prefabPath']
}
},
{
name: 'instantiate_prefab',
description: 'Instantiate a prefab in the scene',
inputSchema: {
type: 'object',
properties: {
prefabPath: {
type: 'string',
description: 'Prefab asset path'
},
parentUuid: {
type: 'string',
description: 'Parent node UUID (optional)'
},
position: {
type: 'object',
description: 'Initial position',
properties: {
x: { type: 'number' },
y: { type: 'number' },
z: { type: 'number' }
}
}
},
required: ['prefabPath']
}
},
{
name: 'create_prefab',
description: 'Create a prefab from a node',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Source node UUID'
},
savePath: {
type: 'string',
description: 'Path to save the prefab'
},
prefabName: {
type: 'string',
description: 'Prefab name'
},
includeChildren: {
type: 'boolean',
description: 'Whether to include child nodes',
default: true
},
includeComponents: {
type: 'boolean',
description: 'Whether to include components',
default: true
}
},
required: ['nodeUuid', 'savePath', 'prefabName']
}
},
{
name: 'create_prefab_from_node',
description: 'Create a prefab from a node (alias for create_prefab)',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Source node UUID'
},
prefabPath: {
type: 'string',
description: 'Path to save the prefab'
}
},
required: ['nodeUuid', 'prefabPath']
}
},
{
name: 'update_prefab',
description: 'Update an existing prefab',
inputSchema: {
type: 'object',
properties: {
prefabPath: {
type: 'string',
description: 'Prefab asset path'
},
nodeUuid: {
type: 'string',
description: 'Node UUID with changes'
}
},
required: ['prefabPath', 'nodeUuid']
}
},
{
name: 'revert_prefab',
description: 'Revert prefab instance to original',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Prefab instance node UUID'
}
},
required: ['nodeUuid']
}
},
{
name: 'get_prefab_info',
description: 'Get detailed prefab information',
inputSchema: {
type: 'object',
properties: {
prefabPath: {
type: 'string',
description: 'Prefab asset path'
}
},
required: ['prefabPath']
}
},
{
name: 'validate_prefab',
description: 'Validate a prefab file format',
inputSchema: {
type: 'object',
properties: {
prefabPath: {
type: 'string',
description: 'Prefab asset path'
}
},
required: ['prefabPath']
}
},
{
name: 'duplicate_prefab',
description: 'Duplicate an existing prefab',
inputSchema: {
type: 'object',
properties: {
sourcePrefabPath: {
type: 'string',
description: 'Source prefab path'
},
targetPrefabPath: {
type: 'string',
description: 'Target prefab path'
},
newPrefabName: {
type: 'string',
description: 'New prefab name'
}
},
required: ['sourcePrefabPath', 'targetPrefabPath']
}
},
{
name: 'restore_prefab_node',
description: 'Restore prefab node using prefab asset (built-in undo record)',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Prefab instance node UUID'
},
assetUuid: {
type: 'string',
description: 'Prefab asset UUID'
}
},
required: ['nodeUuid', 'assetUuid']
}
}
];
}
async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) {
case 'get_prefab_list':
return await this.getPrefabList(args.folder);
case 'load_prefab':
return await this.loadPrefab(args.prefabPath);
case 'instantiate_prefab':
return await this.instantiatePrefab(args);
case 'create_prefab':
return await this.createPrefab(args);
case 'create_prefab_from_node':
return await this.createPrefabFromNode(args);
case 'update_prefab':
return await this.updatePrefab(args.prefabPath, args.nodeUuid);
case 'revert_prefab':
return await this.revertPrefab(args.nodeUuid);
case 'get_prefab_info':
return await this.getPrefabInfo(args.prefabPath);
case 'validate_prefab':
return await this.validatePrefab(args.prefabPath);
case 'duplicate_prefab':
return await this.duplicatePrefab(args);
case 'restore_prefab_node':
return await this.restorePrefabNode(args.nodeUuid, args.assetUuid);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
private async getPrefabList(folder: string = 'db://assets'): Promise<ToolResponse> {
return new Promise((resolve) => {
const pattern = folder.endsWith('/') ?
`${folder}**/*.prefab` : `${folder}/**/*.prefab`;
Editor.Message.request('asset-db', 'query-assets', {
pattern: pattern
}).then((results: any[]) => {
const prefabs: PrefabInfo[] = results.map(asset => ({
name: asset.name,
path: asset.url,
uuid: asset.uuid,
folder: asset.url.substring(0, asset.url.lastIndexOf('/'))
}));
resolve({ success: true, data: prefabs });
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async loadPrefab(prefabPath: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
if (!assetInfo) {
throw new Error('Prefab not found');
}
return Editor.Message.request('scene', 'load-asset', {
uuid: assetInfo.uuid
});
}).then((prefabData: any) => {
resolve({
success: true,
data: {
uuid: prefabData.uuid,
name: prefabData.name,
message: 'Prefab loaded successfully'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async instantiatePrefab(args: any): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'query-asset-info', args.prefabPath).then((assetInfo: any) => {
if (!assetInfo) {
throw new Error('预制体未找到');
}
// 使用正确的 create-node API 从预制体资源实例化
const createNodeOptions: any = {
assetUuid: assetInfo.uuid
};
// 设置父节点
if (args.parentUuid) {
createNodeOptions.parent = args.parentUuid;
}
// 设置节点名称
if (args.name) {
createNodeOptions.name = args.name;
} else if (assetInfo.name) {
createNodeOptions.name = assetInfo.name;
}
// 设置初始属性(如位置)
if (args.position) {
createNodeOptions.dump = {
position: {
value: args.position
}
};
}
return Editor.Message.request('scene', 'create-node', createNodeOptions);
}).then((nodeUuid: string | string[]) => {
// 获取实际的节点UUID
const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
resolve({
success: true,
data: {
nodeUuid: uuid,
prefabPath: args.prefabPath,
parentUuid: args.parentUuid,
position: args.position,
message: '预制体实例化成功'
}
});
}).catch((err: Error) => {
resolve({
success: false,
error: `预制体实例化失败: ${err.message}`,
instruction: '请检查预制体路径是否正确,确保预制体文件格式正确'
});
});
});
}
private async tryCreateNodeWithPrefab(args: any): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'query-asset-info', args.prefabPath).then((assetInfo: any) => {
if (!assetInfo) {
throw new Error('预制体未找到');
}
// 方法2: 使用 create-node 指定预制体资源
const createNodeOptions: any = {
assetUuid: assetInfo.uuid
};
// 设置父节点
if (args.parentUuid) {
createNodeOptions.parent = args.parentUuid;
}
return Editor.Message.request('scene', 'create-node', createNodeOptions);
}).then((nodeUuid: string | string[]) => {
const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
// 如果指定了位置,设置节点位置
if (args.position && uuid) {
Editor.Message.request('scene', 'set-property', {
uuid: uuid,
path: 'position',
dump: { value: args.position }
}).then(() => {
resolve({
success: true,
data: {
nodeUuid: uuid,
prefabPath: args.prefabPath,
position: args.position,
message: '预制体实例化成功(备用方法)并设置了位置'
}
});
}).catch(() => {
resolve({
success: true,
data: {
nodeUuid: uuid,
prefabPath: args.prefabPath,
message: '预制体实例化成功(备用方法)但位置设置失败'
}
});
});
} else {
resolve({
success: true,
data: {
nodeUuid: uuid,
prefabPath: args.prefabPath,
message: '预制体实例化成功(备用方法)'
}
});
}
}).catch((err: Error) => {
resolve({
success: false,
error: `备用预制体实例化方法也失败: ${err.message}`
});
});
});
}
private async tryAlternativeInstantiateMethods(args: any): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
// 方法1: 尝试使用 create-node 然后设置预制体
const assetInfo = await this.getAssetInfo(args.prefabPath);
if (!assetInfo) {
resolve({ success: false, error: '无法获取预制体信息' });
return;
}
// 创建空节点
const createResult = await this.createNode(args.parentUuid, args.position);
if (!createResult.success) {
resolve(createResult);
return;
}
// 尝试将预制体应用到节点
const applyResult = await this.applyPrefabToNode(createResult.data.nodeUuid, assetInfo.uuid);
if (applyResult.success) {
resolve({
success: true,
data: {
nodeUuid: createResult.data.nodeUuid,
name: createResult.data.name,
message: '预制体实例化成功(使用备选方法)'
}
});
} else {
resolve({
success: false,
error: '无法将预制体应用到节点',
data: {
nodeUuid: createResult.data.nodeUuid,
message: '已创建节点,但无法应用预制体数据'
}
});
}
} catch (error) {
resolve({ success: false, error: `备选实例化方法失败: ${error}` });
}
});
}
private async getAssetInfo(prefabPath: string): Promise<any> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
resolve(assetInfo);
}).catch(() => {
resolve(null);
});
});
}
private async createNode(parentUuid?: string, position?: any): Promise<ToolResponse> {
return new Promise((resolve) => {
const createNodeOptions: any = {
name: 'PrefabInstance'
};
// 设置父节点
if (parentUuid) {
createNodeOptions.parent = parentUuid;
}
// 设置位置
if (position) {
createNodeOptions.dump = {
position: position
};
}
Editor.Message.request('scene', 'create-node', createNodeOptions).then((nodeUuid: string | string[]) => {
const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
resolve({
success: true,
data: {
nodeUuid: uuid,
name: 'PrefabInstance'
}
});
}).catch((error: any) => {
resolve({ success: false, error: error.message || '创建节点失败' });
});
});
}
private async applyPrefabToNode(nodeUuid: string, prefabUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
// 尝试多种方法来应用预制体数据
const methods = [
() => Editor.Message.request('scene', 'apply-prefab', { node: nodeUuid, prefab: prefabUuid }),
() => Editor.Message.request('scene', 'set-prefab', { node: nodeUuid, prefab: prefabUuid }),
() => Editor.Message.request('scene', 'load-prefab-to-node', { node: nodeUuid, prefab: prefabUuid })
];
const tryMethod = (index: number) => {
if (index >= methods.length) {
resolve({ success: false, error: '无法应用预制体数据' });
return;
}
methods[index]().then(() => {
resolve({ success: true });
}).catch(() => {
tryMethod(index + 1);
});
};
tryMethod(0);
});
}
private async createPrefab(args: any): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
// 支持 prefabPath 和 savePath 两种参数名
const pathParam = args.prefabPath || args.savePath;
if (!pathParam) {
resolve({
success: false,
error: '缺少预制体路径参数。请提供 prefabPath 或 savePath。'
});
return;
}
const prefabName = args.prefabName || 'NewPrefab';
const fullPath = pathParam.endsWith('.prefab') ?
pathParam : `${pathParam}/${prefabName}.prefab`;
// 尝试使用Cocos Creator的原生预制体创建API
const nativeResult = await this.createPrefabNative(args.nodeUuid, fullPath);
if (nativeResult.success) {
resolve(nativeResult);
return;
}
// 如果原生API失败使用自定义实现
const customResult = await this.createPrefabCustom(args.nodeUuid, fullPath, prefabName);
resolve(customResult);
} catch (error) {
resolve({
success: false,
error: `创建预制体时发生错误: ${error}`
});
}
});
}
private async createPrefabNative(nodeUuid: string, prefabPath: string): Promise<ToolResponse> {
return new Promise((resolve) => {
// 根据官方API文档不存在直接的预制体创建API
// 预制体创建需要手动在编辑器中完成
resolve({
success: false,
error: '原生预制体创建API不存在',
instruction: '根据Cocos Creator官方API文档预制体创建需要手动操作\n1. 在场景中选择节点\n2. 将节点拖拽到资源管理器中\n3. 或右键节点选择"生成预制体"'
});
});
}
private async createPrefabCustom(nodeUuid: string, prefabPath: string, prefabName: string): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
// 1. 获取源节点的完整数据
const nodeData = await this.getNodeData(nodeUuid);
if (!nodeData) {
resolve({
success: false,
error: `无法找到节点: ${nodeUuid}`
});
return;
}
// 2. 生成预制体UUID
const prefabUuid = this.generateUUID();
// 3. 创建预制体数据结构
const prefabData = this.createPrefabData(nodeData, prefabName, prefabUuid);
// 4. 基于官方格式创建预制体数据结构
console.log('=== 开始创建预制体 ===');
console.log('节点名称:', nodeData.name?.value || '未知');
console.log('节点UUID:', nodeData.uuid?.value || '未知');
console.log('预制体保存路径:', prefabPath);
console.log(`开始创建预制体,节点数据:`, nodeData);
const prefabJsonData = await this.createStandardPrefabData(nodeData, prefabName, prefabUuid);
// 5. 创建标准meta文件数据
const standardMetaData = this.createStandardMetaData(prefabName, prefabUuid);
// 6. 保存预制体和meta文件
const saveResult = await this.savePrefabWithMeta(prefabPath, prefabJsonData, standardMetaData);
if (saveResult.success) {
resolve({
success: true,
data: {
prefabUuid: prefabUuid,
prefabPath: prefabPath,
nodeUuid: nodeUuid,
prefabName: prefabName,
message: '自定义预制体创建成功'
}
});
} else {
resolve({
success: false,
error: saveResult.error || '保存预制体文件失败'
});
}
} catch (error) {
resolve({
success: false,
error: `创建预制体时发生错误: ${error}`
});
}
});
}
private async getNodeData(nodeUuid: string): Promise<any> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeInfo: any) => {
if (!nodeInfo) {
resolve(null);
return;
}
// 尝试获取节点的完整序列化数据
Editor.Message.request('scene', 'serialize-node', {
node: nodeUuid,
includeChildren: true
}).then((serializedData: any) => {
resolve(serializedData);
}).catch(() => {
// 如果序列化失败,尝试获取节点的详细信息
this.getNodeDetailedInfo(nodeUuid).then((detailedInfo: any) => {
resolve(detailedInfo || nodeInfo);
}).catch(() => {
resolve(nodeInfo);
});
});
}).catch(() => {
resolve(null);
});
});
}
private async getNodeDetailedInfo(nodeUuid: string): Promise<any> {
return new Promise((resolve) => {
// 获取节点的详细信息,包括组件和子节点
Editor.Message.request('scene', 'query-node-detail', nodeUuid).then((detailInfo: any) => {
if (detailInfo) {
resolve(detailInfo);
} else {
// 如果无法获取详细信息,构建基本节点信息
this.buildBasicNodeInfo(nodeUuid).then((basicInfo: any) => {
resolve(basicInfo);
}).catch(() => {
resolve(null);
});
}
}).catch(() => {
resolve(null);
});
});
}
private async buildBasicNodeInfo(nodeUuid: string): Promise<any> {
return new Promise((resolve) => {
// 构建基本的节点信息
Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeInfo: any) => {
if (!nodeInfo) {
resolve(null);
return;
}
// 简化版本:只返回基本节点信息,不获取子节点和组件
// 这些信息将在后续的预制体处理中根据需要添加
const basicInfo = {
...nodeInfo,
children: [],
components: []
};
resolve(basicInfo);
}).catch(() => {
resolve(null);
});
});
}
private generateUUID(): string {
// 生成符合Cocos Creator格式的UUID
const chars = '0123456789abcdef';
let uuid = '';
for (let i = 0; i < 32; i++) {
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += chars[Math.floor(Math.random() * chars.length)];
}
return uuid;
}
private createPrefabData(nodeData: any, prefabName: string, prefabUuid: string): any[] {
// 创建标准的预制体数据结构
const prefabAsset = {
"__type__": "cc.Prefab",
"_name": prefabName,
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
};
// 处理节点数据,确保符合预制体格式
const processedNodeData = this.processNodeForPrefab(nodeData, prefabUuid);
return [prefabAsset, ...processedNodeData];
}
private processNodeForPrefab(nodeData: any, prefabUuid: string): any[] {
// 处理节点数据以符合预制体格式
const processedData: any[] = [];
let idCounter = 1;
// 递归处理节点和组件
const processNode = (node: any, parentId: number = 0): number => {
const nodeId = idCounter++;
// 创建节点对象
const processedNode = {
"__type__": "cc.Node",
"_name": node.name || "Node",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": parentId > 0 ? { "__id__": parentId } : null,
"_children": node.children ? node.children.map(() => ({ "__id__": idCounter++ })) : [],
"_active": node.active !== false,
"_components": node.components ? node.components.map(() => ({ "__id__": idCounter++ })) : [],
"_prefab": {
"__id__": idCounter++
},
"_lpos": {
"__type__": "cc.Vec3",
"x": node.position?.x || 0,
"y": node.position?.y || 0,
"z": node.position?.z || 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": node.scale?.x || 1,
"y": node.scale?.y || 1,
"z": node.scale?.z || 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
};
processedData.push(processedNode);
// 处理组件
if (node.components) {
node.components.forEach((component: any) => {
const componentId = idCounter++;
const processedComponents = this.processComponentForPrefab(component, componentId);
processedData.push(...processedComponents);
});
}
// 处理子节点
if (node.children) {
node.children.forEach((child: any) => {
processNode(child, nodeId);
});
}
return nodeId;
};
processNode(nodeData);
return processedData;
}
private processComponentForPrefab(component: any, componentId: number): any[] {
// 处理组件数据以符合预制体格式
const processedComponent = {
"__type__": component.type || "cc.Component",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": componentId - 1
},
"_enabled": component.enabled !== false,
"__prefab": {
"__id__": componentId + 1
},
...component.properties
};
// 添加组件特定的预制体信息
const compPrefabInfo = {
"__type__": "cc.CompPrefabInfo",
"fileId": this.generateFileId()
};
return [processedComponent, compPrefabInfo];
}
private generateFileId(): string {
// 生成文件ID简化版本
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/';
let fileId = '';
for (let i = 0; i < 22; i++) {
fileId += chars[Math.floor(Math.random() * chars.length)];
}
return fileId;
}
private createMetaData(prefabName: string, prefabUuid: string): any {
return {
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": prefabUuid,
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": prefabName
}
};
}
private async savePrefabFiles(prefabPath: string, prefabData: any[], metaData: any): Promise<{ success: boolean; error?: string }> {
return new Promise((resolve) => {
try {
// 使用Editor API保存预制体文件
const prefabContent = JSON.stringify(prefabData, null, 2);
const metaContent = JSON.stringify(metaData, null, 2);
// 尝试使用更可靠的保存方法
this.saveAssetFile(prefabPath, prefabContent).then(() => {
// 再创建meta文件
const metaPath = `${prefabPath}.meta`;
return this.saveAssetFile(metaPath, metaContent);
}).then(() => {
resolve({ success: true });
}).catch((error: any) => {
resolve({ success: false, error: error.message || '保存预制体文件失败' });
});
} catch (error) {
resolve({ success: false, error: `保存文件时发生错误: ${error}` });
}
});
}
private async saveAssetFile(filePath: string, content: string): Promise<void> {
return new Promise((resolve, reject) => {
// 尝试多种保存方法
const saveMethods = [
() => Editor.Message.request('asset-db', 'create-asset', filePath, content),
() => Editor.Message.request('asset-db', 'save-asset', filePath, content),
() => Editor.Message.request('asset-db', 'write-asset', filePath, content)
];
const trySave = (index: number) => {
if (index >= saveMethods.length) {
reject(new Error('所有保存方法都失败了'));
return;
}
saveMethods[index]().then(() => {
resolve();
}).catch(() => {
trySave(index + 1);
});
};
trySave(0);
});
}
private async updatePrefab(prefabPath: string, nodeUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
if (!assetInfo) {
throw new Error('Prefab not found');
}
return Editor.Message.request('scene', 'apply-prefab', {
node: nodeUuid,
prefab: assetInfo.uuid
});
}).then(() => {
resolve({
success: true,
message: 'Prefab updated successfully'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async revertPrefab(nodeUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'revert-prefab', {
node: nodeUuid
}).then(() => {
resolve({
success: true,
message: 'Prefab instance reverted successfully'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async getPrefabInfo(prefabPath: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
if (!assetInfo) {
throw new Error('Prefab not found');
}
return Editor.Message.request('asset-db', 'query-asset-meta', assetInfo.uuid);
}).then((metaInfo: any) => {
const info: PrefabInfo = {
name: metaInfo.name,
uuid: metaInfo.uuid,
path: prefabPath,
folder: prefabPath.substring(0, prefabPath.lastIndexOf('/')),
createTime: metaInfo.createTime,
modifyTime: metaInfo.modifyTime,
dependencies: metaInfo.depends || []
};
resolve({ success: true, data: info });
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async createPrefabFromNode(args: any): Promise<ToolResponse> {
// 从 prefabPath 提取名称
const prefabPath = args.prefabPath;
const prefabName = prefabPath.split('/').pop()?.replace('.prefab', '') || 'NewPrefab';
// 调用原来的 createPrefab 方法
return await this.createPrefab({
nodeUuid: args.nodeUuid,
savePath: prefabPath,
prefabName: prefabName
});
}
private async validatePrefab(prefabPath: string): Promise<ToolResponse> {
return new Promise((resolve) => {
try {
// 读取预制体文件内容
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
if (!assetInfo) {
resolve({
success: false,
error: '预制体文件不存在'
});
return;
}
// 验证预制体格式
Editor.Message.request('asset-db', 'read-asset', prefabPath).then((content: string) => {
try {
const prefabData = JSON.parse(content);
const validationResult = this.validatePrefabFormat(prefabData);
resolve({
success: true,
data: {
isValid: validationResult.isValid,
issues: validationResult.issues,
nodeCount: validationResult.nodeCount,
componentCount: validationResult.componentCount,
message: validationResult.isValid ? '预制体格式有效' : '预制体格式存在问题'
}
});
} catch (parseError) {
resolve({
success: false,
error: '预制体文件格式错误无法解析JSON'
});
}
}).catch((error: any) => {
resolve({
success: false,
error: `读取预制体文件失败: ${error.message}`
});
});
}).catch((error: any) => {
resolve({
success: false,
error: `查询预制体信息失败: ${error.message}`
});
});
} catch (error) {
resolve({
success: false,
error: `验证预制体时发生错误: ${error}`
});
}
});
}
private validatePrefabFormat(prefabData: any): { isValid: boolean; issues: string[]; nodeCount: number; componentCount: number } {
const issues: string[] = [];
let nodeCount = 0;
let componentCount = 0;
// 检查基本结构
if (!Array.isArray(prefabData)) {
issues.push('预制体数据必须是数组格式');
return { isValid: false, issues, nodeCount, componentCount };
}
if (prefabData.length === 0) {
issues.push('预制体数据为空');
return { isValid: false, issues, nodeCount, componentCount };
}
// 检查第一个元素是否为预制体资产
const firstElement = prefabData[0];
if (!firstElement || firstElement.__type__ !== 'cc.Prefab') {
issues.push('第一个元素必须是cc.Prefab类型');
}
// 统计节点和组件
prefabData.forEach((item: any, index: number) => {
if (item.__type__ === 'cc.Node') {
nodeCount++;
} else if (item.__type__ && item.__type__.includes('cc.')) {
componentCount++;
}
});
// 检查必要的字段
if (nodeCount === 0) {
issues.push('预制体必须包含至少一个节点');
}
return {
isValid: issues.length === 0,
issues,
nodeCount,
componentCount
};
}
private async duplicatePrefab(args: any): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
const { sourcePrefabPath, targetPrefabPath, newPrefabName } = args;
// 读取源预制体
const sourceInfo = await this.getPrefabInfo(sourcePrefabPath);
if (!sourceInfo.success) {
resolve({
success: false,
error: `无法读取源预制体: ${sourceInfo.error}`
});
return;
}
// 读取源预制体内容
const sourceContent = await this.readPrefabContent(sourcePrefabPath);
if (!sourceContent.success) {
resolve({
success: false,
error: `无法读取源预制体内容: ${sourceContent.error}`
});
return;
}
// 生成新的UUID
const newUuid = this.generateUUID();
// 修改预制体数据
const modifiedData = this.modifyPrefabForDuplication(sourceContent.data, newPrefabName, newUuid);
// 创建新的meta数据
const newMetaData = this.createMetaData(newPrefabName || 'DuplicatedPrefab', newUuid);
// 预制体复制功能暂时禁用,因为涉及复杂的序列化格式
resolve({
success: false,
error: '预制体复制功能暂时不可用',
instruction: '请在 Cocos Creator 编辑器中手动复制预制体:\n1. 在资源管理器中选择要复制的预制体\n2. 右键选择复制\n3. 在目标位置粘贴'
});
} catch (error) {
resolve({
success: false,
error: `复制预制体时发生错误: ${error}`
});
}
});
}
private async readPrefabContent(prefabPath: string): Promise<{ success: boolean; data?: any; error?: string }> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'read-asset', prefabPath).then((content: string) => {
try {
const prefabData = JSON.parse(content);
resolve({ success: true, data: prefabData });
} catch (parseError) {
resolve({ success: false, error: '预制体文件格式错误' });
}
}).catch((error: any) => {
resolve({ success: false, error: error.message || '读取预制体文件失败' });
});
});
}
private modifyPrefabForDuplication(prefabData: any[], newName: string, newUuid: string): any[] {
// 修改预制体数据以创建副本
const modifiedData = [...prefabData];
// 修改第一个元素(预制体资产)
if (modifiedData[0] && modifiedData[0].__type__ === 'cc.Prefab') {
modifiedData[0]._name = newName || 'DuplicatedPrefab';
}
// 更新所有UUID引用简化版本
// 在实际应用中可能需要更复杂的UUID映射处理
return modifiedData;
}
private async restorePrefabNode(nodeUuid: string, assetUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
// 使用官方API restore-prefab 还原预制体节点
(Editor.Message.request as any)('scene', 'restore-prefab', nodeUuid, assetUuid).then(() => {
resolve({
success: true,
data: {
nodeUuid: nodeUuid,
assetUuid: assetUuid,
message: '预制体节点还原成功'
}
});
}).catch((error: any) => {
resolve({
success: false,
error: `预制体节点还原失败: ${error.message}`
});
});
});
}
// 基于官方预制体格式的新实现方法
private async getNodeDataForPrefab(nodeUuid: string): Promise<{ success: boolean; data?: any; error?: string }> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeData: any) => {
if (!nodeData) {
resolve({ success: false, error: '节点不存在' });
return;
}
resolve({ success: true, data: nodeData });
}).catch((error: any) => {
resolve({ success: false, error: error.message });
});
});
}
private async createStandardPrefabData(nodeData: any, prefabName: string, prefabUuid: string): Promise<any[]> {
// 基于官方Canvas.prefab格式创建预制体数据结构
const prefabData: any[] = [];
let currentId = 0;
// 第一个元素cc.Prefab 资源对象
const prefabAsset = {
"__type__": "cc.Prefab",
"_name": prefabName,
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
};
prefabData.push(prefabAsset);
currentId++;
// 第二个元素:根节点
const rootNode = await this.createNodeObject(nodeData, null, prefabData, currentId);
prefabData.push(rootNode.node);
currentId = rootNode.nextId;
// 添加根节点的 PrefabInfo
const rootPrefabInfo = {
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": this.generateFileId(),
"targetOverrides": null
};
prefabData.push(rootPrefabInfo);
return prefabData;
}
private async createNodeObject(nodeData: any, parentId: number | null, prefabData: any[], currentId: number): Promise<{ node: any; nextId: number }> {
const nodeId = currentId++;
// 提取节点的基本属性
const position = nodeData.position?.value || { x: 0, y: 0, z: 0 };
const rotation = nodeData.rotation?.value || { x: 0, y: 0, z: 0, w: 1 };
const scale = nodeData.scale?.value || { x: 1, y: 1, z: 1 };
const active = nodeData.active?.value !== undefined ? nodeData.active.value : true;
const name = nodeData.name?.value || 'Node';
const layer = nodeData.layer?.value || 33554432;
const node: any = {
"__type__": "cc.Node",
"_name": name,
"_objFlags": 0,
"__editorExtras__": {},
"_parent": parentId !== null ? { "__id__": parentId } : null,
"_children": [],
"_active": active,
"_components": [],
"_prefab": {
"__id__": currentId++
},
"_lpos": {
"__type__": "cc.Vec3",
"x": position.x,
"y": position.y,
"z": position.z
},
"_lrot": {
"__type__": "cc.Quat",
"x": rotation.x,
"y": rotation.y,
"z": rotation.z,
"w": rotation.w
},
"_lscale": {
"__type__": "cc.Vec3",
"x": scale.x,
"y": scale.y,
"z": scale.z
},
"_mobility": 0,
"_layer": layer,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
};
// 添加组件
if (nodeData.__comps__ && nodeData.__comps__.length > 0) {
for (const comp of nodeData.__comps__) {
const componentId = currentId++;
node._components.push({ "__id__": componentId });
// 创建组件对象
const componentObj = this.createComponentObject(comp, nodeId, currentId++);
prefabData.push(componentObj);
// 添加组件的 CompPrefabInfo
const compPrefabInfo = {
"__type__": "cc.CompPrefabInfo",
"fileId": this.generateFileId()
};
prefabData.push(compPrefabInfo);
currentId++;
}
}
// 处理子节点
if (nodeData.children && Array.isArray(nodeData.children) && nodeData.children.length > 0) {
console.log(`=== 处理子节点 ===`);
console.log(`节点 ${name} 包含 ${nodeData.children.length} 个子节点`);
console.log('完整子节点数据:', JSON.stringify(nodeData.children, null, 2));
for (let i = 0; i < nodeData.children.length; i++) {
const childRef = nodeData.children[i];
console.log(`${i}个子节点的完整数据:`, JSON.stringify(childRef, null, 2));
// 尝试多种可能的UUID提取方式
let childUuid = null;
if (typeof childRef === 'string') {
childUuid = childRef;
console.log(`方法1 - 直接字符串: ${childUuid}`);
} else if (childRef && childRef.value) {
if (typeof childRef.value === 'string') {
childUuid = childRef.value;
console.log(`方法2 - value是字符串: ${childUuid}`);
} else if (childRef.value.uuid) {
childUuid = childRef.value.uuid;
console.log(`方法3 - value.uuid: ${childUuid}`);
} else if (childRef.value.__id__) {
console.log(`方法4 - 发现__id__引用: ${childRef.value.__id__}`);
// 这可能是一个内部引用,我们需要不同的处理方式
continue;
} else {
console.log('方法4 - value不是字符串内容:', JSON.stringify(childRef.value));
}
} else if (childRef && childRef.uuid) {
childUuid = childRef.uuid;
console.log(`方法5 - 直接uuid属性: ${childUuid}`);
} else {
console.log('方法6 - 无法识别的子节点格式:', JSON.stringify(childRef));
}
if (childUuid && childUuid !== '[object Object]') {
try {
// 获取子节点数据
const childNodeData = await this.getNodeData(childUuid);
if (childNodeData) {
const childId = currentId;
node._children.push({ "__id__": childId });
// 递归创建子节点
const childResult = await this.createNodeObject(childNodeData, nodeId, prefabData, currentId);
prefabData.push(childResult.node);
currentId = childResult.nextId;
// 为子节点添加PrefabInfo
const childPrefabInfo = {
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1 // 指向根节点
},
"asset": {
"__id__": 0 // 指向Prefab资源
},
"fileId": this.generateFileId(),
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
};
prefabData.push(childPrefabInfo);
currentId++;
console.log(`✅ 已添加子节点: ${childNodeData.name?.value || '未知'}`);
console.log(`子节点在预制体数组中的索引: ${currentId}`);
} else {
console.warn(`无法获取子节点数据: ${childUuid}`);
}
} catch (error) {
console.error(`处理子节点 ${childUuid} 时出错:`, error);
}
}
}
}
return { node, nextId: currentId };
}
private createComponentObject(componentData: any, nodeId: number, prefabInfoId: number): any {
const componentType = componentData.__type__ || 'cc.Component';
const component: any = {
"__type__": componentType,
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": nodeId
},
"_enabled": componentData.enabled !== undefined ? componentData.enabled : true,
"__prefab": {
"__id__": prefabInfoId
},
"_id": ""
};
// 复制组件的其他属性
for (const key in componentData) {
if (!key.startsWith('_') && key !== '__type__' && key !== 'enabled' && key !== 'node') {
component[key] = componentData[key];
}
}
return component;
}
private createStandardMetaData(prefabName: string, prefabUuid: string): any {
return {
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": prefabUuid,
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": prefabName
}
};
}
private async savePrefabWithMeta(prefabPath: string, prefabData: any[], metaData: any): Promise<{ success: boolean; error?: string }> {
try {
const prefabContent = JSON.stringify(prefabData, null, 2);
const metaContent = JSON.stringify(metaData, null, 2);
// 确保路径以.prefab结尾
const finalPrefabPath = prefabPath.endsWith('.prefab') ? prefabPath : `${prefabPath}.prefab`;
const metaPath = `${finalPrefabPath}.meta`;
// 使用asset-db API创建预制体文件
await new Promise((resolve, reject) => {
Editor.Message.request('asset-db', 'create-asset', finalPrefabPath, prefabContent).then(() => {
resolve(true);
}).catch((error: any) => {
reject(error);
});
});
// 创建meta文件
await new Promise((resolve, reject) => {
Editor.Message.request('asset-db', 'create-asset', metaPath, metaContent).then(() => {
resolve(true);
}).catch((error: any) => {
reject(error);
});
});
console.log(`=== 预制体保存完成 ===`);
console.log(`预制体文件已保存: ${finalPrefabPath}`);
console.log(`Meta文件已保存: ${metaPath}`);
console.log(`预制体数组总长度: ${prefabData.length}`);
console.log(`预制体根节点索引: ${prefabData.length - 1}`);
return { success: true };
} catch (error: any) {
console.error('保存预制体文件时出错:', error);
return { success: false, error: error.message };
}
}
}