2856 lines
112 KiB
TypeScript
2856 lines
112 KiB
TypeScript
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 with all children and components',
|
||
inputSchema: {
|
||
type: 'object',
|
||
properties: {
|
||
nodeUuid: {
|
||
type: 'string',
|
||
description: 'Source node UUID'
|
||
},
|
||
savePath: {
|
||
type: 'string',
|
||
description: 'Path to save the prefab (e.g., db://assets/prefabs/MyPrefab.prefab)'
|
||
},
|
||
prefabName: {
|
||
type: 'string',
|
||
description: 'Prefab name'
|
||
}
|
||
},
|
||
required: ['nodeUuid', 'savePath', 'prefabName']
|
||
}
|
||
},
|
||
{
|
||
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 '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) => {
|
||
let assetInfo: any = null;
|
||
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((info: any) => {
|
||
if (!info) {
|
||
throw new Error('Prefab not found');
|
||
}
|
||
assetInfo = info;
|
||
|
||
return Editor.Message.request('scene', 'open-scene', info.uuid);
|
||
}).then(() => {
|
||
resolve({
|
||
success: true,
|
||
data: {
|
||
uuid: assetInfo.uuid,
|
||
name: assetInfo.name,
|
||
message: 'Prefab loaded successfully'
|
||
}
|
||
});
|
||
}).catch((err: Error) => {
|
||
resolve({ success: false, error: err.message });
|
||
});
|
||
});
|
||
}
|
||
|
||
private async instantiatePrefab(args: any): Promise<ToolResponse> {
|
||
return new Promise(async (resolve) => {
|
||
try {
|
||
// 获取预制体资源信息
|
||
const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', args.prefabPath);
|
||
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
|
||
}
|
||
};
|
||
}
|
||
|
||
// 创建节点
|
||
const nodeUuid = await Editor.Message.request('scene', 'create-node', createNodeOptions);
|
||
const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
|
||
|
||
// 注意:create-node API从预制体资源创建时应该自动建立预制体关联
|
||
console.log('预制体节点创建成功:', {
|
||
nodeUuid: uuid,
|
||
prefabUuid: assetInfo.uuid,
|
||
prefabPath: args.prefabPath
|
||
});
|
||
|
||
resolve({
|
||
success: true,
|
||
data: {
|
||
nodeUuid: uuid,
|
||
prefabPath: args.prefabPath,
|
||
parentUuid: args.parentUuid,
|
||
position: args.position,
|
||
message: '预制体实例化成功,已建立预制体关联'
|
||
}
|
||
});
|
||
} catch (err: any) {
|
||
resolve({
|
||
success: false,
|
||
error: `预制体实例化失败: ${err.message}`,
|
||
instruction: '请检查预制体路径是否正确,确保预制体文件格式正确'
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 建立节点与预制体的关联关系
|
||
* 这个方法创建必要的PrefabInfo和PrefabInstance结构
|
||
*/
|
||
private async establishPrefabConnection(nodeUuid: string, prefabUuid: string, prefabPath: string): Promise<void> {
|
||
try {
|
||
// 读取预制体文件获取根节点的fileId
|
||
const prefabContent = await this.readPrefabFile(prefabPath);
|
||
if (!prefabContent || !prefabContent.data || !prefabContent.data.length) {
|
||
throw new Error('无法读取预制体文件内容');
|
||
}
|
||
|
||
// 找到预制体根节点的fileId (通常是第二个对象,即索引1)
|
||
const rootNode = prefabContent.data.find((item: any) => item.__type === 'cc.Node' && item._parent === null);
|
||
if (!rootNode || !rootNode._prefab) {
|
||
throw new Error('无法找到预制体根节点或其预制体信息');
|
||
}
|
||
|
||
// 获取根节点的PrefabInfo
|
||
const rootPrefabInfo = prefabContent.data[rootNode._prefab.__id__];
|
||
if (!rootPrefabInfo || rootPrefabInfo.__type !== 'cc.PrefabInfo') {
|
||
throw new Error('无法找到预制体根节点的PrefabInfo');
|
||
}
|
||
|
||
const rootFileId = rootPrefabInfo.fileId;
|
||
|
||
// 使用scene API建立预制体连接
|
||
const prefabConnectionData = {
|
||
node: nodeUuid,
|
||
prefab: prefabUuid,
|
||
fileId: rootFileId
|
||
};
|
||
|
||
// 尝试使用多种API方法建立预制体连接
|
||
const connectionMethods = [
|
||
() => Editor.Message.request('scene', 'connect-prefab-instance', prefabConnectionData),
|
||
() => Editor.Message.request('scene', 'set-prefab-connection', prefabConnectionData),
|
||
() => Editor.Message.request('scene', 'apply-prefab-link', prefabConnectionData)
|
||
];
|
||
|
||
let connected = false;
|
||
for (const method of connectionMethods) {
|
||
try {
|
||
await method();
|
||
connected = true;
|
||
break;
|
||
} catch (error) {
|
||
console.warn('预制体连接方法失败,尝试下一个方法:', error);
|
||
}
|
||
}
|
||
|
||
if (!connected) {
|
||
// 如果所有API方法都失败,尝试手动修改场景数据
|
||
console.warn('所有预制体连接API都失败,尝试手动建立连接');
|
||
await this.manuallyEstablishPrefabConnection(nodeUuid, prefabUuid, rootFileId);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('建立预制体连接失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 手动建立预制体连接(当API方法失败时的备用方案)
|
||
*/
|
||
private async manuallyEstablishPrefabConnection(nodeUuid: string, prefabUuid: string, rootFileId: string): Promise<void> {
|
||
try {
|
||
// 尝试使用dump API修改节点的_prefab属性
|
||
const prefabConnectionData = {
|
||
[nodeUuid]: {
|
||
'_prefab': {
|
||
'__uuid__': prefabUuid,
|
||
'__expectedType__': 'cc.Prefab',
|
||
'fileId': rootFileId
|
||
}
|
||
}
|
||
};
|
||
|
||
await Editor.Message.request('scene', 'set-property', {
|
||
uuid: nodeUuid,
|
||
path: '_prefab',
|
||
dump: {
|
||
value: {
|
||
'__uuid__': prefabUuid,
|
||
'__expectedType__': 'cc.Prefab'
|
||
}
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('手动建立预制体连接也失败:', error);
|
||
// 不抛出错误,因为基本的节点创建已经成功
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 读取预制体文件内容
|
||
*/
|
||
private async readPrefabFile(prefabPath: string): Promise<any> {
|
||
try {
|
||
// 尝试使用asset-db API读取文件内容
|
||
let assetContent: any;
|
||
try {
|
||
assetContent = await Editor.Message.request('asset-db', 'query-asset-info', prefabPath);
|
||
if (assetContent && assetContent.source) {
|
||
// 如果有source路径,直接读取文件
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const fullPath = path.resolve(assetContent.source);
|
||
const fileContent = fs.readFileSync(fullPath, 'utf8');
|
||
return JSON.parse(fileContent);
|
||
}
|
||
} catch (error) {
|
||
console.warn('使用asset-db读取失败,尝试其他方法:', error);
|
||
}
|
||
|
||
// 备用方法:转换db://路径为实际文件路径
|
||
const fsPath = prefabPath.replace('db://assets/', 'assets/').replace('db://assets', 'assets');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
// 尝试多个可能的项目根路径
|
||
const possiblePaths = [
|
||
path.resolve(process.cwd(), '../../NewProject_3', fsPath),
|
||
path.resolve('/Users/lizhiyong/NewProject_3', fsPath),
|
||
path.resolve(fsPath),
|
||
// 如果是根目录下的文件,也尝试直接路径
|
||
path.resolve('/Users/lizhiyong/NewProject_3/assets', path.basename(fsPath))
|
||
];
|
||
|
||
console.log('尝试读取预制体文件,路径转换:', {
|
||
originalPath: prefabPath,
|
||
fsPath: fsPath,
|
||
possiblePaths: possiblePaths
|
||
});
|
||
|
||
for (const fullPath of possiblePaths) {
|
||
try {
|
||
console.log(`检查路径: ${fullPath}`);
|
||
if (fs.existsSync(fullPath)) {
|
||
console.log(`找到文件: ${fullPath}`);
|
||
const fileContent = fs.readFileSync(fullPath, 'utf8');
|
||
const parsed = JSON.parse(fileContent);
|
||
console.log('文件解析成功,数据结构:', {
|
||
hasData: !!parsed.data,
|
||
dataLength: parsed.data ? parsed.data.length : 0
|
||
});
|
||
return parsed;
|
||
} else {
|
||
console.log(`文件不存在: ${fullPath}`);
|
||
}
|
||
} catch (readError) {
|
||
console.warn(`读取文件失败 ${fullPath}:`, readError);
|
||
}
|
||
}
|
||
|
||
throw new Error('无法找到或读取预制体文件');
|
||
} catch (error) {
|
||
console.error('读取预制体文件失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
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);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 使用 asset-db API 创建预制体的新方法
|
||
* 深度整合引擎的资源管理系统,实现完整的预制体创建流程
|
||
*/
|
||
private async createPrefabWithAssetDB(nodeUuid: string, savePath: string, prefabName: string, includeChildren: boolean, includeComponents: boolean): Promise<ToolResponse> {
|
||
return new Promise(async (resolve) => {
|
||
try {
|
||
console.log('=== 使用 Asset-DB API 创建预制体 ===');
|
||
console.log(`节点UUID: ${nodeUuid}`);
|
||
console.log(`保存路径: ${savePath}`);
|
||
console.log(`预制体名称: ${prefabName}`);
|
||
|
||
// 第一步:获取节点数据(包括变换属性)
|
||
const nodeData = await this.getNodeData(nodeUuid);
|
||
if (!nodeData) {
|
||
resolve({
|
||
success: false,
|
||
error: '无法获取节点数据'
|
||
});
|
||
return;
|
||
}
|
||
|
||
console.log('获取到节点数据,子节点数量:', nodeData.children ? nodeData.children.length : 0);
|
||
|
||
// 第二步:先创建资源文件以获取引擎分配的UUID
|
||
console.log('创建预制体资源文件...');
|
||
const tempPrefabContent = JSON.stringify([{"__type__": "cc.Prefab", "_name": prefabName}], null, 2);
|
||
const createResult = await this.createAssetWithAssetDB(savePath, tempPrefabContent);
|
||
if (!createResult.success) {
|
||
resolve(createResult);
|
||
return;
|
||
}
|
||
|
||
// 获取引擎分配的实际UUID
|
||
const actualPrefabUuid = createResult.data?.uuid;
|
||
if (!actualPrefabUuid) {
|
||
resolve({
|
||
success: false,
|
||
error: '无法获取引擎分配的预制体UUID'
|
||
});
|
||
return;
|
||
}
|
||
console.log('引擎分配的UUID:', actualPrefabUuid);
|
||
|
||
// 第三步:使用实际UUID重新生成预制体内容
|
||
const prefabContent = await this.createStandardPrefabContent(nodeData, prefabName, actualPrefabUuid, includeChildren, includeComponents);
|
||
const prefabContentString = JSON.stringify(prefabContent, null, 2);
|
||
|
||
// 第四步:更新预制体文件内容
|
||
console.log('更新预制体文件内容...');
|
||
const updateResult = await this.updateAssetWithAssetDB(savePath, prefabContentString);
|
||
|
||
// 第五步:创建对应的meta文件(使用实际UUID)
|
||
console.log('创建预制体meta文件...');
|
||
const metaContent = this.createStandardMetaContent(prefabName, actualPrefabUuid);
|
||
const metaResult = await this.createMetaWithAssetDB(savePath, metaContent);
|
||
|
||
// 第六步:重新导入资源以更新引用
|
||
console.log('重新导入预制体资源...');
|
||
const reimportResult = await this.reimportAssetWithAssetDB(savePath);
|
||
|
||
// 第七步:尝试将原始节点转换为预制体实例
|
||
console.log('尝试将原始节点转换为预制体实例...');
|
||
const convertResult = await this.convertNodeToPrefabInstance(nodeUuid, actualPrefabUuid, savePath);
|
||
|
||
resolve({
|
||
success: true,
|
||
data: {
|
||
prefabUuid: actualPrefabUuid,
|
||
prefabPath: savePath,
|
||
nodeUuid: nodeUuid,
|
||
prefabName: prefabName,
|
||
convertedToPrefabInstance: convertResult.success,
|
||
createAssetResult: createResult,
|
||
updateResult: updateResult,
|
||
metaResult: metaResult,
|
||
reimportResult: reimportResult,
|
||
convertResult: convertResult,
|
||
message: convertResult.success ? '预制体创建并成功转换原始节点' : '预制体创建成功,但节点转换失败'
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('创建预制体时发生错误:', error);
|
||
resolve({
|
||
success: false,
|
||
error: `创建预制体失败: ${error}`
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
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`;
|
||
|
||
const includeChildren = args.includeChildren !== false; // 默认为 true
|
||
const includeComponents = args.includeComponents !== false; // 默认为 true
|
||
|
||
// 优先使用新的 asset-db 方法创建预制体
|
||
console.log('使用新的 asset-db 方法创建预制体...');
|
||
const assetDbResult = await this.createPrefabWithAssetDB(
|
||
args.nodeUuid,
|
||
fullPath,
|
||
prefabName,
|
||
includeChildren,
|
||
includeComponents
|
||
);
|
||
|
||
if (assetDbResult.success) {
|
||
resolve(assetDbResult);
|
||
return;
|
||
}
|
||
|
||
// 如果 asset-db 方法失败,尝试使用Cocos Creator的原生预制体创建API
|
||
console.log('asset-db 方法失败,尝试原生API...');
|
||
const nativeResult = await this.createPrefabNative(args.nodeUuid, fullPath);
|
||
if (nativeResult.success) {
|
||
resolve(nativeResult);
|
||
return;
|
||
}
|
||
|
||
// 如果原生API失败,使用自定义实现
|
||
console.log('原生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.createStandardPrefabContent(nodeData, prefabName, prefabUuid, true, true);
|
||
|
||
// 5. 创建标准meta文件数据
|
||
const standardMetaData = this.createStandardMetaData(prefabName, prefabUuid);
|
||
|
||
// 6. 保存预制体和meta文件
|
||
const saveResult = await this.savePrefabWithMeta(prefabPath, prefabJsonData, standardMetaData);
|
||
|
||
if (saveResult.success) {
|
||
// 保存成功后,将原始节点转换为预制体实例
|
||
const convertResult = await this.convertNodeToPrefabInstance(nodeUuid, prefabPath, prefabUuid);
|
||
|
||
resolve({
|
||
success: true,
|
||
data: {
|
||
prefabUuid: prefabUuid,
|
||
prefabPath: prefabPath,
|
||
nodeUuid: nodeUuid,
|
||
prefabName: prefabName,
|
||
convertedToPrefabInstance: convertResult.success,
|
||
message: convertResult.success ?
|
||
'自定义预制体创建成功,原始节点已转换为预制体实例' :
|
||
'预制体创建成功,但节点转换失败'
|
||
}
|
||
});
|
||
} else {
|
||
resolve({
|
||
success: false,
|
||
error: saveResult.error || '保存预制体文件失败'
|
||
});
|
||
}
|
||
|
||
} catch (error) {
|
||
resolve({
|
||
success: false,
|
||
error: `创建预制体时发生错误: ${error}`
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
private async getNodeData(nodeUuid: string): Promise<any> {
|
||
return new Promise(async (resolve) => {
|
||
try {
|
||
// 首先获取基本节点信息
|
||
const nodeInfo = await Editor.Message.request('scene', 'query-node', nodeUuid);
|
||
if (!nodeInfo) {
|
||
resolve(null);
|
||
return;
|
||
}
|
||
|
||
console.log(`获取节点 ${nodeUuid} 的基本信息成功`);
|
||
|
||
// 使用query-node-tree获取包含子节点的完整结构
|
||
const nodeTree = await this.getNodeWithChildren(nodeUuid);
|
||
if (nodeTree) {
|
||
console.log(`获取节点 ${nodeUuid} 的完整树结构成功`);
|
||
resolve(nodeTree);
|
||
} else {
|
||
console.log(`使用基本节点信息`);
|
||
resolve(nodeInfo);
|
||
}
|
||
} catch (error) {
|
||
console.warn(`获取节点数据失败 ${nodeUuid}:`, error);
|
||
resolve(null);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 使用query-node-tree获取包含子节点的完整节点结构
|
||
private async getNodeWithChildren(nodeUuid: string): Promise<any> {
|
||
try {
|
||
// 获取整个场景树
|
||
const tree = await Editor.Message.request('scene', 'query-node-tree');
|
||
if (!tree) {
|
||
return null;
|
||
}
|
||
|
||
// 在树中查找指定的节点
|
||
const targetNode = this.findNodeInTree(tree, nodeUuid);
|
||
if (targetNode) {
|
||
console.log(`在场景树中找到节点 ${nodeUuid},子节点数量: ${targetNode.children ? targetNode.children.length : 0}`);
|
||
|
||
// 增强节点树,获取每个节点的正确组件信息
|
||
const enhancedTree = await this.enhanceTreeWithMCPComponents(targetNode);
|
||
return enhancedTree;
|
||
}
|
||
|
||
return null;
|
||
} catch (error) {
|
||
console.warn(`获取节点树结构失败 ${nodeUuid}:`, error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 在节点树中递归查找指定UUID的节点
|
||
private findNodeInTree(node: any, targetUuid: string): any {
|
||
if (!node) return null;
|
||
|
||
// 检查当前节点
|
||
if (node.uuid === targetUuid || node.value?.uuid === targetUuid) {
|
||
return node;
|
||
}
|
||
|
||
// 递归检查子节点
|
||
if (node.children && Array.isArray(node.children)) {
|
||
for (const child of node.children) {
|
||
const found = this.findNodeInTree(child, targetUuid);
|
||
if (found) {
|
||
return found;
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 使用MCP接口增强节点树,获取正确的组件信息
|
||
*/
|
||
private async enhanceTreeWithMCPComponents(node: any): Promise<any> {
|
||
if (!node || !node.uuid) {
|
||
return node;
|
||
}
|
||
|
||
try {
|
||
// 使用MCP接口获取节点的组件信息
|
||
const response = await fetch('http://localhost:8585/mcp', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
"jsonrpc": "2.0",
|
||
"method": "tools/call",
|
||
"params": {
|
||
"name": "component_get_components",
|
||
"arguments": {
|
||
"nodeUuid": node.uuid
|
||
}
|
||
},
|
||
"id": Date.now()
|
||
})
|
||
});
|
||
|
||
const mcpResult = await response.json();
|
||
if (mcpResult.result?.content?.[0]?.text) {
|
||
const componentData = JSON.parse(mcpResult.result.content[0].text);
|
||
if (componentData.success && componentData.data.components) {
|
||
// 更新节点的组件信息为MCP返回的正确数据
|
||
node.components = componentData.data.components;
|
||
console.log(`节点 ${node.uuid} 获取到 ${componentData.data.components.length} 个组件,包含脚本组件的正确类型`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn(`获取节点 ${node.uuid} 的MCP组件信息失败:`, error);
|
||
}
|
||
|
||
// 递归处理子节点
|
||
if (node.children && Array.isArray(node.children)) {
|
||
for (let i = 0; i < node.children.length; i++) {
|
||
node.children[i] = await this.enhanceTreeWithMCPComponents(node.children[i]);
|
||
}
|
||
}
|
||
|
||
return node;
|
||
}
|
||
|
||
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 isValidNodeData(nodeData: any): boolean {
|
||
if (!nodeData) return false;
|
||
if (typeof nodeData !== 'object') return false;
|
||
|
||
// 检查基本属性 - 适配query-node-tree的数据格式
|
||
return nodeData.hasOwnProperty('uuid') ||
|
||
nodeData.hasOwnProperty('name') ||
|
||
nodeData.hasOwnProperty('__type__') ||
|
||
(nodeData.value && (
|
||
nodeData.value.hasOwnProperty('uuid') ||
|
||
nodeData.value.hasOwnProperty('name') ||
|
||
nodeData.value.hasOwnProperty('__type__')
|
||
));
|
||
}
|
||
|
||
// 提取子节点UUID的统一方法
|
||
private extractChildUuid(childRef: any): string | null {
|
||
if (!childRef) return null;
|
||
|
||
// 方法1: 直接字符串
|
||
if (typeof childRef === 'string') {
|
||
return childRef;
|
||
}
|
||
|
||
// 方法2: value属性包含字符串
|
||
if (childRef.value && typeof childRef.value === 'string') {
|
||
return childRef.value;
|
||
}
|
||
|
||
// 方法3: value.uuid属性
|
||
if (childRef.value && childRef.value.uuid) {
|
||
return childRef.value.uuid;
|
||
}
|
||
|
||
// 方法4: 直接uuid属性
|
||
if (childRef.uuid) {
|
||
return childRef.uuid;
|
||
}
|
||
|
||
// 方法5: __id__引用 - 这种情况需要特殊处理
|
||
if (childRef.__id__ !== undefined) {
|
||
console.log(`发现__id__引用: ${childRef.__id__},可能需要从数据结构中查找`);
|
||
return null; // 暂时返回null,后续可以添加引用解析逻辑
|
||
}
|
||
|
||
console.warn('无法提取子节点UUID:', JSON.stringify(childRef));
|
||
return null;
|
||
}
|
||
|
||
// 获取需要处理的子节点数据
|
||
private getChildrenToProcess(nodeData: any): any[] {
|
||
const children: any[] = [];
|
||
|
||
// 方法1: 直接从children数组获取(从query-node-tree返回的数据)
|
||
if (nodeData.children && Array.isArray(nodeData.children)) {
|
||
console.log(`从children数组获取子节点,数量: ${nodeData.children.length}`);
|
||
for (const child of nodeData.children) {
|
||
// query-node-tree返回的子节点通常已经是完整的数据结构
|
||
if (this.isValidNodeData(child)) {
|
||
children.push(child);
|
||
console.log(`添加子节点: ${child.name || child.value?.name || '未知'}`);
|
||
} else {
|
||
console.log('子节点数据无效:', JSON.stringify(child, null, 2));
|
||
}
|
||
}
|
||
} else {
|
||
console.log('节点没有子节点或children数组为空');
|
||
}
|
||
|
||
return children;
|
||
}
|
||
|
||
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": 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
|
||
},
|
||
"_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;
|
||
}
|
||
|
||
/**
|
||
* 使用 asset-db API 创建资源文件
|
||
*/
|
||
private async createAssetWithAssetDB(assetPath: string, content: string): Promise<{ success: boolean; data?: any; error?: string }> {
|
||
return new Promise((resolve) => {
|
||
Editor.Message.request('asset-db', 'create-asset', assetPath, content, {
|
||
overwrite: true,
|
||
rename: false
|
||
}).then((assetInfo: any) => {
|
||
console.log('创建资源文件成功:', assetInfo);
|
||
resolve({ success: true, data: assetInfo });
|
||
}).catch((error: any) => {
|
||
console.error('创建资源文件失败:', error);
|
||
resolve({ success: false, error: error.message || '创建资源文件失败' });
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 使用 asset-db API 创建 meta 文件
|
||
*/
|
||
private async createMetaWithAssetDB(assetPath: string, metaContent: any): Promise<{ success: boolean; data?: any; error?: string }> {
|
||
return new Promise((resolve) => {
|
||
const metaContentString = JSON.stringify(metaContent, null, 2);
|
||
Editor.Message.request('asset-db', 'save-asset-meta', assetPath, metaContentString).then((assetInfo: any) => {
|
||
console.log('创建meta文件成功:', assetInfo);
|
||
resolve({ success: true, data: assetInfo });
|
||
}).catch((error: any) => {
|
||
console.error('创建meta文件失败:', error);
|
||
resolve({ success: false, error: error.message || '创建meta文件失败' });
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 使用 asset-db API 重新导入资源
|
||
*/
|
||
private async reimportAssetWithAssetDB(assetPath: string): Promise<{ success: boolean; data?: any; error?: string }> {
|
||
return new Promise((resolve) => {
|
||
Editor.Message.request('asset-db', 'reimport-asset', assetPath).then((result: any) => {
|
||
console.log('重新导入资源成功:', result);
|
||
resolve({ success: true, data: result });
|
||
}).catch((error: any) => {
|
||
console.error('重新导入资源失败:', error);
|
||
resolve({ success: false, error: error.message || '重新导入资源失败' });
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 使用 asset-db API 更新资源文件内容
|
||
*/
|
||
private async updateAssetWithAssetDB(assetPath: string, content: string): Promise<{ success: boolean; data?: any; error?: string }> {
|
||
return new Promise((resolve) => {
|
||
Editor.Message.request('asset-db', 'save-asset', assetPath, content).then((result: any) => {
|
||
console.log('更新资源文件成功:', result);
|
||
resolve({ success: true, data: result });
|
||
}).catch((error: any) => {
|
||
console.error('更新资源文件失败:', error);
|
||
resolve({ success: false, error: error.message || '更新资源文件失败' });
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 创建符合 Cocos Creator 标准的预制体内容
|
||
* 完整实现递归节点树处理,匹配引擎标准格式
|
||
*/
|
||
private async createStandardPrefabContent(nodeData: any, prefabName: string, prefabUuid: string, includeChildren: boolean, includeComponents: boolean): Promise<any[]> {
|
||
console.log('开始创建引擎标准预制体内容...');
|
||
|
||
const prefabData: any[] = [];
|
||
let currentId = 0;
|
||
|
||
// 1. 创建预制体资产对象 (index 0)
|
||
const prefabAsset = {
|
||
"__type__": "cc.Prefab",
|
||
"_name": prefabName || "", // 确保预制体名称不为空
|
||
"_objFlags": 0,
|
||
"__editorExtras__": {},
|
||
"_native": "",
|
||
"data": {
|
||
"__id__": 1
|
||
},
|
||
"optimizationPolicy": 0,
|
||
"persistent": false
|
||
};
|
||
prefabData.push(prefabAsset);
|
||
currentId++;
|
||
|
||
// 2. 递归创建完整的节点树结构
|
||
const context = {
|
||
prefabData,
|
||
currentId: currentId + 1, // 根节点占用索引1,子节点从索引2开始
|
||
prefabAssetIndex: 0,
|
||
nodeFileIds: new Map<string, string>(), // 存储节点ID到fileId的映射
|
||
nodeUuidToIndex: new Map<string, number>(), // 存储节点UUID到索引的映射
|
||
componentUuidToIndex: new Map<string, number>() // 存储组件UUID到索引的映射
|
||
};
|
||
|
||
// 创建根节点和整个节点树 - 注意:根节点的父节点应该是null,不是预制体对象
|
||
await this.createCompleteNodeTree(nodeData, null, 1, context, includeChildren, includeComponents, prefabName);
|
||
|
||
console.log(`预制体内容创建完成,总共 ${prefabData.length} 个对象`);
|
||
console.log('节点fileId映射:', Array.from(context.nodeFileIds.entries()));
|
||
|
||
return prefabData;
|
||
}
|
||
|
||
/**
|
||
* 递归创建完整的节点树,包括所有子节点和对应的PrefabInfo
|
||
*/
|
||
private async createCompleteNodeTree(
|
||
nodeData: any,
|
||
parentNodeIndex: number | null,
|
||
nodeIndex: number,
|
||
context: {
|
||
prefabData: any[],
|
||
currentId: number,
|
||
prefabAssetIndex: number,
|
||
nodeFileIds: Map<string, string>,
|
||
nodeUuidToIndex: Map<string, number>,
|
||
componentUuidToIndex: Map<string, number>
|
||
},
|
||
includeChildren: boolean,
|
||
includeComponents: boolean,
|
||
nodeName?: string
|
||
): Promise<void> {
|
||
const { prefabData } = context;
|
||
|
||
// 创建节点对象
|
||
const node = this.createEngineStandardNode(nodeData, parentNodeIndex, nodeName);
|
||
|
||
// 确保节点在指定的索引位置
|
||
while (prefabData.length <= nodeIndex) {
|
||
prefabData.push(null);
|
||
}
|
||
console.log(`设置节点到索引 ${nodeIndex}: ${node._name}, _parent:`, node._parent, `_children count: ${node._children.length}`);
|
||
prefabData[nodeIndex] = node;
|
||
|
||
// 为当前节点生成fileId并记录UUID到索引的映射
|
||
const nodeUuid = this.extractNodeUuid(nodeData);
|
||
const fileId = nodeUuid || this.generateFileId();
|
||
context.nodeFileIds.set(nodeIndex.toString(), fileId);
|
||
|
||
// 记录节点UUID到索引的映射
|
||
if (nodeUuid) {
|
||
context.nodeUuidToIndex.set(nodeUuid, nodeIndex);
|
||
console.log(`记录节点UUID映射: ${nodeUuid} -> ${nodeIndex}`);
|
||
}
|
||
|
||
// 先处理子节点(保持与手动创建的索引顺序一致)
|
||
const childrenToProcess = this.getChildrenToProcess(nodeData);
|
||
if (includeChildren && childrenToProcess.length > 0) {
|
||
console.log(`处理节点 ${node._name} 的 ${childrenToProcess.length} 个子节点`);
|
||
|
||
// 为每个子节点分配索引
|
||
const childIndices: number[] = [];
|
||
console.log(`准备为 ${childrenToProcess.length} 个子节点分配索引,当前ID: ${context.currentId}`);
|
||
for (let i = 0; i < childrenToProcess.length; i++) {
|
||
console.log(`处理第 ${i+1} 个子节点,当前currentId: ${context.currentId}`);
|
||
const childIndex = context.currentId++;
|
||
childIndices.push(childIndex);
|
||
node._children.push({ "__id__": childIndex });
|
||
console.log(`✅ 添加子节点引用到 ${node._name}: {__id__: ${childIndex}}`);
|
||
}
|
||
console.log(`✅ 节点 ${node._name} 最终的子节点数组:`, node._children);
|
||
|
||
// 递归创建子节点
|
||
for (let i = 0; i < childrenToProcess.length; i++) {
|
||
const childData = childrenToProcess[i];
|
||
const childIndex = childIndices[i];
|
||
await this.createCompleteNodeTree(
|
||
childData,
|
||
nodeIndex,
|
||
childIndex,
|
||
context,
|
||
includeChildren,
|
||
includeComponents,
|
||
childData.name || `Child${i+1}`
|
||
);
|
||
}
|
||
}
|
||
|
||
// 然后处理组件
|
||
if (includeComponents && nodeData.components && Array.isArray(nodeData.components)) {
|
||
console.log(`处理节点 ${node._name} 的 ${nodeData.components.length} 个组件`);
|
||
|
||
const componentIndices: number[] = [];
|
||
for (const component of nodeData.components) {
|
||
const componentIndex = context.currentId++;
|
||
componentIndices.push(componentIndex);
|
||
node._components.push({ "__id__": componentIndex });
|
||
|
||
// 记录组件UUID到索引的映射
|
||
const componentUuid = component.uuid || (component.value && component.value.uuid);
|
||
if (componentUuid) {
|
||
context.componentUuidToIndex.set(componentUuid, componentIndex);
|
||
console.log(`记录组件UUID映射: ${componentUuid} -> ${componentIndex}`);
|
||
}
|
||
|
||
// 创建组件对象,传入context以处理引用
|
||
const componentObj = this.createComponentObject(component, nodeIndex, context);
|
||
prefabData[componentIndex] = componentObj;
|
||
|
||
// 为组件创建 CompPrefabInfo
|
||
const compPrefabInfoIndex = context.currentId++;
|
||
prefabData[compPrefabInfoIndex] = {
|
||
"__type__": "cc.CompPrefabInfo",
|
||
"fileId": this.generateFileId()
|
||
};
|
||
|
||
// 如果组件对象有 __prefab 属性,设置引用
|
||
if (componentObj && typeof componentObj === 'object') {
|
||
componentObj.__prefab = { "__id__": compPrefabInfoIndex };
|
||
}
|
||
}
|
||
|
||
console.log(`✅ 节点 ${node._name} 添加了 ${componentIndices.length} 个组件`);
|
||
}
|
||
|
||
|
||
// 为当前节点创建PrefabInfo
|
||
const prefabInfoIndex = context.currentId++;
|
||
node._prefab = { "__id__": prefabInfoIndex };
|
||
|
||
const prefabInfo: any = {
|
||
"__type__": "cc.PrefabInfo",
|
||
"root": { "__id__": 1 },
|
||
"asset": { "__id__": context.prefabAssetIndex },
|
||
"fileId": fileId,
|
||
"targetOverrides": null,
|
||
"nestedPrefabInstanceRoots": null
|
||
};
|
||
|
||
// 根节点的特殊处理
|
||
if (nodeIndex === 1) {
|
||
// 根节点没有instance,但可能有targetOverrides
|
||
prefabInfo.instance = null;
|
||
} else {
|
||
// 子节点通常有instance为null
|
||
prefabInfo.instance = null;
|
||
}
|
||
|
||
prefabData[prefabInfoIndex] = prefabInfo;
|
||
context.currentId = prefabInfoIndex + 1;
|
||
}
|
||
|
||
/**
|
||
* 将UUID转换为Cocos Creator的压缩格式
|
||
* 基于真实Cocos Creator编辑器的压缩算法实现
|
||
* 前5个hex字符保持不变,剩余27个字符压缩成18个字符
|
||
*/
|
||
private uuidToCompressedId(uuid: string): string {
|
||
const BASE64_KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||
|
||
// 移除连字符并转为小写
|
||
const cleanUuid = uuid.replace(/-/g, '').toLowerCase();
|
||
|
||
// 确保UUID有效
|
||
if (cleanUuid.length !== 32) {
|
||
return uuid; // 如果不是有效的UUID,返回原始值
|
||
}
|
||
|
||
// Cocos Creator的压缩算法:前5个字符保持不变,剩余27个字符压缩成18个字符
|
||
let result = cleanUuid.substring(0, 5);
|
||
|
||
// 剩余27个字符需要压缩成18个字符
|
||
const remainder = cleanUuid.substring(5);
|
||
|
||
// 每3个hex字符压缩成2个字符
|
||
for (let i = 0; i < remainder.length; i += 3) {
|
||
const hex1 = remainder[i] || '0';
|
||
const hex2 = remainder[i + 1] || '0';
|
||
const hex3 = remainder[i + 2] || '0';
|
||
|
||
// 将3个hex字符(12位)转换为2个base64字符
|
||
const value = parseInt(hex1 + hex2 + hex3, 16);
|
||
|
||
// 12位分成两个6位
|
||
const high6 = (value >> 6) & 63;
|
||
const low6 = value & 63;
|
||
|
||
result += BASE64_KEYS[high6] + BASE64_KEYS[low6];
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 创建组件对象
|
||
*/
|
||
private createComponentObject(componentData: any, nodeIndex: number, context?: {
|
||
nodeUuidToIndex?: Map<string, number>,
|
||
componentUuidToIndex?: Map<string, number>
|
||
}): any {
|
||
let componentType = componentData.type || componentData.__type__ || 'cc.Component';
|
||
const enabled = componentData.enabled !== undefined ? componentData.enabled : true;
|
||
|
||
// console.log(`创建组件对象 - 原始类型: ${componentType}`);
|
||
// console.log('组件完整数据:', JSON.stringify(componentData, null, 2));
|
||
|
||
// 处理脚本组件 - MCP接口已经返回正确的压缩UUID格式
|
||
if (componentType && !componentType.startsWith('cc.')) {
|
||
console.log(`使用脚本组件压缩UUID类型: ${componentType}`);
|
||
}
|
||
|
||
// 基础组件结构
|
||
const component: any = {
|
||
"__type__": componentType,
|
||
"_name": "",
|
||
"_objFlags": 0,
|
||
"__editorExtras__": {},
|
||
"node": { "__id__": nodeIndex },
|
||
"_enabled": enabled
|
||
};
|
||
|
||
// 提前设置 __prefab 属性占位符,后续会被正确设置
|
||
component.__prefab = null;
|
||
|
||
// 根据组件类型添加特定属性
|
||
if (componentType === 'cc.UITransform') {
|
||
const contentSize = componentData.properties?.contentSize?.value || { width: 100, height: 100 };
|
||
const anchorPoint = componentData.properties?.anchorPoint?.value || { x: 0.5, y: 0.5 };
|
||
|
||
component._contentSize = {
|
||
"__type__": "cc.Size",
|
||
"width": contentSize.width,
|
||
"height": contentSize.height
|
||
};
|
||
component._anchorPoint = {
|
||
"__type__": "cc.Vec2",
|
||
"x": anchorPoint.x,
|
||
"y": anchorPoint.y
|
||
};
|
||
} else if (componentType === 'cc.Sprite') {
|
||
// 处理Sprite组件的spriteFrame引用
|
||
const spriteFrameProp = componentData.properties?._spriteFrame || componentData.properties?.spriteFrame;
|
||
if (spriteFrameProp) {
|
||
component._spriteFrame = this.processComponentProperty(spriteFrameProp, context);
|
||
} else {
|
||
component._spriteFrame = null;
|
||
}
|
||
|
||
component._type = componentData.properties?._type?.value ?? 0;
|
||
component._fillType = componentData.properties?._fillType?.value ?? 0;
|
||
component._sizeMode = componentData.properties?._sizeMode?.value ?? 1;
|
||
component._fillCenter = { "__type__": "cc.Vec2", "x": 0, "y": 0 };
|
||
component._fillStart = componentData.properties?._fillStart?.value ?? 0;
|
||
component._fillRange = componentData.properties?._fillRange?.value ?? 0;
|
||
component._isTrimmedMode = componentData.properties?._isTrimmedMode?.value ?? true;
|
||
component._useGrayscale = componentData.properties?._useGrayscale?.value ?? false;
|
||
|
||
// 调试:打印Sprite组件的所有属性(已注释)
|
||
// console.log('Sprite组件属性:', JSON.stringify(componentData.properties, null, 2));
|
||
component._atlas = null;
|
||
component._id = "";
|
||
} else if (componentType === 'cc.Button') {
|
||
component._interactable = true;
|
||
component._transition = 3;
|
||
component._normalColor = { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 };
|
||
component._hoverColor = { "__type__": "cc.Color", "r": 211, "g": 211, "b": 211, "a": 255 };
|
||
component._pressedColor = { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 };
|
||
component._disabledColor = { "__type__": "cc.Color", "r": 124, "g": 124, "b": 124, "a": 255 };
|
||
component._normalSprite = null;
|
||
component._hoverSprite = null;
|
||
component._pressedSprite = null;
|
||
component._disabledSprite = null;
|
||
component._duration = 0.1;
|
||
component._zoomScale = 1.2;
|
||
// 处理Button的target引用
|
||
const targetProp = componentData.properties?._target || componentData.properties?.target;
|
||
if (targetProp) {
|
||
component._target = this.processComponentProperty(targetProp, context);
|
||
} else {
|
||
component._target = { "__id__": nodeIndex }; // 默认指向自身节点
|
||
}
|
||
component._clickEvents = [];
|
||
component._id = "";
|
||
} else if (componentType === 'cc.Label') {
|
||
component._string = componentData.properties?._string?.value || "Label";
|
||
component._horizontalAlign = 1;
|
||
component._verticalAlign = 1;
|
||
component._actualFontSize = 20;
|
||
component._fontSize = 20;
|
||
component._fontFamily = "Arial";
|
||
component._lineHeight = 25;
|
||
component._overflow = 0;
|
||
component._enableWrapText = true;
|
||
component._font = null;
|
||
component._isSystemFontUsed = true;
|
||
component._spacingX = 0;
|
||
component._isItalic = false;
|
||
component._isBold = false;
|
||
component._isUnderline = false;
|
||
component._underlineHeight = 2;
|
||
component._cacheMode = 0;
|
||
component._id = "";
|
||
} else if (componentData.properties) {
|
||
// 处理所有组件的属性(包括内置组件和自定义脚本组件)
|
||
for (const [key, value] of Object.entries(componentData.properties)) {
|
||
if (key === 'node' || key === 'enabled' || key === '__type__' ||
|
||
key === 'uuid' || key === 'name' || key === '__scriptAsset' || key === '_objFlags') {
|
||
continue; // 跳过这些特殊属性,包括_objFlags
|
||
}
|
||
|
||
// 对于以下划线开头的属性,需要特殊处理
|
||
if (key.startsWith('_')) {
|
||
// 确保属性名保持原样(包括下划线)
|
||
const propValue = this.processComponentProperty(value, context);
|
||
if (propValue !== undefined) {
|
||
component[key] = propValue;
|
||
}
|
||
} else {
|
||
// 非下划线开头的属性正常处理
|
||
const propValue = this.processComponentProperty(value, context);
|
||
if (propValue !== undefined) {
|
||
component[key] = propValue;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 确保 _id 在最后位置
|
||
const _id = component._id || "";
|
||
delete component._id;
|
||
component._id = _id;
|
||
|
||
return component;
|
||
}
|
||
|
||
/**
|
||
* 处理组件属性值,确保格式与手动创建的预制体一致
|
||
*/
|
||
private processComponentProperty(propData: any, context?: {
|
||
nodeUuidToIndex?: Map<string, number>,
|
||
componentUuidToIndex?: Map<string, number>
|
||
}): any {
|
||
if (!propData || typeof propData !== 'object') {
|
||
return propData;
|
||
}
|
||
|
||
const value = propData.value;
|
||
const type = propData.type;
|
||
|
||
// 处理null值
|
||
if (value === null || value === undefined) {
|
||
return null;
|
||
}
|
||
|
||
// 处理空UUID对象,转换为null
|
||
if (value && typeof value === 'object' && value.uuid === '') {
|
||
return null;
|
||
}
|
||
|
||
// 处理节点引用
|
||
if (type === 'cc.Node' && value?.uuid) {
|
||
// 在预制体中,节点引用需要转换为 __id__ 形式
|
||
if (context?.nodeUuidToIndex && context.nodeUuidToIndex.has(value.uuid)) {
|
||
// 内部引用:转换为__id__格式
|
||
return {
|
||
"__id__": context.nodeUuidToIndex.get(value.uuid)
|
||
};
|
||
}
|
||
// 外部引用:设置为null,因为外部节点不属于预制体结构
|
||
console.warn(`Node reference UUID ${value.uuid} not found in prefab context, setting to null (external reference)`);
|
||
return null;
|
||
}
|
||
|
||
// 处理资源引用(预制体、纹理、精灵帧等)
|
||
if (value?.uuid && (
|
||
type === 'cc.Prefab' ||
|
||
type === 'cc.Texture2D' ||
|
||
type === 'cc.SpriteFrame' ||
|
||
type === 'cc.Material' ||
|
||
type === 'cc.AnimationClip' ||
|
||
type === 'cc.AudioClip' ||
|
||
type === 'cc.Font' ||
|
||
type === 'cc.Asset'
|
||
)) {
|
||
// 对于预制体引用,保持原始UUID格式
|
||
const uuidToUse = type === 'cc.Prefab' ? value.uuid : this.uuidToCompressedId(value.uuid);
|
||
return {
|
||
"__uuid__": uuidToUse,
|
||
"__expectedType__": type
|
||
};
|
||
}
|
||
|
||
// 处理组件引用(包括具体的组件类型如cc.Label, cc.Button等)
|
||
if (value?.uuid && (type === 'cc.Component' ||
|
||
type === 'cc.Label' || type === 'cc.Button' || type === 'cc.Sprite' ||
|
||
type === 'cc.UITransform' || type === 'cc.RigidBody2D' ||
|
||
type === 'cc.BoxCollider2D' || type === 'cc.Animation' ||
|
||
type === 'cc.AudioSource' || (type?.startsWith('cc.') && !type.includes('@')))) {
|
||
// 在预制体中,组件引用也需要转换为 __id__ 形式
|
||
if (context?.componentUuidToIndex && context.componentUuidToIndex.has(value.uuid)) {
|
||
// 内部引用:转换为__id__格式
|
||
console.log(`Component reference ${type} UUID ${value.uuid} found in prefab context, converting to __id__`);
|
||
return {
|
||
"__id__": context.componentUuidToIndex.get(value.uuid)
|
||
};
|
||
}
|
||
// 外部引用:设置为null,因为外部组件不属于预制体结构
|
||
console.warn(`Component reference ${type} UUID ${value.uuid} not found in prefab context, setting to null (external reference)`);
|
||
return null;
|
||
}
|
||
|
||
// 处理复杂类型,添加__type__标记
|
||
if (value && typeof value === 'object') {
|
||
if (type === 'cc.Color') {
|
||
return {
|
||
"__type__": "cc.Color",
|
||
"r": Math.min(255, Math.max(0, Number(value.r) || 0)),
|
||
"g": Math.min(255, Math.max(0, Number(value.g) || 0)),
|
||
"b": Math.min(255, Math.max(0, Number(value.b) || 0)),
|
||
"a": value.a !== undefined ? Math.min(255, Math.max(0, Number(value.a))) : 255
|
||
};
|
||
} else if (type === 'cc.Vec3') {
|
||
return {
|
||
"__type__": "cc.Vec3",
|
||
"x": Number(value.x) || 0,
|
||
"y": Number(value.y) || 0,
|
||
"z": Number(value.z) || 0
|
||
};
|
||
} else if (type === 'cc.Vec2') {
|
||
return {
|
||
"__type__": "cc.Vec2",
|
||
"x": Number(value.x) || 0,
|
||
"y": Number(value.y) || 0
|
||
};
|
||
} else if (type === 'cc.Size') {
|
||
return {
|
||
"__type__": "cc.Size",
|
||
"width": Number(value.width) || 0,
|
||
"height": Number(value.height) || 0
|
||
};
|
||
} else if (type === 'cc.Quat') {
|
||
return {
|
||
"__type__": "cc.Quat",
|
||
"x": Number(value.x) || 0,
|
||
"y": Number(value.y) || 0,
|
||
"z": Number(value.z) || 0,
|
||
"w": value.w !== undefined ? Number(value.w) : 1
|
||
};
|
||
}
|
||
}
|
||
|
||
// 处理数组类型
|
||
if (Array.isArray(value)) {
|
||
// 节点数组
|
||
if (propData.elementTypeData?.type === 'cc.Node') {
|
||
return value.map(item => {
|
||
if (item?.uuid && context?.nodeUuidToIndex?.has(item.uuid)) {
|
||
return { "__id__": context.nodeUuidToIndex.get(item.uuid) };
|
||
}
|
||
return null;
|
||
}).filter(item => item !== null);
|
||
}
|
||
|
||
// 资源数组
|
||
if (propData.elementTypeData?.type && propData.elementTypeData.type.startsWith('cc.')) {
|
||
return value.map(item => {
|
||
if (item?.uuid) {
|
||
return {
|
||
"__uuid__": this.uuidToCompressedId(item.uuid),
|
||
"__expectedType__": propData.elementTypeData.type
|
||
};
|
||
}
|
||
return null;
|
||
}).filter(item => item !== null);
|
||
}
|
||
|
||
// 基础类型数组
|
||
return value.map(item => item?.value !== undefined ? item.value : item);
|
||
}
|
||
|
||
// 其他复杂对象类型,保持原样但确保有__type__标记
|
||
if (value && typeof value === 'object' && type && type.startsWith('cc.')) {
|
||
return {
|
||
"__type__": type,
|
||
...value
|
||
};
|
||
}
|
||
|
||
return value;
|
||
}
|
||
|
||
/**
|
||
* 创建符合引擎标准的节点对象
|
||
*/
|
||
private createEngineStandardNode(nodeData: any, parentNodeIndex: number | null, nodeName?: string): any {
|
||
// 调试:打印原始节点数据(已注释)
|
||
// console.log('原始节点数据:', JSON.stringify(nodeData, null, 2));
|
||
|
||
// 提取节点的基本属性
|
||
const getValue = (prop: any) => {
|
||
if (prop?.value !== undefined) return prop.value;
|
||
if (prop !== undefined) return prop;
|
||
return null;
|
||
};
|
||
|
||
const position = getValue(nodeData.position) || getValue(nodeData.value?.position) || { x: 0, y: 0, z: 0 };
|
||
const rotation = getValue(nodeData.rotation) || getValue(nodeData.value?.rotation) || { x: 0, y: 0, z: 0, w: 1 };
|
||
const scale = getValue(nodeData.scale) || getValue(nodeData.value?.scale) || { x: 1, y: 1, z: 1 };
|
||
const active = getValue(nodeData.active) ?? getValue(nodeData.value?.active) ?? true;
|
||
const name = nodeName || getValue(nodeData.name) || getValue(nodeData.value?.name) || 'Node';
|
||
const layer = getValue(nodeData.layer) || getValue(nodeData.value?.layer) || 1073741824;
|
||
|
||
// 调试输出
|
||
console.log(`创建节点: ${name}, parentNodeIndex: ${parentNodeIndex}`);
|
||
|
||
const parentRef = parentNodeIndex !== null ? { "__id__": parentNodeIndex } : null;
|
||
console.log(`节点 ${name} 的父节点引用:`, parentRef);
|
||
|
||
return {
|
||
"__type__": "cc.Node",
|
||
"_name": name,
|
||
"_objFlags": 0,
|
||
"__editorExtras__": {},
|
||
"_parent": parentRef,
|
||
"_children": [], // 子节点引用将在递归过程中动态添加
|
||
"_active": active,
|
||
"_components": [], // 组件引用将在处理组件时动态添加
|
||
"_prefab": { "__id__": 0 }, // 临时值,后续会被正确设置
|
||
"_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": ""
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 从节点数据中提取UUID
|
||
*/
|
||
private extractNodeUuid(nodeData: any): string | null {
|
||
if (!nodeData) return null;
|
||
|
||
// 尝试多种方式获取UUID
|
||
const sources = [
|
||
nodeData.uuid,
|
||
nodeData.value?.uuid,
|
||
nodeData.__uuid__,
|
||
nodeData.value?.__uuid__,
|
||
nodeData.id,
|
||
nodeData.value?.id
|
||
];
|
||
|
||
for (const source of sources) {
|
||
if (typeof source === 'string' && source.length > 0) {
|
||
return source;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 创建最小化的节点对象,不包含任何组件以避免依赖问题
|
||
*/
|
||
private createMinimalNode(nodeData: any, nodeName?: string): any {
|
||
// 提取节点的基本属性
|
||
const getValue = (prop: any) => {
|
||
if (prop?.value !== undefined) return prop.value;
|
||
if (prop !== undefined) return prop;
|
||
return null;
|
||
};
|
||
|
||
const position = getValue(nodeData.position) || getValue(nodeData.value?.position) || { x: 0, y: 0, z: 0 };
|
||
const rotation = getValue(nodeData.rotation) || getValue(nodeData.value?.rotation) || { x: 0, y: 0, z: 0, w: 1 };
|
||
const scale = getValue(nodeData.scale) || getValue(nodeData.value?.scale) || { x: 1, y: 1, z: 1 };
|
||
const active = getValue(nodeData.active) ?? getValue(nodeData.value?.active) ?? true;
|
||
const name = nodeName || getValue(nodeData.name) || getValue(nodeData.value?.name) || 'Node';
|
||
const layer = getValue(nodeData.layer) || getValue(nodeData.value?.layer) || 33554432;
|
||
|
||
return {
|
||
"__type__": "cc.Node",
|
||
"_name": name,
|
||
"_objFlags": 0,
|
||
"_parent": null,
|
||
"_children": [],
|
||
"_active": active,
|
||
"_components": [], // 空的组件数组,避免组件依赖问题
|
||
"_prefab": {
|
||
"__id__": 2
|
||
},
|
||
"_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
|
||
},
|
||
"_layer": layer,
|
||
"_euler": {
|
||
"__type__": "cc.Vec3",
|
||
"x": 0,
|
||
"y": 0,
|
||
"z": 0
|
||
},
|
||
"_id": ""
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 创建标准的 meta 文件内容
|
||
*/
|
||
private createStandardMetaContent(prefabName: string, prefabUuid: string): any {
|
||
return {
|
||
"ver": "2.0.3",
|
||
"importer": "prefab",
|
||
"imported": true,
|
||
"uuid": prefabUuid,
|
||
"files": [
|
||
".json"
|
||
],
|
||
"subMetas": {},
|
||
"userData": {
|
||
"syncNodeName": prefabName,
|
||
"hasIcon": false
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 尝试将原始节点转换为预制体实例
|
||
*/
|
||
private async convertNodeToPrefabInstance(nodeUuid: string, prefabUuid: string, prefabPath: string): Promise<{ success: boolean; error?: string }> {
|
||
return new Promise((resolve) => {
|
||
// 这个功能需要深入的场景编辑器集成,暂时返回失败
|
||
// 在实际的引擎中,这涉及到复杂的预制体实例化和节点替换逻辑
|
||
console.log('节点转换为预制体实例的功能需要更深入的引擎集成');
|
||
resolve({
|
||
success: false,
|
||
error: '节点转换为预制体实例需要更深入的引擎集成支持'
|
||
});
|
||
});
|
||
}
|
||
|
||
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 - 修复asset引用使用UUID
|
||
const rootPrefabInfo = {
|
||
"__type__": "cc.PrefabInfo",
|
||
"root": {
|
||
"__id__": 1
|
||
},
|
||
"asset": {
|
||
"__uuid__": prefabUuid
|
||
},
|
||
"fileId": this.generateFileId(),
|
||
"instance": null,
|
||
"targetOverrides": [],
|
||
"nestedPrefabInstanceRoots": []
|
||
};
|
||
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++;
|
||
|
||
// 提取节点的基本属性 - 适配query-node-tree的数据格式
|
||
const getValue = (prop: any) => {
|
||
if (prop?.value !== undefined) return prop.value;
|
||
if (prop !== undefined) return prop;
|
||
return null;
|
||
};
|
||
|
||
const position = getValue(nodeData.position) || getValue(nodeData.value?.position) || { x: 0, y: 0, z: 0 };
|
||
const rotation = getValue(nodeData.rotation) || getValue(nodeData.value?.rotation) || { x: 0, y: 0, z: 0, w: 1 };
|
||
const scale = getValue(nodeData.scale) || getValue(nodeData.value?.scale) || { x: 1, y: 1, z: 1 };
|
||
const active = getValue(nodeData.active) ?? getValue(nodeData.value?.active) ?? true;
|
||
const name = getValue(nodeData.name) || getValue(nodeData.value?.name) || 'Node';
|
||
const layer = getValue(nodeData.layer) || getValue(nodeData.value?.layer) || 33554432;
|
||
|
||
const node: any = {
|
||
"__type__": "cc.Node",
|
||
"_name": name,
|
||
"_objFlags": 0,
|
||
"__editorExtras__": {},
|
||
"_parent": parentId !== null ? { "__id__": parentId } : null,
|
||
"_children": [],
|
||
"_active": active,
|
||
"_components": [],
|
||
"_prefab": parentId === null ? {
|
||
"__id__": currentId++
|
||
} : null,
|
||
"_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": ""
|
||
};
|
||
|
||
// 暂时跳过UITransform组件以避免_getDependComponent错误
|
||
// 后续通过Engine API动态添加
|
||
console.log(`节点 ${name} 暂时跳过UITransform组件,避免引擎依赖错误`);
|
||
|
||
// 处理其他组件(暂时跳过,专注于修复UITransform问题)
|
||
const components = this.extractComponentsFromNode(nodeData);
|
||
if (components.length > 0) {
|
||
console.log(`节点 ${name} 包含 ${components.length} 个其他组件,暂时跳过以专注于UITransform修复`);
|
||
}
|
||
|
||
// 处理子节点 - 使用query-node-tree获取的完整结构
|
||
const childrenToProcess = this.getChildrenToProcess(nodeData);
|
||
if (childrenToProcess.length > 0) {
|
||
console.log(`=== 处理子节点 ===`);
|
||
console.log(`节点 ${name} 包含 ${childrenToProcess.length} 个子节点`);
|
||
|
||
for (let i = 0; i < childrenToProcess.length; i++) {
|
||
const childData = childrenToProcess[i];
|
||
const childName = childData.name || childData.value?.name || '未知';
|
||
console.log(`处理第${i + 1}个子节点: ${childName}`);
|
||
|
||
try {
|
||
const childId = currentId;
|
||
node._children.push({ "__id__": childId });
|
||
|
||
// 递归创建子节点
|
||
const childResult = await this.createNodeObject(childData, nodeId, prefabData, currentId);
|
||
prefabData.push(childResult.node);
|
||
currentId = childResult.nextId;
|
||
|
||
// 子节点不需要PrefabInfo,只有根节点需要
|
||
// 子节点的_prefab应该设置为null
|
||
childResult.node._prefab = null;
|
||
|
||
console.log(`✅ 成功添加子节点: ${childName}`);
|
||
} catch (error) {
|
||
console.error(`处理子节点 ${childName} 时出错:`, error);
|
||
}
|
||
}
|
||
}
|
||
|
||
return { node, nextId: currentId };
|
||
}
|
||
|
||
// 从节点数据中提取组件信息
|
||
private extractComponentsFromNode(nodeData: any): any[] {
|
||
const components: any[] = [];
|
||
|
||
// 从不同位置尝试获取组件数据
|
||
const componentSources = [
|
||
nodeData.__comps__,
|
||
nodeData.components,
|
||
nodeData.value?.__comps__,
|
||
nodeData.value?.components
|
||
];
|
||
|
||
for (const source of componentSources) {
|
||
if (Array.isArray(source)) {
|
||
components.push(...source.filter(comp => comp && (comp.__type__ || comp.type)));
|
||
break; // 找到有效的组件数组就退出
|
||
}
|
||
}
|
||
|
||
return components;
|
||
}
|
||
|
||
// 创建标准的组件对象
|
||
private createStandardComponentObject(componentData: any, nodeId: number, prefabInfoId: number): any {
|
||
const componentType = componentData.__type__ || componentData.type;
|
||
|
||
if (!componentType) {
|
||
console.warn('组件缺少类型信息:', componentData);
|
||
return null;
|
||
}
|
||
|
||
// 基础组件结构 - 基于官方预制体格式
|
||
const component: any = {
|
||
"__type__": componentType,
|
||
"_name": "",
|
||
"_objFlags": 0,
|
||
"node": {
|
||
"__id__": nodeId
|
||
},
|
||
"_enabled": this.getComponentPropertyValue(componentData, 'enabled', true),
|
||
"__prefab": {
|
||
"__id__": prefabInfoId
|
||
}
|
||
};
|
||
|
||
// 根据组件类型添加特定属性
|
||
this.addComponentSpecificProperties(component, componentData, componentType);
|
||
|
||
// 添加_id属性
|
||
component._id = "";
|
||
|
||
return component;
|
||
}
|
||
|
||
// 添加组件特定的属性
|
||
private addComponentSpecificProperties(component: any, componentData: any, componentType: string): void {
|
||
switch (componentType) {
|
||
case 'cc.UITransform':
|
||
this.addUITransformProperties(component, componentData);
|
||
break;
|
||
case 'cc.Sprite':
|
||
this.addSpriteProperties(component, componentData);
|
||
break;
|
||
case 'cc.Label':
|
||
this.addLabelProperties(component, componentData);
|
||
break;
|
||
case 'cc.Button':
|
||
this.addButtonProperties(component, componentData);
|
||
break;
|
||
default:
|
||
// 对于未知类型的组件,复制所有安全的属性
|
||
this.addGenericProperties(component, componentData);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// UITransform组件属性
|
||
private addUITransformProperties(component: any, componentData: any): void {
|
||
component._contentSize = this.createSizeObject(
|
||
this.getComponentPropertyValue(componentData, 'contentSize', { width: 100, height: 100 })
|
||
);
|
||
component._anchorPoint = this.createVec2Object(
|
||
this.getComponentPropertyValue(componentData, 'anchorPoint', { x: 0.5, y: 0.5 })
|
||
);
|
||
}
|
||
|
||
// Sprite组件属性
|
||
private addSpriteProperties(component: any, componentData: any): void {
|
||
component._visFlags = 0;
|
||
component._customMaterial = null;
|
||
component._srcBlendFactor = 2;
|
||
component._dstBlendFactor = 4;
|
||
component._color = this.createColorObject(
|
||
this.getComponentPropertyValue(componentData, 'color', { r: 255, g: 255, b: 255, a: 255 })
|
||
);
|
||
component._spriteFrame = this.getComponentPropertyValue(componentData, 'spriteFrame', null);
|
||
component._type = this.getComponentPropertyValue(componentData, 'type', 0);
|
||
component._fillType = 0;
|
||
component._sizeMode = this.getComponentPropertyValue(componentData, 'sizeMode', 1);
|
||
component._fillCenter = this.createVec2Object({ x: 0, y: 0 });
|
||
component._fillStart = 0;
|
||
component._fillRange = 0;
|
||
component._isTrimmedMode = true;
|
||
component._useGrayscale = false;
|
||
component._atlas = null;
|
||
}
|
||
|
||
// Label组件属性
|
||
private addLabelProperties(component: any, componentData: any): void {
|
||
component._visFlags = 0;
|
||
component._customMaterial = null;
|
||
component._srcBlendFactor = 2;
|
||
component._dstBlendFactor = 4;
|
||
component._color = this.createColorObject(
|
||
this.getComponentPropertyValue(componentData, 'color', { r: 0, g: 0, b: 0, a: 255 })
|
||
);
|
||
component._string = this.getComponentPropertyValue(componentData, 'string', 'Label');
|
||
component._horizontalAlign = 1;
|
||
component._verticalAlign = 1;
|
||
component._actualFontSize = 20;
|
||
component._fontSize = this.getComponentPropertyValue(componentData, 'fontSize', 20);
|
||
component._fontFamily = 'Arial';
|
||
component._lineHeight = 40;
|
||
component._overflow = 1;
|
||
component._enableWrapText = false;
|
||
component._font = null;
|
||
component._isSystemFontUsed = true;
|
||
component._isItalic = false;
|
||
component._isBold = false;
|
||
component._isUnderline = false;
|
||
component._underlineHeight = 2;
|
||
component._cacheMode = 0;
|
||
}
|
||
|
||
// Button组件属性
|
||
private addButtonProperties(component: any, componentData: any): void {
|
||
component.clickEvents = [];
|
||
component._interactable = true;
|
||
component._transition = 2;
|
||
component._normalColor = this.createColorObject({ r: 214, g: 214, b: 214, a: 255 });
|
||
component._hoverColor = this.createColorObject({ r: 211, g: 211, b: 211, a: 255 });
|
||
component._pressedColor = this.createColorObject({ r: 255, g: 255, b: 255, a: 255 });
|
||
component._disabledColor = this.createColorObject({ r: 124, g: 124, b: 124, a: 255 });
|
||
component._duration = 0.1;
|
||
component._zoomScale = 1.2;
|
||
}
|
||
|
||
// 添加通用属性
|
||
private addGenericProperties(component: any, componentData: any): void {
|
||
// 只复制安全的、已知的属性
|
||
const safeProperties = ['enabled', 'color', 'string', 'fontSize', 'spriteFrame', 'type', 'sizeMode'];
|
||
|
||
for (const prop of safeProperties) {
|
||
if (componentData.hasOwnProperty(prop)) {
|
||
const value = this.getComponentPropertyValue(componentData, prop);
|
||
if (value !== undefined) {
|
||
component[`_${prop}`] = value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建Vec2对象
|
||
private createVec2Object(data: any): any {
|
||
return {
|
||
"__type__": "cc.Vec2",
|
||
"x": data?.x || 0,
|
||
"y": data?.y || 0
|
||
};
|
||
}
|
||
|
||
// 创建Vec3对象
|
||
private createVec3Object(data: any): any {
|
||
return {
|
||
"__type__": "cc.Vec3",
|
||
"x": data?.x || 0,
|
||
"y": data?.y || 0,
|
||
"z": data?.z || 0
|
||
};
|
||
}
|
||
|
||
// 创建Size对象
|
||
private createSizeObject(data: any): any {
|
||
return {
|
||
"__type__": "cc.Size",
|
||
"width": data?.width || 100,
|
||
"height": data?.height || 100
|
||
};
|
||
}
|
||
|
||
// 创建Color对象
|
||
private createColorObject(data: any): any {
|
||
return {
|
||
"__type__": "cc.Color",
|
||
"r": data?.r ?? 255,
|
||
"g": data?.g ?? 255,
|
||
"b": data?.b ?? 255,
|
||
"a": data?.a ?? 255
|
||
};
|
||
}
|
||
|
||
// 判断是否应该复制组件属性
|
||
private shouldCopyComponentProperty(key: string, value: any): boolean {
|
||
// 跳过内部属性和已处理的属性
|
||
if (key.startsWith('__') || key === '_enabled' || key === 'node' || key === 'enabled') {
|
||
return false;
|
||
}
|
||
|
||
// 跳过函数和undefined值
|
||
if (typeof value === 'function' || value === undefined) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
// 获取组件属性值 - 重命名以避免冲突
|
||
private getComponentPropertyValue(componentData: any, propertyName: string, defaultValue?: any): any {
|
||
// 尝试直接获取属性
|
||
if (componentData[propertyName] !== undefined) {
|
||
return this.extractValue(componentData[propertyName]);
|
||
}
|
||
|
||
// 尝试从value属性中获取
|
||
if (componentData.value && componentData.value[propertyName] !== undefined) {
|
||
return this.extractValue(componentData.value[propertyName]);
|
||
}
|
||
|
||
// 尝试带下划线前缀的属性名
|
||
const prefixedName = `_${propertyName}`;
|
||
if (componentData[prefixedName] !== undefined) {
|
||
return this.extractValue(componentData[prefixedName]);
|
||
}
|
||
|
||
return defaultValue;
|
||
}
|
||
|
||
// 提取属性值
|
||
private extractValue(data: any): any {
|
||
if (data === null || data === undefined) {
|
||
return data;
|
||
}
|
||
|
||
// 如果有value属性,优先使用value
|
||
if (typeof data === 'object' && data.hasOwnProperty('value')) {
|
||
return data.value;
|
||
}
|
||
|
||
// 如果是引用对象,保持原样
|
||
if (typeof data === 'object' && (data.__id__ !== undefined || data.__uuid__ !== undefined)) {
|
||
return data;
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
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 };
|
||
}
|
||
}
|
||
|
||
} |