This is page 3 of 3. Use http://codebase.md/daxianlee/cocos-mcp-server?page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── @types
│ └── schema
│ └── package
│ ├── base
│ │ └── panels.json
│ ├── contributions
│ │ └── index.json
│ └── index.json
├── base.tsconfig.json
├── dist
│ ├── examples
│ │ └── prefab-instantiation-example.js
│ ├── main.js
│ ├── mcp-server.js
│ ├── panels
│ │ ├── default
│ │ │ └── index.js
│ │ └── tool-manager
│ │ └── index.js
│ ├── scene.js
│ ├── settings.js
│ ├── test
│ │ ├── manual-test.js
│ │ ├── mcp-tool-tester.js
│ │ ├── prefab-tools-test.js
│ │ └── tool-tester.js
│ ├── tools
│ │ ├── asset-advanced-tools.js
│ │ ├── broadcast-tools.js
│ │ ├── component-tools.js
│ │ ├── debug-tools.js
│ │ ├── node-tools.js
│ │ ├── prefab-tools.js
│ │ ├── preferences-tools.js
│ │ ├── project-tools.js
│ │ ├── reference-image-tools.js
│ │ ├── scene-advanced-tools.js
│ │ ├── scene-tools.js
│ │ ├── scene-view-tools.js
│ │ ├── server-tools.js
│ │ ├── tool-manager.js
│ │ └── validation-tools.js
│ └── types
│ └── index.js
├── FEATURE_GUIDE_CN.md
├── FEATURE_GUIDE_EN.md
├── i18n
│ ├── en.js
│ └── zh.js
├── image
│ ├── iamge2.png
│ └── image-20250717174157957.png
├── package-lock.json
├── package.json
├── README.EN.md
├── README.md
├── scripts
│ └── preinstall.js
├── source
│ ├── main.ts
│ ├── mcp-server.ts
│ ├── panels
│ │ ├── default
│ │ │ └── index.ts
│ │ └── tool-manager
│ │ └── index.ts
│ ├── scene.ts
│ ├── settings.ts
│ ├── test
│ │ ├── manual-test.ts
│ │ ├── mcp-tool-tester.ts
│ │ ├── prefab-tools-test.ts
│ │ └── tool-tester.ts
│ ├── tools
│ │ ├── asset-advanced-tools.ts
│ │ ├── broadcast-tools.ts
│ │ ├── component-tools.ts
│ │ ├── debug-tools.ts
│ │ ├── node-tools.ts
│ │ ├── prefab-tools.ts
│ │ ├── preferences-tools.ts
│ │ ├── project-tools.ts
│ │ ├── reference-image-tools.ts
│ │ ├── scene-advanced-tools.ts
│ │ ├── scene-tools.ts
│ │ ├── scene-view-tools.ts
│ │ ├── server-tools.ts
│ │ ├── tool-manager.ts
│ │ └── validation-tools.ts
│ └── types
│ └── index.ts
├── static
│ ├── icon.png
│ ├── style
│ │ └── default
│ │ └── index.css
│ └── template
│ ├── default
│ │ ├── index.html
│ │ └── tool-manager.html
│ └── vue
│ └── mcp-server-app.html
├── TestScript.js
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/source/tools/component-tools.ts:
--------------------------------------------------------------------------------
```typescript
import { ToolDefinition, ToolResponse, ToolExecutor, ComponentInfo } from '../types';
export class ComponentTools implements ToolExecutor {
getTools(): ToolDefinition[] {
return [
{
name: 'add_component',
description: 'Add a component to a specific node. IMPORTANT: You must provide the nodeUuid parameter to specify which node to add the component to.',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Target node UUID. REQUIRED: You must specify the exact node to add the component to. Use get_all_nodes or find_node_by_name to get the UUID of the desired node.'
},
componentType: {
type: 'string',
description: 'Component type (e.g., cc.Sprite, cc.Label, cc.Button)'
}
},
required: ['nodeUuid', 'componentType']
}
},
{
name: 'remove_component',
description: 'Remove a component from a node. componentType must be the component\'s classId (cid, i.e. the type field from getComponents), not the script name or class name. Use getComponents to get the correct cid.',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Node UUID'
},
componentType: {
type: 'string',
description: 'Component cid (type field from getComponents). Do NOT use script name or class name. Example: "cc.Sprite" or "9b4a7ueT9xD6aRE+AlOusy1"'
}
},
required: ['nodeUuid', 'componentType']
}
},
{
name: 'get_components',
description: 'Get all components of a node',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Node UUID'
}
},
required: ['nodeUuid']
}
},
{
name: 'get_component_info',
description: 'Get specific component information',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Node UUID'
},
componentType: {
type: 'string',
description: 'Component type to get info for'
}
},
required: ['nodeUuid', 'componentType']
}
},
{
name: 'set_component_property',
description: 'Set component property values for UI components or custom script components. Supports setting properties of built-in UI components (e.g., cc.Label, cc.Sprite) and custom script components. Note: For node basic properties (name, active, layer, etc.), use set_node_property. For node transform properties (position, rotation, scale, etc.), use set_node_transform.',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Target node UUID - Must specify the node to operate on'
},
componentType: {
type: 'string',
description: 'Component type - Can be built-in components (e.g., cc.Label) or custom script components (e.g., MyScript). If unsure about component type, use get_components first to retrieve all components on the node.',
// 移除enum限制,允许任意组件类型包括自定义脚本
},
property: {
type: 'string',
description: 'Property name - The property to set. Common properties include:\n' +
'• cc.Label: string (text content), fontSize (font size), color (text color)\n' +
'• cc.Sprite: spriteFrame (sprite frame), color (tint color), sizeMode (size mode)\n' +
'• cc.Button: normalColor (normal color), pressedColor (pressed color), target (target node)\n' +
'• cc.UITransform: contentSize (content size), anchorPoint (anchor point)\n' +
'• Custom Scripts: Based on properties defined in the script'
},
propertyType: {
type: 'string',
description: 'Property type - Must explicitly specify the property data type for correct value conversion and validation',
enum: [
'string', 'number', 'boolean', 'integer', 'float',
'color', 'vec2', 'vec3', 'size',
'node', 'component', 'spriteFrame', 'prefab', 'asset',
'nodeArray', 'colorArray', 'numberArray', 'stringArray'
]
},
value: {
description: 'Property value - Use the corresponding data format based on propertyType:\n\n' +
'📝 Basic Data Types:\n' +
'• string: "Hello World" (text string)\n' +
'• number/integer/float: 42 or 3.14 (numeric value)\n' +
'• boolean: true or false (boolean value)\n\n' +
'🎨 Color Type:\n' +
'• color: {"r":255,"g":0,"b":0,"a":255} (RGBA values, range 0-255)\n' +
' - Alternative: "#FF0000" (hexadecimal format)\n' +
' - Transparency: a value controls opacity, 255 = fully opaque, 0 = fully transparent\n\n' +
'📐 Vector and Size Types:\n' +
'• vec2: {"x":100,"y":50} (2D vector)\n' +
'• vec3: {"x":1,"y":2,"z":3} (3D vector)\n' +
'• size: {"width":100,"height":50} (size dimensions)\n\n' +
'🔗 Reference Types (using UUID strings):\n' +
'• node: "target-node-uuid" (node reference)\n' +
' How to get: Use get_all_nodes or find_node_by_name to get node UUIDs\n' +
'• component: "target-node-uuid" (component reference)\n' +
' How it works: \n' +
' 1. Provide the UUID of the NODE that contains the target component\n' +
' 2. System auto-detects required component type from property metadata\n' +
' 3. Finds the component on target node and gets its scene __id__\n' +
' 4. Sets reference using the scene __id__ (not node UUID)\n' +
' Example: value="label-node-uuid" will find cc.Label and use its scene ID\n' +
'• spriteFrame: "spriteframe-uuid" (sprite frame asset)\n' +
' How to get: Check asset database or use asset browser\n' +
'• prefab: "prefab-uuid" (prefab asset)\n' +
' How to get: Check asset database or use asset browser\n' +
'• asset: "asset-uuid" (generic asset reference)\n' +
' How to get: Check asset database or use asset browser\n\n' +
'📋 Array Types:\n' +
'• nodeArray: ["uuid1","uuid2"] (array of node UUIDs)\n' +
'• colorArray: [{"r":255,"g":0,"b":0,"a":255}] (array of colors)\n' +
'• numberArray: [1,2,3,4,5] (array of numbers)\n' +
'• stringArray: ["item1","item2"] (array of strings)'
}
},
required: ['nodeUuid', 'componentType', 'property', 'propertyType', 'value']
}
},
{
name: 'attach_script',
description: 'Attach a script component to a node',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Node UUID'
},
scriptPath: {
type: 'string',
description: 'Script asset path (e.g., db://assets/scripts/MyScript.ts)'
}
},
required: ['nodeUuid', 'scriptPath']
}
},
{
name: 'get_available_components',
description: 'Get list of available component types',
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Component category filter',
enum: ['all', 'renderer', 'ui', 'physics', 'animation', 'audio'],
default: 'all'
}
}
}
}
];
}
async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) {
case 'add_component':
return await this.addComponent(args.nodeUuid, args.componentType);
case 'remove_component':
return await this.removeComponent(args.nodeUuid, args.componentType);
case 'get_components':
return await this.getComponents(args.nodeUuid);
case 'get_component_info':
return await this.getComponentInfo(args.nodeUuid, args.componentType);
case 'set_component_property':
return await this.setComponentProperty(args);
case 'attach_script':
return await this.attachScript(args.nodeUuid, args.scriptPath);
case 'get_available_components':
return await this.getAvailableComponents(args.category);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
private async addComponent(nodeUuid: string, componentType: string): Promise<ToolResponse> {
return new Promise(async (resolve) => {
// 先查找节点上是否已存在该组件
const allComponentsInfo = await this.getComponents(nodeUuid);
if (allComponentsInfo.success && allComponentsInfo.data?.components) {
const existingComponent = allComponentsInfo.data.components.find((comp: any) => comp.type === componentType);
if (existingComponent) {
resolve({
success: true,
message: `Component '${componentType}' already exists on node`,
data: {
nodeUuid: nodeUuid,
componentType: componentType,
componentVerified: true,
existing: true
}
});
return;
}
}
// 尝试直接使用 Editor API 添加组件
Editor.Message.request('scene', 'create-component', {
uuid: nodeUuid,
component: componentType
}).then(async (result: any) => {
// 等待一段时间让Editor完成组件添加
await new Promise(resolve => setTimeout(resolve, 100));
// 重新查询节点信息验证组件是否真的添加成功
try {
const allComponentsInfo2 = await this.getComponents(nodeUuid);
if (allComponentsInfo2.success && allComponentsInfo2.data?.components) {
const addedComponent = allComponentsInfo2.data.components.find((comp: any) => comp.type === componentType);
if (addedComponent) {
resolve({
success: true,
message: `Component '${componentType}' added successfully`,
data: {
nodeUuid: nodeUuid,
componentType: componentType,
componentVerified: true,
existing: false
}
});
} else {
resolve({
success: false,
error: `Component '${componentType}' was not found on node after addition. Available components: ${allComponentsInfo2.data.components.map((c: any) => c.type).join(', ')}`
});
}
} else {
resolve({
success: false,
error: `Failed to verify component addition: ${allComponentsInfo2.error || 'Unable to get node components'}`
});
}
} catch (verifyError: any) {
resolve({
success: false,
error: `Failed to verify component addition: ${verifyError.message}`
});
}
}).catch((err: Error) => {
// 备用方案:使用场景脚本
const options = {
name: 'cocos-mcp-server',
method: 'addComponentToNode',
args: [nodeUuid, componentType]
};
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
resolve(result);
}).catch((err2: Error) => {
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
});
});
});
}
private async removeComponent(nodeUuid: string, componentType: string): Promise<ToolResponse> {
return new Promise(async (resolve) => {
// 1. 查找节点上的所有组件
const allComponentsInfo = await this.getComponents(nodeUuid);
if (!allComponentsInfo.success || !allComponentsInfo.data?.components) {
resolve({ success: false, error: `Failed to get components for node '${nodeUuid}': ${allComponentsInfo.error}` });
return;
}
// 2. 只查找type字段等于componentType的组件(即cid)
const exists = allComponentsInfo.data.components.some((comp: any) => comp.type === componentType);
if (!exists) {
resolve({ success: false, error: `Component cid '${componentType}' not found on node '${nodeUuid}'. 请用getComponents获取type字段(cid)作为componentType。` });
return;
}
// 3. 官方API直接移除
try {
await Editor.Message.request('scene', 'remove-component', {
uuid: nodeUuid,
component: componentType
});
// 4. 再查一次确认是否移除
const afterRemoveInfo = await this.getComponents(nodeUuid);
const stillExists = afterRemoveInfo.success && afterRemoveInfo.data?.components?.some((comp: any) => comp.type === componentType);
if (stillExists) {
resolve({ success: false, error: `Component cid '${componentType}' was not removed from node '${nodeUuid}'.` });
} else {
resolve({
success: true,
message: `Component cid '${componentType}' removed successfully from node '${nodeUuid}'`,
data: { nodeUuid, componentType }
});
}
} catch (err: any) {
resolve({ success: false, error: `Failed to remove component: ${err.message}` });
}
});
}
private async getComponents(nodeUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
// 优先尝试直接使用 Editor API 查询节点信息
Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeData: any) => {
if (nodeData && nodeData.__comps__) {
const components = nodeData.__comps__.map((comp: any) => ({
type: comp.__type__ || comp.cid || comp.type || 'Unknown',
uuid: comp.uuid?.value || comp.uuid || null,
enabled: comp.enabled !== undefined ? comp.enabled : true,
properties: this.extractComponentProperties(comp)
}));
resolve({
success: true,
data: {
nodeUuid: nodeUuid,
components: components
}
});
} else {
resolve({ success: false, error: 'Node not found or no components data' });
}
}).catch((err: Error) => {
// 备用方案:使用场景脚本
const options = {
name: 'cocos-mcp-server',
method: 'getNodeInfo',
args: [nodeUuid]
};
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
if (result.success) {
resolve({
success: true,
data: result.data.components
});
} else {
resolve(result);
}
}).catch((err2: Error) => {
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
});
});
});
}
private async getComponentInfo(nodeUuid: string, componentType: string): Promise<ToolResponse> {
return new Promise((resolve) => {
// 优先尝试直接使用 Editor API 查询节点信息
Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeData: any) => {
if (nodeData && nodeData.__comps__) {
const component = nodeData.__comps__.find((comp: any) => {
const compType = comp.__type__ || comp.cid || comp.type;
return compType === componentType;
});
if (component) {
resolve({
success: true,
data: {
nodeUuid: nodeUuid,
componentType: componentType,
enabled: component.enabled !== undefined ? component.enabled : true,
properties: this.extractComponentProperties(component)
}
});
} else {
resolve({ success: false, error: `Component '${componentType}' not found on node` });
}
} else {
resolve({ success: false, error: 'Node not found or no components data' });
}
}).catch((err: Error) => {
// 备用方案:使用场景脚本
const options = {
name: 'cocos-mcp-server',
method: 'getNodeInfo',
args: [nodeUuid]
};
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
if (result.success && result.data.components) {
const component = result.data.components.find((comp: any) => comp.type === componentType);
if (component) {
resolve({
success: true,
data: {
nodeUuid: nodeUuid,
componentType: componentType,
...component
}
});
} else {
resolve({ success: false, error: `Component '${componentType}' not found on node` });
}
} else {
resolve({ success: false, error: result.error || 'Failed to get component info' });
}
}).catch((err2: Error) => {
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
});
});
});
}
private extractComponentProperties(component: any): Record<string, any> {
console.log(`[extractComponentProperties] Processing component:`, Object.keys(component));
// 检查组件是否有 value 属性,这通常包含实际的组件属性
if (component.value && typeof component.value === 'object') {
console.log(`[extractComponentProperties] Found component.value with properties:`, Object.keys(component.value));
return component.value; // 直接返回 value 对象,它包含所有组件属性
}
// 备用方案:从组件对象中直接提取属性
const properties: Record<string, any> = {};
const excludeKeys = ['__type__', 'enabled', 'node', '_id', '__scriptAsset', 'uuid', 'name', '_name', '_objFlags', '_enabled', 'type', 'readonly', 'visible', 'cid', 'editor', 'extends'];
for (const key in component) {
if (!excludeKeys.includes(key) && !key.startsWith('_')) {
console.log(`[extractComponentProperties] Found direct property '${key}':`, typeof component[key]);
properties[key] = component[key];
}
}
console.log(`[extractComponentProperties] Final extracted properties:`, Object.keys(properties));
return properties;
}
private async findComponentTypeByUuid(componentUuid: string): Promise<string | null> {
console.log(`[findComponentTypeByUuid] Searching for component type with UUID: ${componentUuid}`);
if (!componentUuid) {
return null;
}
try {
const nodeTree = await Editor.Message.request('scene', 'query-node-tree');
if (!nodeTree) {
console.warn('[findComponentTypeByUuid] Failed to query node tree.');
return null;
}
const queue: any[] = [nodeTree];
while (queue.length > 0) {
const currentNodeInfo = queue.shift();
if (!currentNodeInfo || !currentNodeInfo.uuid) {
continue;
}
try {
const fullNodeData = await Editor.Message.request('scene', 'query-node', currentNodeInfo.uuid);
if (fullNodeData && fullNodeData.__comps__) {
for (const comp of fullNodeData.__comps__) {
const compAny = comp as any; // Cast to any to access dynamic properties
// The component UUID is nested in the 'value' property
if (compAny.uuid && compAny.uuid.value === componentUuid) {
const componentType = compAny.__type__;
console.log(`[findComponentTypeByUuid] Found component type '${componentType}' for UUID ${componentUuid} on node ${fullNodeData.name?.value}`);
return componentType;
}
}
}
} catch (e) {
console.warn(`[findComponentTypeByUuid] Could not query node ${currentNodeInfo.uuid}:`, e);
}
if (currentNodeInfo.children) {
for (const child of currentNodeInfo.children) {
queue.push(child);
}
}
}
console.warn(`[findComponentTypeByUuid] Component with UUID ${componentUuid} not found in scene tree.`);
return null;
} catch (error) {
console.error(`[findComponentTypeByUuid] Error while searching for component type:`, error);
return null;
}
}
private async setComponentProperty(args: any): Promise<ToolResponse> {
const { nodeUuid, componentType, property, propertyType, value } = args;
return new Promise(async (resolve) => {
try {
console.log(`[ComponentTools] Setting ${componentType}.${property} (type: ${propertyType}) = ${JSON.stringify(value)} on node ${nodeUuid}`);
// Step 0: 检测是否为节点属性,如果是则重定向到对应的节点方法
const nodeRedirectResult = await this.checkAndRedirectNodeProperties(args);
if (nodeRedirectResult) {
resolve(nodeRedirectResult);
return;
}
// Step 1: 获取组件信息,使用与getComponents相同的方法
const componentsResponse = await this.getComponents(nodeUuid);
if (!componentsResponse.success || !componentsResponse.data) {
resolve({
success: false,
error: `Failed to get components for node '${nodeUuid}': ${componentsResponse.error}`,
instruction: `Please verify that node UUID '${nodeUuid}' is correct. Use get_all_nodes or find_node_by_name to get the correct node UUID.`
});
return;
}
const allComponents = componentsResponse.data.components;
// Step 2: 查找目标组件
let targetComponent = null;
const availableTypes: string[] = [];
for (let i = 0; i < allComponents.length; i++) {
const comp = allComponents[i];
availableTypes.push(comp.type);
if (comp.type === componentType) {
targetComponent = comp;
break;
}
}
if (!targetComponent) {
// 提供更详细的错误信息和建议
const instruction = this.generateComponentSuggestion(componentType, availableTypes, property);
resolve({
success: false,
error: `Component '${componentType}' not found on node. Available components: ${availableTypes.join(', ')}`,
instruction: instruction
});
return;
}
// Step 3: 自动检测和转换属性值
let propertyInfo;
try {
console.log(`[ComponentTools] Analyzing property: ${property}`);
propertyInfo = this.analyzeProperty(targetComponent, property);
} catch (analyzeError: any) {
console.error(`[ComponentTools] Error in analyzeProperty:`, analyzeError);
resolve({
success: false,
error: `Failed to analyze property '${property}': ${analyzeError.message}`
});
return;
}
if (!propertyInfo.exists) {
resolve({
success: false,
error: `Property '${property}' not found on component '${componentType}'. Available properties: ${propertyInfo.availableProperties.join(', ')}`
});
return;
}
// Step 4: 处理属性值和设置
const originalValue = propertyInfo.originalValue;
let processedValue: any;
// 根据明确的propertyType处理属性值
switch (propertyType) {
case 'string':
processedValue = String(value);
break;
case 'number':
case 'integer':
case 'float':
processedValue = Number(value);
break;
case 'boolean':
processedValue = Boolean(value);
break;
case 'color':
if (typeof value === 'string') {
// 字符串格式:支持十六进制、颜色名称、rgb()/rgba()
processedValue = this.parseColorString(value);
} else if (typeof value === 'object' && value !== null) {
// 对象格式:验证并转换RGBA值
processedValue = {
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 {
throw new Error('Color value must be an object with r, g, b properties or a hexadecimal string (e.g., "#FF0000")');
}
break;
case 'vec2':
if (typeof value === 'object' && value !== null) {
processedValue = {
x: Number(value.x) || 0,
y: Number(value.y) || 0
};
} else {
throw new Error('Vec2 value must be an object with x, y properties');
}
break;
case 'vec3':
if (typeof value === 'object' && value !== null) {
processedValue = {
x: Number(value.x) || 0,
y: Number(value.y) || 0,
z: Number(value.z) || 0
};
} else {
throw new Error('Vec3 value must be an object with x, y, z properties');
}
break;
case 'size':
if (typeof value === 'object' && value !== null) {
processedValue = {
width: Number(value.width) || 0,
height: Number(value.height) || 0
};
} else {
throw new Error('Size value must be an object with width, height properties');
}
break;
case 'node':
if (typeof value === 'string') {
processedValue = { uuid: value };
} else {
throw new Error('Node reference value must be a string UUID');
}
break;
case 'component':
if (typeof value === 'string') {
// 组件引用需要特殊处理:通过节点UUID找到组件的__id__
processedValue = value; // 先保存节点UUID,后续会转换为__id__
} else {
throw new Error('Component reference value must be a string (node UUID containing the target component)');
}
break;
case 'spriteFrame':
case 'prefab':
case 'asset':
if (typeof value === 'string') {
processedValue = { uuid: value };
} else {
throw new Error(`${propertyType} value must be a string UUID`);
}
break;
case 'nodeArray':
if (Array.isArray(value)) {
processedValue = value.map((item: any) => {
if (typeof item === 'string') {
return { uuid: item };
} else {
throw new Error('NodeArray items must be string UUIDs');
}
});
} else {
throw new Error('NodeArray value must be an array');
}
break;
case 'colorArray':
if (Array.isArray(value)) {
processedValue = value.map((item: any) => {
if (typeof item === 'object' && item !== null && 'r' in item) {
return {
r: Math.min(255, Math.max(0, Number(item.r) || 0)),
g: Math.min(255, Math.max(0, Number(item.g) || 0)),
b: Math.min(255, Math.max(0, Number(item.b) || 0)),
a: item.a !== undefined ? Math.min(255, Math.max(0, Number(item.a))) : 255
};
} else {
return { r: 255, g: 255, b: 255, a: 255 };
}
});
} else {
throw new Error('ColorArray value must be an array');
}
break;
case 'numberArray':
if (Array.isArray(value)) {
processedValue = value.map((item: any) => Number(item));
} else {
throw new Error('NumberArray value must be an array');
}
break;
case 'stringArray':
if (Array.isArray(value)) {
processedValue = value.map((item: any) => String(item));
} else {
throw new Error('StringArray value must be an array');
}
break;
default:
throw new Error(`Unsupported property type: ${propertyType}`);
}
console.log(`[ComponentTools] Converting value: ${JSON.stringify(value)} -> ${JSON.stringify(processedValue)} (type: ${propertyType})`);
console.log(`[ComponentTools] Property analysis result: propertyInfo.type="${propertyInfo.type}", propertyType="${propertyType}"`);
console.log(`[ComponentTools] Will use color special handling: ${propertyType === 'color' && processedValue && typeof processedValue === 'object'}`);
// 用于验证的实际期望值(对于组件引用需要特殊处理)
let actualExpectedValue = processedValue;
// Step 5: 获取原始节点数据来构建正确的路径
const rawNodeData = await Editor.Message.request('scene', 'query-node', nodeUuid);
if (!rawNodeData || !rawNodeData.__comps__) {
resolve({
success: false,
error: `Failed to get raw node data for property setting`
});
return;
}
// 找到原始组件的索引
let rawComponentIndex = -1;
for (let i = 0; i < rawNodeData.__comps__.length; i++) {
const comp = rawNodeData.__comps__[i] as any;
const compType = comp.__type__ || comp.cid || comp.type || 'Unknown';
if (compType === componentType) {
rawComponentIndex = i;
break;
}
}
if (rawComponentIndex === -1) {
resolve({
success: false,
error: `Could not find component index for setting property`
});
return;
}
// 构建正确的属性路径
let propertyPath = `__comps__.${rawComponentIndex}.${property}`;
// 特殊处理资源类属性
if (propertyType === 'asset' || propertyType === 'spriteFrame' || propertyType === 'prefab' ||
(propertyInfo.type === 'asset' && propertyType === 'string')) {
console.log(`[ComponentTools] Setting asset reference:`, {
value: processedValue,
property: property,
propertyType: propertyType,
path: propertyPath
});
// Determine asset type based on property name
let assetType = 'cc.SpriteFrame'; // default
if (property.toLowerCase().includes('texture')) {
assetType = 'cc.Texture2D';
} else if (property.toLowerCase().includes('material')) {
assetType = 'cc.Material';
} else if (property.toLowerCase().includes('font')) {
assetType = 'cc.Font';
} else if (property.toLowerCase().includes('clip')) {
assetType = 'cc.AudioClip';
} else if (propertyType === 'prefab') {
assetType = 'cc.Prefab';
}
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: processedValue,
type: assetType
}
});
} else if (componentType === 'cc.UITransform' && (property === '_contentSize' || property === 'contentSize')) {
// Special handling for UITransform contentSize - set width and height separately
const width = Number(value.width) || 100;
const height = Number(value.height) || 100;
// Set width first
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: `__comps__.${rawComponentIndex}.width`,
dump: { value: width }
});
// Then set height
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: `__comps__.${rawComponentIndex}.height`,
dump: { value: height }
});
} else if (componentType === 'cc.UITransform' && (property === '_anchorPoint' || property === 'anchorPoint')) {
// Special handling for UITransform anchorPoint - set anchorX and anchorY separately
const anchorX = Number(value.x) || 0.5;
const anchorY = Number(value.y) || 0.5;
// Set anchorX first
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: `__comps__.${rawComponentIndex}.anchorX`,
dump: { value: anchorX }
});
// Then set anchorY
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: `__comps__.${rawComponentIndex}.anchorY`,
dump: { value: anchorY }
});
} else if (propertyType === 'color' && processedValue && typeof processedValue === 'object') {
// 特殊处理颜色属性,确保RGBA值正确
// Cocos Creator颜色值范围是0-255
const colorValue = {
r: Math.min(255, Math.max(0, Number(processedValue.r) || 0)),
g: Math.min(255, Math.max(0, Number(processedValue.g) || 0)),
b: Math.min(255, Math.max(0, Number(processedValue.b) || 0)),
a: processedValue.a !== undefined ? Math.min(255, Math.max(0, Number(processedValue.a))) : 255
};
console.log(`[ComponentTools] Setting color value:`, colorValue);
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: colorValue,
type: 'cc.Color'
}
});
} else if (propertyType === 'vec3' && processedValue && typeof processedValue === 'object') {
// 特殊处理Vec3属性
const vec3Value = {
x: Number(processedValue.x) || 0,
y: Number(processedValue.y) || 0,
z: Number(processedValue.z) || 0
};
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: vec3Value,
type: 'cc.Vec3'
}
});
} else if (propertyType === 'vec2' && processedValue && typeof processedValue === 'object') {
// 特殊处理Vec2属性
const vec2Value = {
x: Number(processedValue.x) || 0,
y: Number(processedValue.y) || 0
};
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: vec2Value,
type: 'cc.Vec2'
}
});
} else if (propertyType === 'size' && processedValue && typeof processedValue === 'object') {
// 特殊处理Size属性
const sizeValue = {
width: Number(processedValue.width) || 0,
height: Number(processedValue.height) || 0
};
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: sizeValue,
type: 'cc.Size'
}
});
} else if (propertyType === 'node' && processedValue && typeof processedValue === 'object' && 'uuid' in processedValue) {
// 特殊处理节点引用
console.log(`[ComponentTools] Setting node reference with UUID: ${processedValue.uuid}`);
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: processedValue,
type: 'cc.Node'
}
});
} else if (propertyType === 'component' && typeof processedValue === 'string') {
// 特殊处理组件引用:通过节点UUID找到组件的__id__
const targetNodeUuid = processedValue;
console.log(`[ComponentTools] Setting component reference - finding component on node: ${targetNodeUuid}`);
// 从当前组件的属性元数据中获取期望的组件类型
let expectedComponentType = '';
// 获取当前组件的详细信息,包括属性元数据
const currentComponentInfo = await this.getComponentInfo(nodeUuid, componentType);
if (currentComponentInfo.success && currentComponentInfo.data?.properties?.[property]) {
const propertyMeta = currentComponentInfo.data.properties[property];
// 从属性元数据中提取组件类型信息
if (propertyMeta && typeof propertyMeta === 'object') {
// 检查是否有type字段指示组件类型
if (propertyMeta.type) {
expectedComponentType = propertyMeta.type;
} else if (propertyMeta.ctor) {
// 有些属性可能使用ctor字段
expectedComponentType = propertyMeta.ctor;
} else if (propertyMeta.extends && Array.isArray(propertyMeta.extends)) {
// 检查extends数组,通常第一个是最具体的类型
for (const extendType of propertyMeta.extends) {
if (extendType.startsWith('cc.') && extendType !== 'cc.Component' && extendType !== 'cc.Object') {
expectedComponentType = extendType;
break;
}
}
}
}
}
if (!expectedComponentType) {
throw new Error(`Unable to determine required component type for property '${property}' on component '${componentType}'. Property metadata may not contain type information.`);
}
console.log(`[ComponentTools] Detected required component type: ${expectedComponentType} for property: ${property}`);
try {
// 获取目标节点的组件信息
const targetNodeData = await Editor.Message.request('scene', 'query-node', targetNodeUuid);
if (!targetNodeData || !targetNodeData.__comps__) {
throw new Error(`Target node ${targetNodeUuid} not found or has no components`);
}
// 打印目标节点的组件概览
console.log(`[ComponentTools] Target node ${targetNodeUuid} has ${targetNodeData.__comps__.length} components:`);
targetNodeData.__comps__.forEach((comp: any, index: number) => {
const sceneId = comp.value && comp.value.uuid && comp.value.uuid.value ? comp.value.uuid.value : 'unknown';
console.log(`[ComponentTools] Component ${index}: ${comp.type} (scene_id: ${sceneId})`);
});
// 查找对应的组件
let targetComponent = null;
let componentId: string | null = null;
// 在目标节点的_components数组中查找指定类型的组件
// 注意:__comps__和_components的索引是对应的
console.log(`[ComponentTools] Searching for component type: ${expectedComponentType}`);
for (let i = 0; i < targetNodeData.__comps__.length; i++) {
const comp = targetNodeData.__comps__[i] as any;
console.log(`[ComponentTools] Checking component ${i}: type=${comp.type}, target=${expectedComponentType}`);
if (comp.type === expectedComponentType) {
targetComponent = comp;
console.log(`[ComponentTools] Found matching component at index ${i}: ${comp.type}`);
// 从组件的value.uuid.value中获取组件在场景中的ID
if (comp.value && comp.value.uuid && comp.value.uuid.value) {
componentId = comp.value.uuid.value;
console.log(`[ComponentTools] Got componentId from comp.value.uuid.value: ${componentId}`);
} else {
console.log(`[ComponentTools] Component structure:`, {
hasValue: !!comp.value,
hasUuid: !!(comp.value && comp.value.uuid),
hasUuidValue: !!(comp.value && comp.value.uuid && comp.value.uuid.value),
uuidStructure: comp.value ? comp.value.uuid : 'No value'
});
throw new Error(`Unable to extract component ID from component structure`);
}
break;
}
}
if (!targetComponent) {
// 如果没找到,列出可用组件让用户了解,显示场景中的真实ID
const availableComponents = targetNodeData.__comps__.map((comp: any, index: number) => {
let sceneId = 'unknown';
// 从组件的value.uuid.value获取场景ID
if (comp.value && comp.value.uuid && comp.value.uuid.value) {
sceneId = comp.value.uuid.value;
}
return `${comp.type}(scene_id:${sceneId})`;
});
throw new Error(`Component type '${expectedComponentType}' not found on node ${targetNodeUuid}. Available components: ${availableComponents.join(', ')}`);
}
console.log(`[ComponentTools] Found component ${expectedComponentType} with scene ID: ${componentId} on node ${targetNodeUuid}`);
// 更新期望值为实际的组件ID对象格式,用于后续验证
if (componentId) {
actualExpectedValue = { uuid: componentId };
}
// 尝试使用与节点/资源引用相同的格式:{uuid: componentId}
// 测试看是否能正确设置组件引用
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: { uuid: componentId }, // 使用对象格式,像节点/资源引用一样
type: expectedComponentType
}
});
} catch (error) {
console.error(`[ComponentTools] Error setting component reference:`, error);
throw error;
}
} else if (propertyType === 'nodeArray' && Array.isArray(processedValue)) {
// 特殊处理节点数组 - 保持预处理的格式
console.log(`[ComponentTools] Setting node array:`, processedValue);
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: processedValue // 保持 [{uuid: "..."}, {uuid: "..."}] 格式
}
});
} else if (propertyType === 'colorArray' && Array.isArray(processedValue)) {
// 特殊处理颜色数组
const colorArrayValue = processedValue.map((item: any) => {
if (item && typeof item === 'object' && 'r' in item) {
return {
r: Math.min(255, Math.max(0, Number(item.r) || 0)),
g: Math.min(255, Math.max(0, Number(item.g) || 0)),
b: Math.min(255, Math.max(0, Number(item.b) || 0)),
a: item.a !== undefined ? Math.min(255, Math.max(0, Number(item.a))) : 255
};
} else {
return { r: 255, g: 255, b: 255, a: 255 };
}
});
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: {
value: colorArrayValue,
type: 'cc.Color'
}
});
} else {
// Normal property setting for non-asset properties
await Editor.Message.request('scene', 'set-property', {
uuid: nodeUuid,
path: propertyPath,
dump: { value: processedValue }
});
}
// Step 5: 等待Editor完成更新,然后验证设置结果
await new Promise(resolve => setTimeout(resolve, 200)); // 等待200ms让Editor完成更新
const verification = await this.verifyPropertyChange(nodeUuid, componentType, property, originalValue, actualExpectedValue);
resolve({
success: true,
message: `Successfully set ${componentType}.${property}`,
data: {
nodeUuid,
componentType,
property,
actualValue: verification.actualValue,
changeVerified: verification.verified
}
});
} catch (error: any) {
console.error(`[ComponentTools] Error setting property:`, error);
resolve({
success: false,
error: `Failed to set property: ${error.message}`
});
}
});
}
private async attachScript(nodeUuid: string, scriptPath: string): Promise<ToolResponse> {
return new Promise(async (resolve) => {
// 从脚本路径提取组件类名
const scriptName = scriptPath.split('/').pop()?.replace('.ts', '').replace('.js', '');
if (!scriptName) {
resolve({ success: false, error: 'Invalid script path' });
return;
}
// 先查找节点上是否已存在该脚本组件
const allComponentsInfo = await this.getComponents(nodeUuid);
if (allComponentsInfo.success && allComponentsInfo.data?.components) {
const existingScript = allComponentsInfo.data.components.find((comp: any) => comp.type === scriptName);
if (existingScript) {
resolve({
success: true,
message: `Script '${scriptName}' already exists on node`,
data: {
nodeUuid: nodeUuid,
componentName: scriptName,
existing: true
}
});
return;
}
}
// 首先尝试直接使用脚本名称作为组件类型
Editor.Message.request('scene', 'create-component', {
uuid: nodeUuid,
component: scriptName // 使用脚本名称而非UUID
}).then(async (result: any) => {
// 等待一段时间让Editor完成组件添加
await new Promise(resolve => setTimeout(resolve, 100));
// 重新查询节点信息验证脚本是否真的添加成功
const allComponentsInfo2 = await this.getComponents(nodeUuid);
if (allComponentsInfo2.success && allComponentsInfo2.data?.components) {
const addedScript = allComponentsInfo2.data.components.find((comp: any) => comp.type === scriptName);
if (addedScript) {
resolve({
success: true,
message: `Script '${scriptName}' attached successfully`,
data: {
nodeUuid: nodeUuid,
componentName: scriptName,
existing: false
}
});
} else {
resolve({
success: false,
error: `Script '${scriptName}' was not found on node after addition. Available components: ${allComponentsInfo2.data.components.map((c: any) => c.type).join(', ')}`
});
}
} else {
resolve({
success: false,
error: `Failed to verify script addition: ${allComponentsInfo2.error || 'Unable to get node components'}`
});
}
}).catch((err: Error) => {
// 备用方案:使用场景脚本
const options = {
name: 'cocos-mcp-server',
method: 'attachScript',
args: [nodeUuid, scriptPath]
};
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
resolve(result);
}).catch(() => {
resolve({
success: false,
error: `Failed to attach script '${scriptName}': ${err.message}`,
instruction: 'Please ensure the script is properly compiled and exported as a Component class. You can also manually attach the script through the Properties panel in the editor.'
});
});
});
});
}
private async getAvailableComponents(category: string = 'all'): Promise<ToolResponse> {
const componentCategories: Record<string, string[]> = {
renderer: ['cc.Sprite', 'cc.Label', 'cc.RichText', 'cc.Mask', 'cc.Graphics'],
ui: ['cc.Button', 'cc.Toggle', 'cc.Slider', 'cc.ScrollView', 'cc.EditBox', 'cc.ProgressBar'],
physics: ['cc.RigidBody2D', 'cc.BoxCollider2D', 'cc.CircleCollider2D', 'cc.PolygonCollider2D'],
animation: ['cc.Animation', 'cc.AnimationClip', 'cc.SkeletalAnimation'],
audio: ['cc.AudioSource'],
layout: ['cc.Layout', 'cc.Widget', 'cc.PageView', 'cc.PageViewIndicator'],
effects: ['cc.MotionStreak', 'cc.ParticleSystem2D'],
camera: ['cc.Camera'],
light: ['cc.Light', 'cc.DirectionalLight', 'cc.PointLight', 'cc.SpotLight']
};
let components: string[] = [];
if (category === 'all') {
for (const cat in componentCategories) {
components = components.concat(componentCategories[cat]);
}
} else if (componentCategories[category]) {
components = componentCategories[category];
}
return {
success: true,
data: {
category: category,
components: components
}
};
}
private isValidPropertyDescriptor(propData: any): boolean {
// 检查是否是有效的属性描述对象
if (typeof propData !== 'object' || propData === null) {
return false;
}
try {
const keys = Object.keys(propData);
// 避免遍历简单的数值对象(如 {width: 200, height: 150})
const isSimpleValueObject = keys.every(key => {
const value = propData[key];
return typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean';
});
if (isSimpleValueObject) {
return false;
}
// 检查是否包含属性描述符的特征字段,不使用'in'操作符
const hasName = keys.includes('name');
const hasValue = keys.includes('value');
const hasType = keys.includes('type');
const hasDisplayName = keys.includes('displayName');
const hasReadonly = keys.includes('readonly');
// 必须包含name或value字段,且通常还有type字段
const hasValidStructure = (hasName || hasValue) && (hasType || hasDisplayName || hasReadonly);
// 额外检查:如果有default字段且结构复杂,避免深度遍历
if (keys.includes('default') && propData.default && typeof propData.default === 'object') {
const defaultKeys = Object.keys(propData.default);
if (defaultKeys.includes('value') && typeof propData.default.value === 'object') {
// 这种情况下,我们只返回顶层属性,不深入遍历default.value
return hasValidStructure;
}
}
return hasValidStructure;
} catch (error) {
console.warn(`[isValidPropertyDescriptor] Error checking property descriptor:`, error);
return false;
}
}
private analyzeProperty(component: any, propertyName: string): { exists: boolean; type: string; availableProperties: string[]; originalValue: any } {
// 从复杂的组件结构中提取可用属性
const availableProperties: string[] = [];
let propertyValue: any = undefined;
let propertyExists = false;
// 尝试多种方式查找属性:
// 1. 直接属性访问
if (Object.prototype.hasOwnProperty.call(component, propertyName)) {
propertyValue = component[propertyName];
propertyExists = true;
}
// 2. 从嵌套结构中查找 (如从测试数据看到的复杂结构)
if (!propertyExists && component.properties && typeof component.properties === 'object') {
// 首先检查properties.value是否存在(这是我们在getComponents中看到的结构)
if (component.properties.value && typeof component.properties.value === 'object') {
const valueObj = component.properties.value;
for (const [key, propData] of Object.entries(valueObj)) {
// 检查propData是否是一个有效的属性描述对象
// 确保propData是对象且包含预期的属性结构
if (this.isValidPropertyDescriptor(propData)) {
const propInfo = propData as any;
availableProperties.push(key);
if (key === propertyName) {
// 优先使用value属性,如果没有则使用propData本身
try {
const propKeys = Object.keys(propInfo);
propertyValue = propKeys.includes('value') ? propInfo.value : propInfo;
} catch (error) {
// 如果检查失败,直接使用propInfo
propertyValue = propInfo;
}
propertyExists = true;
}
}
}
} else {
// 备用方案:直接从properties查找
for (const [key, propData] of Object.entries(component.properties)) {
if (this.isValidPropertyDescriptor(propData)) {
const propInfo = propData as any;
availableProperties.push(key);
if (key === propertyName) {
// 优先使用value属性,如果没有则使用propData本身
try {
const propKeys = Object.keys(propInfo);
propertyValue = propKeys.includes('value') ? propInfo.value : propInfo;
} catch (error) {
// 如果检查失败,直接使用propInfo
propertyValue = propInfo;
}
propertyExists = true;
}
}
}
}
}
// 3. 从直接属性中提取简单属性名
if (availableProperties.length === 0) {
for (const key of Object.keys(component)) {
if (!key.startsWith('_') && !['__type__', 'cid', 'node', 'uuid', 'name', 'enabled', 'type', 'readonly', 'visible'].includes(key)) {
availableProperties.push(key);
}
}
}
if (!propertyExists) {
return {
exists: false,
type: 'unknown',
availableProperties,
originalValue: undefined
};
}
let type = 'unknown';
// 智能类型检测
if (Array.isArray(propertyValue)) {
// 数组类型检测
if (propertyName.toLowerCase().includes('node')) {
type = 'nodeArray';
} else if (propertyName.toLowerCase().includes('color')) {
type = 'colorArray';
} else {
type = 'array';
}
} else if (typeof propertyValue === 'string') {
// Check if property name suggests it's an asset
if (['spriteFrame', 'texture', 'material', 'font', 'clip', 'prefab'].includes(propertyName.toLowerCase())) {
type = 'asset';
} else {
type = 'string';
}
} else if (typeof propertyValue === 'number') {
type = 'number';
} else if (typeof propertyValue === 'boolean') {
type = 'boolean';
} else if (propertyValue && typeof propertyValue === 'object') {
try {
const keys = Object.keys(propertyValue);
if (keys.includes('r') && keys.includes('g') && keys.includes('b')) {
type = 'color';
} else if (keys.includes('x') && keys.includes('y')) {
type = propertyValue.z !== undefined ? 'vec3' : 'vec2';
} else if (keys.includes('width') && keys.includes('height')) {
type = 'size';
} else if (keys.includes('uuid') || keys.includes('__uuid__')) {
// 检查是否是节点引用(通过属性名或__id__属性判断)
if (propertyName.toLowerCase().includes('node') ||
propertyName.toLowerCase().includes('target') ||
keys.includes('__id__')) {
type = 'node';
} else {
type = 'asset';
}
} else if (keys.includes('__id__')) {
// 节点引用特征
type = 'node';
} else {
type = 'object';
}
} catch (error) {
console.warn(`[analyzeProperty] Error checking property type for: ${JSON.stringify(propertyValue)}`);
type = 'object';
}
} else if (propertyValue === null || propertyValue === undefined) {
// For null/undefined values, check property name to determine type
if (['spriteFrame', 'texture', 'material', 'font', 'clip', 'prefab'].includes(propertyName.toLowerCase())) {
type = 'asset';
} else if (propertyName.toLowerCase().includes('node') ||
propertyName.toLowerCase().includes('target')) {
type = 'node';
} else if (propertyName.toLowerCase().includes('component')) {
type = 'component';
} else {
type = 'unknown';
}
}
return {
exists: true,
type,
availableProperties,
originalValue: propertyValue
};
}
private smartConvertValue(inputValue: any, propertyInfo: any): any {
const { type, originalValue } = propertyInfo;
console.log(`[smartConvertValue] Converting ${JSON.stringify(inputValue)} to type: ${type}`);
switch (type) {
case 'string':
return String(inputValue);
case 'number':
return Number(inputValue);
case 'boolean':
if (typeof inputValue === 'boolean') return inputValue;
if (typeof inputValue === 'string') {
return inputValue.toLowerCase() === 'true' || inputValue === '1';
}
return Boolean(inputValue);
case 'color':
// 优化的颜色处理,支持多种输入格式
if (typeof inputValue === 'string') {
// 字符串格式:十六进制、颜色名称、rgb()/rgba()
return this.parseColorString(inputValue);
} else if (typeof inputValue === 'object' && inputValue !== null) {
try {
const inputKeys = Object.keys(inputValue);
// 如果输入是颜色对象,验证并转换
if (inputKeys.includes('r') || inputKeys.includes('g') || inputKeys.includes('b')) {
return {
r: Math.min(255, Math.max(0, Number(inputValue.r) || 0)),
g: Math.min(255, Math.max(0, Number(inputValue.g) || 0)),
b: Math.min(255, Math.max(0, Number(inputValue.b) || 0)),
a: inputValue.a !== undefined ? Math.min(255, Math.max(0, Number(inputValue.a))) : 255
};
}
} catch (error) {
console.warn(`[smartConvertValue] Invalid color object: ${JSON.stringify(inputValue)}`);
}
}
// 如果有原值,保持原值结构并更新提供的值
if (originalValue && typeof originalValue === 'object') {
try {
const inputKeys = typeof inputValue === 'object' && inputValue ? Object.keys(inputValue) : [];
return {
r: inputKeys.includes('r') ? Math.min(255, Math.max(0, Number(inputValue.r))) : (originalValue.r || 255),
g: inputKeys.includes('g') ? Math.min(255, Math.max(0, Number(inputValue.g))) : (originalValue.g || 255),
b: inputKeys.includes('b') ? Math.min(255, Math.max(0, Number(inputValue.b))) : (originalValue.b || 255),
a: inputKeys.includes('a') ? Math.min(255, Math.max(0, Number(inputValue.a))) : (originalValue.a || 255)
};
} catch (error) {
console.warn(`[smartConvertValue] Error processing color with original value: ${error}`);
}
}
// 默认返回白色
console.warn(`[smartConvertValue] Using default white color for invalid input: ${JSON.stringify(inputValue)}`);
return { r: 255, g: 255, b: 255, a: 255 };
case 'vec2':
if (typeof inputValue === 'object' && inputValue !== null) {
return {
x: Number(inputValue.x) || originalValue.x || 0,
y: Number(inputValue.y) || originalValue.y || 0
};
}
return originalValue;
case 'vec3':
if (typeof inputValue === 'object' && inputValue !== null) {
return {
x: Number(inputValue.x) || originalValue.x || 0,
y: Number(inputValue.y) || originalValue.y || 0,
z: Number(inputValue.z) || originalValue.z || 0
};
}
return originalValue;
case 'size':
if (typeof inputValue === 'object' && inputValue !== null) {
return {
width: Number(inputValue.width) || originalValue.width || 100,
height: Number(inputValue.height) || originalValue.height || 100
};
}
return originalValue;
case 'node':
if (typeof inputValue === 'string') {
// 节点引用需要特殊处理
return inputValue;
} else if (typeof inputValue === 'object' && inputValue !== null) {
// 如果已经是对象形式,返回UUID或完整对象
return inputValue.uuid || inputValue;
}
return originalValue;
case 'asset':
if (typeof inputValue === 'string') {
// 如果输入是字符串路径,转换为asset对象
return { uuid: inputValue };
} else if (typeof inputValue === 'object' && inputValue !== null) {
return inputValue;
}
return originalValue;
default:
// 对于未知类型,尽量保持原有结构
if (typeof inputValue === typeof originalValue) {
return inputValue;
}
return originalValue;
}
}
private parseColorString(colorStr: string): { r: number; g: number; b: number; a: number } {
const str = colorStr.trim();
// 只支持十六进制格式 #RRGGBB 或 #RRGGBBAA
if (str.startsWith('#')) {
if (str.length === 7) { // #RRGGBB
const r = parseInt(str.substring(1, 3), 16);
const g = parseInt(str.substring(3, 5), 16);
const b = parseInt(str.substring(5, 7), 16);
return { r, g, b, a: 255 };
} else if (str.length === 9) { // #RRGGBBAA
const r = parseInt(str.substring(1, 3), 16);
const g = parseInt(str.substring(3, 5), 16);
const b = parseInt(str.substring(5, 7), 16);
const a = parseInt(str.substring(7, 9), 16);
return { r, g, b, a };
}
}
// 如果不是有效的十六进制格式,返回错误提示
throw new Error(`Invalid color format: "${colorStr}". Only hexadecimal format is supported (e.g., "#FF0000" or "#FF0000FF")`);
}
private async verifyPropertyChange(nodeUuid: string, componentType: string, property: string, originalValue: any, expectedValue: any): Promise<{ verified: boolean; actualValue: any; fullData: any }> {
console.log(`[verifyPropertyChange] Starting verification for ${componentType}.${property}`);
console.log(`[verifyPropertyChange] Expected value:`, JSON.stringify(expectedValue));
console.log(`[verifyPropertyChange] Original value:`, JSON.stringify(originalValue));
try {
// 重新获取组件信息进行验证
console.log(`[verifyPropertyChange] Calling getComponentInfo...`);
const componentInfo = await this.getComponentInfo(nodeUuid, componentType);
console.log(`[verifyPropertyChange] getComponentInfo success:`, componentInfo.success);
const allComponents = await this.getComponents(nodeUuid);
console.log(`[verifyPropertyChange] getComponents success:`, allComponents.success);
if (componentInfo.success && componentInfo.data) {
console.log(`[verifyPropertyChange] Component data available, extracting property '${property}'`);
const allPropertyNames = Object.keys(componentInfo.data.properties || {});
console.log(`[verifyPropertyChange] Available properties:`, allPropertyNames);
const propertyData = componentInfo.data.properties?.[property];
console.log(`[verifyPropertyChange] Raw property data for '${property}':`, JSON.stringify(propertyData));
// 从属性数据中提取实际值
let actualValue = propertyData;
console.log(`[verifyPropertyChange] Initial actualValue:`, JSON.stringify(actualValue));
if (propertyData && typeof propertyData === 'object' && 'value' in propertyData) {
actualValue = propertyData.value;
console.log(`[verifyPropertyChange] Extracted actualValue from .value:`, JSON.stringify(actualValue));
} else {
console.log(`[verifyPropertyChange] No .value property found, using raw data`);
}
// 修复验证逻辑:检查实际值是否匹配期望值
let verified = false;
if (typeof expectedValue === 'object' && expectedValue !== null && 'uuid' in expectedValue) {
// 对于引用类型(节点/组件/资源),比较UUID
const actualUuid = actualValue && typeof actualValue === 'object' && 'uuid' in actualValue ? actualValue.uuid : '';
const expectedUuid = expectedValue.uuid || '';
verified = actualUuid === expectedUuid && expectedUuid !== '';
console.log(`[verifyPropertyChange] Reference comparison:`);
console.log(` - Expected UUID: "${expectedUuid}"`);
console.log(` - Actual UUID: "${actualUuid}"`);
console.log(` - UUID match: ${actualUuid === expectedUuid}`);
console.log(` - UUID not empty: ${expectedUuid !== ''}`);
console.log(` - Final verified: ${verified}`);
} else {
// 对于其他类型,直接比较值
console.log(`[verifyPropertyChange] Value comparison:`);
console.log(` - Expected type: ${typeof expectedValue}`);
console.log(` - Actual type: ${typeof actualValue}`);
if (typeof actualValue === typeof expectedValue) {
if (typeof actualValue === 'object' && actualValue !== null && expectedValue !== null) {
// 对象类型的深度比较
verified = JSON.stringify(actualValue) === JSON.stringify(expectedValue);
console.log(` - Object comparison (JSON): ${verified}`);
} else {
// 基本类型的直接比较
verified = actualValue === expectedValue;
console.log(` - Direct comparison: ${verified}`);
}
} else {
// 类型不匹配时的特殊处理(如数字和字符串)
const stringMatch = String(actualValue) === String(expectedValue);
const numberMatch = Number(actualValue) === Number(expectedValue);
verified = stringMatch || numberMatch;
console.log(` - String match: ${stringMatch}`);
console.log(` - Number match: ${numberMatch}`);
console.log(` - Type mismatch verified: ${verified}`);
}
}
console.log(`[verifyPropertyChange] Final verification result: ${verified}`);
console.log(`[verifyPropertyChange] Final actualValue:`, JSON.stringify(actualValue));
const result = {
verified,
actualValue,
fullData: {
// 只返回修改的属性信息,不返回完整组件数据
modifiedProperty: {
name: property,
before: originalValue,
expected: expectedValue,
actual: actualValue,
verified,
propertyMetadata: propertyData // 只包含这个属性的元数据
},
// 简化的组件信息
componentSummary: {
nodeUuid,
componentType,
totalProperties: Object.keys(componentInfo.data?.properties || {}).length
}
}
};
console.log(`[verifyPropertyChange] Returning result:`, JSON.stringify(result, null, 2));
return result;
} else {
console.log(`[verifyPropertyChange] ComponentInfo failed or no data:`, componentInfo);
}
} catch (error) {
console.error('[verifyPropertyChange] Verification failed with error:', error);
console.error('[verifyPropertyChange] Error stack:', error instanceof Error ? error.stack : 'No stack trace');
}
console.log(`[verifyPropertyChange] Returning fallback result`);
return {
verified: false,
actualValue: undefined,
fullData: null
};
}
/**
* 检测是否为节点属性,如果是则重定向到对应的节点方法
*/
private async checkAndRedirectNodeProperties(args: any): Promise<ToolResponse | null> {
const { nodeUuid, componentType, property, propertyType, value } = args;
// 检测是否为节点基础属性(应该使用 set_node_property)
const nodeBasicProperties = [
'name', 'active', 'layer', 'mobility', 'parent', 'children', 'hideFlags'
];
// 检测是否为节点变换属性(应该使用 set_node_transform)
const nodeTransformProperties = [
'position', 'rotation', 'scale', 'eulerAngles', 'angle'
];
// Detect attempts to set cc.Node properties (common mistake)
if (componentType === 'cc.Node' || componentType === 'Node') {
if (nodeBasicProperties.includes(property)) {
return {
success: false,
error: `Property '${property}' is a node basic property, not a component property`,
instruction: `Please use set_node_property method to set node properties: set_node_property(uuid="${nodeUuid}", property="${property}", value=${JSON.stringify(value)})`
};
} else if (nodeTransformProperties.includes(property)) {
return {
success: false,
error: `Property '${property}' is a node transform property, not a component property`,
instruction: `Please use set_node_transform method to set transform properties: set_node_transform(uuid="${nodeUuid}", ${property}=${JSON.stringify(value)})`
};
}
}
// Detect common incorrect usage
if (nodeBasicProperties.includes(property) || nodeTransformProperties.includes(property)) {
const methodName = nodeTransformProperties.includes(property) ? 'set_node_transform' : 'set_node_property';
return {
success: false,
error: `Property '${property}' is a node property, not a component property`,
instruction: `Property '${property}' should be set using ${methodName} method, not set_component_property. Please use: ${methodName}(uuid="${nodeUuid}", ${nodeTransformProperties.includes(property) ? property : `property="${property}"`}=${JSON.stringify(value)})`
};
}
return null; // 不是节点属性,继续正常处理
}
/**
* 生成组件建议信息
*/
private generateComponentSuggestion(requestedType: string, availableTypes: string[], property: string): string {
// 检查是否存在相似的组件类型
const similarTypes = availableTypes.filter(type =>
type.toLowerCase().includes(requestedType.toLowerCase()) ||
requestedType.toLowerCase().includes(type.toLowerCase())
);
let instruction = '';
if (similarTypes.length > 0) {
instruction += `\n\n🔍 Found similar components: ${similarTypes.join(', ')}`;
instruction += `\n💡 Suggestion: Perhaps you meant to set the '${similarTypes[0]}' component?`;
}
// Recommend possible components based on property name
const propertyToComponentMap: Record<string, string[]> = {
'string': ['cc.Label', 'cc.RichText', 'cc.EditBox'],
'text': ['cc.Label', 'cc.RichText'],
'fontSize': ['cc.Label', 'cc.RichText'],
'spriteFrame': ['cc.Sprite'],
'color': ['cc.Label', 'cc.Sprite', 'cc.Graphics'],
'normalColor': ['cc.Button'],
'pressedColor': ['cc.Button'],
'target': ['cc.Button'],
'contentSize': ['cc.UITransform'],
'anchorPoint': ['cc.UITransform']
};
const recommendedComponents = propertyToComponentMap[property] || [];
const availableRecommended = recommendedComponents.filter(comp => availableTypes.includes(comp));
if (availableRecommended.length > 0) {
instruction += `\n\n🎯 Based on property '${property}', recommended components: ${availableRecommended.join(', ')}`;
}
// Provide operation suggestions
instruction += `\n\n📋 Suggested Actions:`;
instruction += `\n1. Use get_components(nodeUuid="${requestedType.includes('uuid') ? 'YOUR_NODE_UUID' : 'nodeUuid'}") to view all components on the node`;
instruction += `\n2. If you need to add a component, use add_component(nodeUuid="...", componentType="${requestedType}")`;
instruction += `\n3. Verify that the component type name is correct (case-sensitive)`;
return instruction;
}
/**
* 快速验证资源设置结果
*/
private async quickVerifyAsset(nodeUuid: string, componentType: string, property: string): Promise<any> {
try {
const rawNodeData = await Editor.Message.request('scene', 'query-node', nodeUuid);
if (!rawNodeData || !rawNodeData.__comps__) {
return null;
}
// 找到组件
const component = rawNodeData.__comps__.find((comp: any) => {
const compType = comp.__type__ || comp.cid || comp.type;
return compType === componentType;
});
if (!component) {
return null;
}
// 提取属性值
const properties = this.extractComponentProperties(component);
const propertyData = properties[property];
if (propertyData && typeof propertyData === 'object' && 'value' in propertyData) {
return propertyData.value;
} else {
return propertyData;
}
} catch (error) {
console.error(`[quickVerifyAsset] Error:`, error);
return null;
}
}
}
```
--------------------------------------------------------------------------------
/source/tools/prefab-tools.ts:
--------------------------------------------------------------------------------
```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) => {
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(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 };
}
}
}
```