This is page 5 of 5. Use http://codebase.md/daxianlee/cocos-mcp-server?lines=true&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/prefab-tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ToolDefinition, ToolResponse, ToolExecutor, PrefabInfo } from '../types';
2 |
3 | export class PrefabTools implements ToolExecutor {
4 | getTools(): ToolDefinition[] {
5 | return [
6 | {
7 | name: 'get_prefab_list',
8 | description: 'Get all prefabs in the project',
9 | inputSchema: {
10 | type: 'object',
11 | properties: {
12 | folder: {
13 | type: 'string',
14 | description: 'Folder path to search (optional)',
15 | default: 'db://assets'
16 | }
17 | }
18 | }
19 | },
20 | {
21 | name: 'load_prefab',
22 | description: 'Load a prefab by path',
23 | inputSchema: {
24 | type: 'object',
25 | properties: {
26 | prefabPath: {
27 | type: 'string',
28 | description: 'Prefab asset path'
29 | }
30 | },
31 | required: ['prefabPath']
32 | }
33 | },
34 | {
35 | name: 'instantiate_prefab',
36 | description: 'Instantiate a prefab in the scene',
37 | inputSchema: {
38 | type: 'object',
39 | properties: {
40 | prefabPath: {
41 | type: 'string',
42 | description: 'Prefab asset path'
43 | },
44 | parentUuid: {
45 | type: 'string',
46 | description: 'Parent node UUID (optional)'
47 | },
48 | position: {
49 | type: 'object',
50 | description: 'Initial position',
51 | properties: {
52 | x: { type: 'number' },
53 | y: { type: 'number' },
54 | z: { type: 'number' }
55 | }
56 | }
57 | },
58 | required: ['prefabPath']
59 | }
60 | },
61 | {
62 | name: 'create_prefab',
63 | description: 'Create a prefab from a node with all children and components',
64 | inputSchema: {
65 | type: 'object',
66 | properties: {
67 | nodeUuid: {
68 | type: 'string',
69 | description: 'Source node UUID'
70 | },
71 | savePath: {
72 | type: 'string',
73 | description: 'Path to save the prefab (e.g., db://assets/prefabs/MyPrefab.prefab)'
74 | },
75 | prefabName: {
76 | type: 'string',
77 | description: 'Prefab name'
78 | }
79 | },
80 | required: ['nodeUuid', 'savePath', 'prefabName']
81 | }
82 | },
83 | {
84 | name: 'update_prefab',
85 | description: 'Update an existing prefab',
86 | inputSchema: {
87 | type: 'object',
88 | properties: {
89 | prefabPath: {
90 | type: 'string',
91 | description: 'Prefab asset path'
92 | },
93 | nodeUuid: {
94 | type: 'string',
95 | description: 'Node UUID with changes'
96 | }
97 | },
98 | required: ['prefabPath', 'nodeUuid']
99 | }
100 | },
101 | {
102 | name: 'revert_prefab',
103 | description: 'Revert prefab instance to original',
104 | inputSchema: {
105 | type: 'object',
106 | properties: {
107 | nodeUuid: {
108 | type: 'string',
109 | description: 'Prefab instance node UUID'
110 | }
111 | },
112 | required: ['nodeUuid']
113 | }
114 | },
115 | {
116 | name: 'get_prefab_info',
117 | description: 'Get detailed prefab information',
118 | inputSchema: {
119 | type: 'object',
120 | properties: {
121 | prefabPath: {
122 | type: 'string',
123 | description: 'Prefab asset path'
124 | }
125 | },
126 | required: ['prefabPath']
127 | }
128 | },
129 | {
130 | name: 'validate_prefab',
131 | description: 'Validate a prefab file format',
132 | inputSchema: {
133 | type: 'object',
134 | properties: {
135 | prefabPath: {
136 | type: 'string',
137 | description: 'Prefab asset path'
138 | }
139 | },
140 | required: ['prefabPath']
141 | }
142 | },
143 | {
144 | name: 'duplicate_prefab',
145 | description: 'Duplicate an existing prefab',
146 | inputSchema: {
147 | type: 'object',
148 | properties: {
149 | sourcePrefabPath: {
150 | type: 'string',
151 | description: 'Source prefab path'
152 | },
153 | targetPrefabPath: {
154 | type: 'string',
155 | description: 'Target prefab path'
156 | },
157 | newPrefabName: {
158 | type: 'string',
159 | description: 'New prefab name'
160 | }
161 | },
162 | required: ['sourcePrefabPath', 'targetPrefabPath']
163 | }
164 | },
165 | {
166 | name: 'restore_prefab_node',
167 | description: 'Restore prefab node using prefab asset (built-in undo record)',
168 | inputSchema: {
169 | type: 'object',
170 | properties: {
171 | nodeUuid: {
172 | type: 'string',
173 | description: 'Prefab instance node UUID'
174 | },
175 | assetUuid: {
176 | type: 'string',
177 | description: 'Prefab asset UUID'
178 | }
179 | },
180 | required: ['nodeUuid', 'assetUuid']
181 | }
182 | }
183 | ];
184 | }
185 |
186 | async execute(toolName: string, args: any): Promise<ToolResponse> {
187 | switch (toolName) {
188 | case 'get_prefab_list':
189 | return await this.getPrefabList(args.folder);
190 | case 'load_prefab':
191 | return await this.loadPrefab(args.prefabPath);
192 | case 'instantiate_prefab':
193 | return await this.instantiatePrefab(args);
194 | case 'create_prefab':
195 | return await this.createPrefab(args);
196 | case 'update_prefab':
197 | return await this.updatePrefab(args.prefabPath, args.nodeUuid);
198 | case 'revert_prefab':
199 | return await this.revertPrefab(args.nodeUuid);
200 | case 'get_prefab_info':
201 | return await this.getPrefabInfo(args.prefabPath);
202 | case 'validate_prefab':
203 | return await this.validatePrefab(args.prefabPath);
204 | case 'duplicate_prefab':
205 | return await this.duplicatePrefab(args);
206 | case 'restore_prefab_node':
207 | return await this.restorePrefabNode(args.nodeUuid, args.assetUuid);
208 | default:
209 | throw new Error(`Unknown tool: ${toolName}`);
210 | }
211 | }
212 |
213 | private async getPrefabList(folder: string = 'db://assets'): Promise<ToolResponse> {
214 | return new Promise((resolve) => {
215 | const pattern = folder.endsWith('/') ?
216 | `${folder}**/*.prefab` : `${folder}/**/*.prefab`;
217 |
218 | Editor.Message.request('asset-db', 'query-assets', {
219 | pattern: pattern
220 | }).then((results: any[]) => {
221 | const prefabs: PrefabInfo[] = results.map(asset => ({
222 | name: asset.name,
223 | path: asset.url,
224 | uuid: asset.uuid,
225 | folder: asset.url.substring(0, asset.url.lastIndexOf('/'))
226 | }));
227 | resolve({ success: true, data: prefabs });
228 | }).catch((err: Error) => {
229 | resolve({ success: false, error: err.message });
230 | });
231 | });
232 | }
233 |
234 | private async loadPrefab(prefabPath: string): Promise<ToolResponse> {
235 | return new Promise((resolve) => {
236 | Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
237 | if (!assetInfo) {
238 | throw new Error('Prefab not found');
239 | }
240 |
241 | return Editor.Message.request('scene', 'load-asset', {
242 | uuid: assetInfo.uuid
243 | });
244 | }).then((prefabData: any) => {
245 | resolve({
246 | success: true,
247 | data: {
248 | uuid: prefabData.uuid,
249 | name: prefabData.name,
250 | message: 'Prefab loaded successfully'
251 | }
252 | });
253 | }).catch((err: Error) => {
254 | resolve({ success: false, error: err.message });
255 | });
256 | });
257 | }
258 |
259 | private async instantiatePrefab(args: any): Promise<ToolResponse> {
260 | return new Promise(async (resolve) => {
261 | try {
262 | // 获取预制体资源信息
263 | const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', args.prefabPath);
264 | if (!assetInfo) {
265 | throw new Error('预制体未找到');
266 | }
267 |
268 | // 使用正确的 create-node API 从预制体资源实例化
269 | const createNodeOptions: any = {
270 | assetUuid: assetInfo.uuid
271 | };
272 |
273 | // 设置父节点
274 | if (args.parentUuid) {
275 | createNodeOptions.parent = args.parentUuid;
276 | }
277 |
278 | // 设置节点名称
279 | if (args.name) {
280 | createNodeOptions.name = args.name;
281 | } else if (assetInfo.name) {
282 | createNodeOptions.name = assetInfo.name;
283 | }
284 |
285 | // 设置初始属性(如位置)
286 | if (args.position) {
287 | createNodeOptions.dump = {
288 | position: {
289 | value: args.position
290 | }
291 | };
292 | }
293 |
294 | // 创建节点
295 | const nodeUuid = await Editor.Message.request('scene', 'create-node', createNodeOptions);
296 | const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
297 |
298 | // 注意:create-node API从预制体资源创建时应该自动建立预制体关联
299 | console.log('预制体节点创建成功:', {
300 | nodeUuid: uuid,
301 | prefabUuid: assetInfo.uuid,
302 | prefabPath: args.prefabPath
303 | });
304 |
305 | resolve({
306 | success: true,
307 | data: {
308 | nodeUuid: uuid,
309 | prefabPath: args.prefabPath,
310 | parentUuid: args.parentUuid,
311 | position: args.position,
312 | message: '预制体实例化成功,已建立预制体关联'
313 | }
314 | });
315 | } catch (err: any) {
316 | resolve({
317 | success: false,
318 | error: `预制体实例化失败: ${err.message}`,
319 | instruction: '请检查预制体路径是否正确,确保预制体文件格式正确'
320 | });
321 | }
322 | });
323 | }
324 |
325 | /**
326 | * 建立节点与预制体的关联关系
327 | * 这个方法创建必要的PrefabInfo和PrefabInstance结构
328 | */
329 | private async establishPrefabConnection(nodeUuid: string, prefabUuid: string, prefabPath: string): Promise<void> {
330 | try {
331 | // 读取预制体文件获取根节点的fileId
332 | const prefabContent = await this.readPrefabFile(prefabPath);
333 | if (!prefabContent || !prefabContent.data || !prefabContent.data.length) {
334 | throw new Error('无法读取预制体文件内容');
335 | }
336 |
337 | // 找到预制体根节点的fileId (通常是第二个对象,即索引1)
338 | const rootNode = prefabContent.data.find((item: any) => item.__type === 'cc.Node' && item._parent === null);
339 | if (!rootNode || !rootNode._prefab) {
340 | throw new Error('无法找到预制体根节点或其预制体信息');
341 | }
342 |
343 | // 获取根节点的PrefabInfo
344 | const rootPrefabInfo = prefabContent.data[rootNode._prefab.__id__];
345 | if (!rootPrefabInfo || rootPrefabInfo.__type !== 'cc.PrefabInfo') {
346 | throw new Error('无法找到预制体根节点的PrefabInfo');
347 | }
348 |
349 | const rootFileId = rootPrefabInfo.fileId;
350 |
351 | // 使用scene API建立预制体连接
352 | const prefabConnectionData = {
353 | node: nodeUuid,
354 | prefab: prefabUuid,
355 | fileId: rootFileId
356 | };
357 |
358 | // 尝试使用多种API方法建立预制体连接
359 | const connectionMethods = [
360 | () => Editor.Message.request('scene', 'connect-prefab-instance', prefabConnectionData),
361 | () => Editor.Message.request('scene', 'set-prefab-connection', prefabConnectionData),
362 | () => Editor.Message.request('scene', 'apply-prefab-link', prefabConnectionData)
363 | ];
364 |
365 | let connected = false;
366 | for (const method of connectionMethods) {
367 | try {
368 | await method();
369 | connected = true;
370 | break;
371 | } catch (error) {
372 | console.warn('预制体连接方法失败,尝试下一个方法:', error);
373 | }
374 | }
375 |
376 | if (!connected) {
377 | // 如果所有API方法都失败,尝试手动修改场景数据
378 | console.warn('所有预制体连接API都失败,尝试手动建立连接');
379 | await this.manuallyEstablishPrefabConnection(nodeUuid, prefabUuid, rootFileId);
380 | }
381 |
382 | } catch (error) {
383 | console.error('建立预制体连接失败:', error);
384 | throw error;
385 | }
386 | }
387 |
388 | /**
389 | * 手动建立预制体连接(当API方法失败时的备用方案)
390 | */
391 | private async manuallyEstablishPrefabConnection(nodeUuid: string, prefabUuid: string, rootFileId: string): Promise<void> {
392 | try {
393 | // 尝试使用dump API修改节点的_prefab属性
394 | const prefabConnectionData = {
395 | [nodeUuid]: {
396 | '_prefab': {
397 | '__uuid__': prefabUuid,
398 | '__expectedType__': 'cc.Prefab',
399 | 'fileId': rootFileId
400 | }
401 | }
402 | };
403 |
404 | await Editor.Message.request('scene', 'set-property', {
405 | uuid: nodeUuid,
406 | path: '_prefab',
407 | dump: {
408 | value: {
409 | '__uuid__': prefabUuid,
410 | '__expectedType__': 'cc.Prefab'
411 | }
412 | }
413 | });
414 |
415 | } catch (error) {
416 | console.error('手动建立预制体连接也失败:', error);
417 | // 不抛出错误,因为基本的节点创建已经成功
418 | }
419 | }
420 |
421 | /**
422 | * 读取预制体文件内容
423 | */
424 | private async readPrefabFile(prefabPath: string): Promise<any> {
425 | try {
426 | // 尝试使用asset-db API读取文件内容
427 | let assetContent: any;
428 | try {
429 | assetContent = await Editor.Message.request('asset-db', 'query-asset-info', prefabPath);
430 | if (assetContent && assetContent.source) {
431 | // 如果有source路径,直接读取文件
432 | const fs = require('fs');
433 | const path = require('path');
434 | const fullPath = path.resolve(assetContent.source);
435 | const fileContent = fs.readFileSync(fullPath, 'utf8');
436 | return JSON.parse(fileContent);
437 | }
438 | } catch (error) {
439 | console.warn('使用asset-db读取失败,尝试其他方法:', error);
440 | }
441 |
442 | // 备用方法:转换db://路径为实际文件路径
443 | const fsPath = prefabPath.replace('db://assets/', 'assets/').replace('db://assets', 'assets');
444 | const fs = require('fs');
445 | const path = require('path');
446 |
447 | // 尝试多个可能的项目根路径
448 | const possiblePaths = [
449 | path.resolve(process.cwd(), '../../NewProject_3', fsPath),
450 | path.resolve('/Users/lizhiyong/NewProject_3', fsPath),
451 | path.resolve(fsPath),
452 | // 如果是根目录下的文件,也尝试直接路径
453 | path.resolve('/Users/lizhiyong/NewProject_3/assets', path.basename(fsPath))
454 | ];
455 |
456 | console.log('尝试读取预制体文件,路径转换:', {
457 | originalPath: prefabPath,
458 | fsPath: fsPath,
459 | possiblePaths: possiblePaths
460 | });
461 |
462 | for (const fullPath of possiblePaths) {
463 | try {
464 | console.log(`检查路径: ${fullPath}`);
465 | if (fs.existsSync(fullPath)) {
466 | console.log(`找到文件: ${fullPath}`);
467 | const fileContent = fs.readFileSync(fullPath, 'utf8');
468 | const parsed = JSON.parse(fileContent);
469 | console.log('文件解析成功,数据结构:', {
470 | hasData: !!parsed.data,
471 | dataLength: parsed.data ? parsed.data.length : 0
472 | });
473 | return parsed;
474 | } else {
475 | console.log(`文件不存在: ${fullPath}`);
476 | }
477 | } catch (readError) {
478 | console.warn(`读取文件失败 ${fullPath}:`, readError);
479 | }
480 | }
481 |
482 | throw new Error('无法找到或读取预制体文件');
483 | } catch (error) {
484 | console.error('读取预制体文件失败:', error);
485 | throw error;
486 | }
487 | }
488 |
489 | private async tryCreateNodeWithPrefab(args: any): Promise<ToolResponse> {
490 | return new Promise((resolve) => {
491 | Editor.Message.request('asset-db', 'query-asset-info', args.prefabPath).then((assetInfo: any) => {
492 | if (!assetInfo) {
493 | throw new Error('预制体未找到');
494 | }
495 |
496 | // 方法2: 使用 create-node 指定预制体资源
497 | const createNodeOptions: any = {
498 | assetUuid: assetInfo.uuid
499 | };
500 |
501 | // 设置父节点
502 | if (args.parentUuid) {
503 | createNodeOptions.parent = args.parentUuid;
504 | }
505 |
506 | return Editor.Message.request('scene', 'create-node', createNodeOptions);
507 | }).then((nodeUuid: string | string[]) => {
508 | const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
509 |
510 | // 如果指定了位置,设置节点位置
511 | if (args.position && uuid) {
512 | Editor.Message.request('scene', 'set-property', {
513 | uuid: uuid,
514 | path: 'position',
515 | dump: { value: args.position }
516 | }).then(() => {
517 | resolve({
518 | success: true,
519 | data: {
520 | nodeUuid: uuid,
521 | prefabPath: args.prefabPath,
522 | position: args.position,
523 | message: '预制体实例化成功(备用方法)并设置了位置'
524 | }
525 | });
526 | }).catch(() => {
527 | resolve({
528 | success: true,
529 | data: {
530 | nodeUuid: uuid,
531 | prefabPath: args.prefabPath,
532 | message: '预制体实例化成功(备用方法)但位置设置失败'
533 | }
534 | });
535 | });
536 | } else {
537 | resolve({
538 | success: true,
539 | data: {
540 | nodeUuid: uuid,
541 | prefabPath: args.prefabPath,
542 | message: '预制体实例化成功(备用方法)'
543 | }
544 | });
545 | }
546 | }).catch((err: Error) => {
547 | resolve({
548 | success: false,
549 | error: `备用预制体实例化方法也失败: ${err.message}`
550 | });
551 | });
552 | });
553 | }
554 |
555 | private async tryAlternativeInstantiateMethods(args: any): Promise<ToolResponse> {
556 | return new Promise(async (resolve) => {
557 | try {
558 | // 方法1: 尝试使用 create-node 然后设置预制体
559 | const assetInfo = await this.getAssetInfo(args.prefabPath);
560 | if (!assetInfo) {
561 | resolve({ success: false, error: '无法获取预制体信息' });
562 | return;
563 | }
564 |
565 | // 创建空节点
566 | const createResult = await this.createNode(args.parentUuid, args.position);
567 | if (!createResult.success) {
568 | resolve(createResult);
569 | return;
570 | }
571 |
572 | // 尝试将预制体应用到节点
573 | const applyResult = await this.applyPrefabToNode(createResult.data.nodeUuid, assetInfo.uuid);
574 | if (applyResult.success) {
575 | resolve({
576 | success: true,
577 | data: {
578 | nodeUuid: createResult.data.nodeUuid,
579 | name: createResult.data.name,
580 | message: '预制体实例化成功(使用备选方法)'
581 | }
582 | });
583 | } else {
584 | resolve({
585 | success: false,
586 | error: '无法将预制体应用到节点',
587 | data: {
588 | nodeUuid: createResult.data.nodeUuid,
589 | message: '已创建节点,但无法应用预制体数据'
590 | }
591 | });
592 | }
593 |
594 | } catch (error) {
595 | resolve({ success: false, error: `备选实例化方法失败: ${error}` });
596 | }
597 | });
598 | }
599 |
600 | private async getAssetInfo(prefabPath: string): Promise<any> {
601 | return new Promise((resolve) => {
602 | Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
603 | resolve(assetInfo);
604 | }).catch(() => {
605 | resolve(null);
606 | });
607 | });
608 | }
609 |
610 | private async createNode(parentUuid?: string, position?: any): Promise<ToolResponse> {
611 | return new Promise((resolve) => {
612 | const createNodeOptions: any = {
613 | name: 'PrefabInstance'
614 | };
615 |
616 | // 设置父节点
617 | if (parentUuid) {
618 | createNodeOptions.parent = parentUuid;
619 | }
620 |
621 | // 设置位置
622 | if (position) {
623 | createNodeOptions.dump = {
624 | position: position
625 | };
626 | }
627 |
628 | Editor.Message.request('scene', 'create-node', createNodeOptions).then((nodeUuid: string | string[]) => {
629 | const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
630 | resolve({
631 | success: true,
632 | data: {
633 | nodeUuid: uuid,
634 | name: 'PrefabInstance'
635 | }
636 | });
637 | }).catch((error: any) => {
638 | resolve({ success: false, error: error.message || '创建节点失败' });
639 | });
640 | });
641 | }
642 |
643 | private async applyPrefabToNode(nodeUuid: string, prefabUuid: string): Promise<ToolResponse> {
644 | return new Promise((resolve) => {
645 | // 尝试多种方法来应用预制体数据
646 | const methods = [
647 | () => Editor.Message.request('scene', 'apply-prefab', { node: nodeUuid, prefab: prefabUuid }),
648 | () => Editor.Message.request('scene', 'set-prefab', { node: nodeUuid, prefab: prefabUuid }),
649 | () => Editor.Message.request('scene', 'load-prefab-to-node', { node: nodeUuid, prefab: prefabUuid })
650 | ];
651 |
652 | const tryMethod = (index: number) => {
653 | if (index >= methods.length) {
654 | resolve({ success: false, error: '无法应用预制体数据' });
655 | return;
656 | }
657 |
658 | methods[index]().then(() => {
659 | resolve({ success: true });
660 | }).catch(() => {
661 | tryMethod(index + 1);
662 | });
663 | };
664 |
665 | tryMethod(0);
666 | });
667 | }
668 |
669 | /**
670 | * 使用 asset-db API 创建预制体的新方法
671 | * 深度整合引擎的资源管理系统,实现完整的预制体创建流程
672 | */
673 | private async createPrefabWithAssetDB(nodeUuid: string, savePath: string, prefabName: string, includeChildren: boolean, includeComponents: boolean): Promise<ToolResponse> {
674 | return new Promise(async (resolve) => {
675 | try {
676 | console.log('=== 使用 Asset-DB API 创建预制体 ===');
677 | console.log(`节点UUID: ${nodeUuid}`);
678 | console.log(`保存路径: ${savePath}`);
679 | console.log(`预制体名称: ${prefabName}`);
680 |
681 | // 第一步:获取节点数据(包括变换属性)
682 | const nodeData = await this.getNodeData(nodeUuid);
683 | if (!nodeData) {
684 | resolve({
685 | success: false,
686 | error: '无法获取节点数据'
687 | });
688 | return;
689 | }
690 |
691 | console.log('获取到节点数据,子节点数量:', nodeData.children ? nodeData.children.length : 0);
692 |
693 | // 第二步:先创建资源文件以获取引擎分配的UUID
694 | console.log('创建预制体资源文件...');
695 | const tempPrefabContent = JSON.stringify([{"__type__": "cc.Prefab", "_name": prefabName}], null, 2);
696 | const createResult = await this.createAssetWithAssetDB(savePath, tempPrefabContent);
697 | if (!createResult.success) {
698 | resolve(createResult);
699 | return;
700 | }
701 |
702 | // 获取引擎分配的实际UUID
703 | const actualPrefabUuid = createResult.data?.uuid;
704 | if (!actualPrefabUuid) {
705 | resolve({
706 | success: false,
707 | error: '无法获取引擎分配的预制体UUID'
708 | });
709 | return;
710 | }
711 | console.log('引擎分配的UUID:', actualPrefabUuid);
712 |
713 | // 第三步:使用实际UUID重新生成预制体内容
714 | const prefabContent = await this.createStandardPrefabContent(nodeData, prefabName, actualPrefabUuid, includeChildren, includeComponents);
715 | const prefabContentString = JSON.stringify(prefabContent, null, 2);
716 |
717 | // 第四步:更新预制体文件内容
718 | console.log('更新预制体文件内容...');
719 | const updateResult = await this.updateAssetWithAssetDB(savePath, prefabContentString);
720 |
721 | // 第五步:创建对应的meta文件(使用实际UUID)
722 | console.log('创建预制体meta文件...');
723 | const metaContent = this.createStandardMetaContent(prefabName, actualPrefabUuid);
724 | const metaResult = await this.createMetaWithAssetDB(savePath, metaContent);
725 |
726 | // 第六步:重新导入资源以更新引用
727 | console.log('重新导入预制体资源...');
728 | const reimportResult = await this.reimportAssetWithAssetDB(savePath);
729 |
730 | // 第七步:尝试将原始节点转换为预制体实例
731 | console.log('尝试将原始节点转换为预制体实例...');
732 | const convertResult = await this.convertNodeToPrefabInstance(nodeUuid, actualPrefabUuid, savePath);
733 |
734 | resolve({
735 | success: true,
736 | data: {
737 | prefabUuid: actualPrefabUuid,
738 | prefabPath: savePath,
739 | nodeUuid: nodeUuid,
740 | prefabName: prefabName,
741 | convertedToPrefabInstance: convertResult.success,
742 | createAssetResult: createResult,
743 | updateResult: updateResult,
744 | metaResult: metaResult,
745 | reimportResult: reimportResult,
746 | convertResult: convertResult,
747 | message: convertResult.success ? '预制体创建并成功转换原始节点' : '预制体创建成功,但节点转换失败'
748 | }
749 | });
750 |
751 | } catch (error) {
752 | console.error('创建预制体时发生错误:', error);
753 | resolve({
754 | success: false,
755 | error: `创建预制体失败: ${error}`
756 | });
757 | }
758 | });
759 | }
760 |
761 | private async createPrefab(args: any): Promise<ToolResponse> {
762 | return new Promise(async (resolve) => {
763 | try {
764 | // 支持 prefabPath 和 savePath 两种参数名
765 | const pathParam = args.prefabPath || args.savePath;
766 | if (!pathParam) {
767 | resolve({
768 | success: false,
769 | error: '缺少预制体路径参数。请提供 prefabPath 或 savePath。'
770 | });
771 | return;
772 | }
773 |
774 | const prefabName = args.prefabName || 'NewPrefab';
775 | const fullPath = pathParam.endsWith('.prefab') ?
776 | pathParam : `${pathParam}/${prefabName}.prefab`;
777 |
778 | const includeChildren = args.includeChildren !== false; // 默认为 true
779 | const includeComponents = args.includeComponents !== false; // 默认为 true
780 |
781 | // 优先使用新的 asset-db 方法创建预制体
782 | console.log('使用新的 asset-db 方法创建预制体...');
783 | const assetDbResult = await this.createPrefabWithAssetDB(
784 | args.nodeUuid,
785 | fullPath,
786 | prefabName,
787 | includeChildren,
788 | includeComponents
789 | );
790 |
791 | if (assetDbResult.success) {
792 | resolve(assetDbResult);
793 | return;
794 | }
795 |
796 | // 如果 asset-db 方法失败,尝试使用Cocos Creator的原生预制体创建API
797 | console.log('asset-db 方法失败,尝试原生API...');
798 | const nativeResult = await this.createPrefabNative(args.nodeUuid, fullPath);
799 | if (nativeResult.success) {
800 | resolve(nativeResult);
801 | return;
802 | }
803 |
804 | // 如果原生API失败,使用自定义实现
805 | console.log('原生API失败,使用自定义实现...');
806 | const customResult = await this.createPrefabCustom(args.nodeUuid, fullPath, prefabName);
807 | resolve(customResult);
808 |
809 | } catch (error) {
810 | resolve({
811 | success: false,
812 | error: `创建预制体时发生错误: ${error}`
813 | });
814 | }
815 | });
816 | }
817 |
818 | private async createPrefabNative(nodeUuid: string, prefabPath: string): Promise<ToolResponse> {
819 | return new Promise((resolve) => {
820 | // 根据官方API文档,不存在直接的预制体创建API
821 | // 预制体创建需要手动在编辑器中完成
822 | resolve({
823 | success: false,
824 | error: '原生预制体创建API不存在',
825 | instruction: '根据Cocos Creator官方API文档,预制体创建需要手动操作:\n1. 在场景中选择节点\n2. 将节点拖拽到资源管理器中\n3. 或右键节点选择"生成预制体"'
826 | });
827 | });
828 | }
829 |
830 | private async createPrefabCustom(nodeUuid: string, prefabPath: string, prefabName: string): Promise<ToolResponse> {
831 | return new Promise(async (resolve) => {
832 | try {
833 | // 1. 获取源节点的完整数据
834 | const nodeData = await this.getNodeData(nodeUuid);
835 | if (!nodeData) {
836 | resolve({
837 | success: false,
838 | error: `无法找到节点: ${nodeUuid}`
839 | });
840 | return;
841 | }
842 |
843 | // 2. 生成预制体UUID
844 | const prefabUuid = this.generateUUID();
845 |
846 | // 3. 创建预制体数据结构
847 | const prefabData = this.createPrefabData(nodeData, prefabName, prefabUuid);
848 |
849 | // 4. 基于官方格式创建预制体数据结构
850 | console.log('=== 开始创建预制体 ===');
851 | console.log('节点名称:', nodeData.name?.value || '未知');
852 | console.log('节点UUID:', nodeData.uuid?.value || '未知');
853 | console.log('预制体保存路径:', prefabPath);
854 | console.log(`开始创建预制体,节点数据:`, nodeData);
855 | const prefabJsonData = await this.createStandardPrefabContent(nodeData, prefabName, prefabUuid, true, true);
856 |
857 | // 5. 创建标准meta文件数据
858 | const standardMetaData = this.createStandardMetaData(prefabName, prefabUuid);
859 |
860 | // 6. 保存预制体和meta文件
861 | const saveResult = await this.savePrefabWithMeta(prefabPath, prefabJsonData, standardMetaData);
862 |
863 | if (saveResult.success) {
864 | // 保存成功后,将原始节点转换为预制体实例
865 | const convertResult = await this.convertNodeToPrefabInstance(nodeUuid, prefabPath, prefabUuid);
866 |
867 | resolve({
868 | success: true,
869 | data: {
870 | prefabUuid: prefabUuid,
871 | prefabPath: prefabPath,
872 | nodeUuid: nodeUuid,
873 | prefabName: prefabName,
874 | convertedToPrefabInstance: convertResult.success,
875 | message: convertResult.success ?
876 | '自定义预制体创建成功,原始节点已转换为预制体实例' :
877 | '预制体创建成功,但节点转换失败'
878 | }
879 | });
880 | } else {
881 | resolve({
882 | success: false,
883 | error: saveResult.error || '保存预制体文件失败'
884 | });
885 | }
886 |
887 | } catch (error) {
888 | resolve({
889 | success: false,
890 | error: `创建预制体时发生错误: ${error}`
891 | });
892 | }
893 | });
894 | }
895 |
896 | private async getNodeData(nodeUuid: string): Promise<any> {
897 | return new Promise(async (resolve) => {
898 | try {
899 | // 首先获取基本节点信息
900 | const nodeInfo = await Editor.Message.request('scene', 'query-node', nodeUuid);
901 | if (!nodeInfo) {
902 | resolve(null);
903 | return;
904 | }
905 |
906 | console.log(`获取节点 ${nodeUuid} 的基本信息成功`);
907 |
908 | // 使用query-node-tree获取包含子节点的完整结构
909 | const nodeTree = await this.getNodeWithChildren(nodeUuid);
910 | if (nodeTree) {
911 | console.log(`获取节点 ${nodeUuid} 的完整树结构成功`);
912 | resolve(nodeTree);
913 | } else {
914 | console.log(`使用基本节点信息`);
915 | resolve(nodeInfo);
916 | }
917 | } catch (error) {
918 | console.warn(`获取节点数据失败 ${nodeUuid}:`, error);
919 | resolve(null);
920 | }
921 | });
922 | }
923 |
924 | // 使用query-node-tree获取包含子节点的完整节点结构
925 | private async getNodeWithChildren(nodeUuid: string): Promise<any> {
926 | try {
927 | // 获取整个场景树
928 | const tree = await Editor.Message.request('scene', 'query-node-tree');
929 | if (!tree) {
930 | return null;
931 | }
932 |
933 | // 在树中查找指定的节点
934 | const targetNode = this.findNodeInTree(tree, nodeUuid);
935 | if (targetNode) {
936 | console.log(`在场景树中找到节点 ${nodeUuid},子节点数量: ${targetNode.children ? targetNode.children.length : 0}`);
937 |
938 | // 增强节点树,获取每个节点的正确组件信息
939 | const enhancedTree = await this.enhanceTreeWithMCPComponents(targetNode);
940 | return enhancedTree;
941 | }
942 |
943 | return null;
944 | } catch (error) {
945 | console.warn(`获取节点树结构失败 ${nodeUuid}:`, error);
946 | return null;
947 | }
948 | }
949 |
950 | // 在节点树中递归查找指定UUID的节点
951 | private findNodeInTree(node: any, targetUuid: string): any {
952 | if (!node) return null;
953 |
954 | // 检查当前节点
955 | if (node.uuid === targetUuid || node.value?.uuid === targetUuid) {
956 | return node;
957 | }
958 |
959 | // 递归检查子节点
960 | if (node.children && Array.isArray(node.children)) {
961 | for (const child of node.children) {
962 | const found = this.findNodeInTree(child, targetUuid);
963 | if (found) {
964 | return found;
965 | }
966 | }
967 | }
968 |
969 | return null;
970 | }
971 |
972 | /**
973 | * 使用MCP接口增强节点树,获取正确的组件信息
974 | */
975 | private async enhanceTreeWithMCPComponents(node: any): Promise<any> {
976 | if (!node || !node.uuid) {
977 | return node;
978 | }
979 |
980 | try {
981 | // 使用MCP接口获取节点的组件信息
982 | const response = await fetch('http://localhost:8585/mcp', {
983 | method: 'POST',
984 | headers: { 'Content-Type': 'application/json' },
985 | body: JSON.stringify({
986 | "jsonrpc": "2.0",
987 | "method": "tools/call",
988 | "params": {
989 | "name": "component_get_components",
990 | "arguments": {
991 | "nodeUuid": node.uuid
992 | }
993 | },
994 | "id": Date.now()
995 | })
996 | });
997 |
998 | const mcpResult = await response.json();
999 | if (mcpResult.result?.content?.[0]?.text) {
1000 | const componentData = JSON.parse(mcpResult.result.content[0].text);
1001 | if (componentData.success && componentData.data.components) {
1002 | // 更新节点的组件信息为MCP返回的正确数据
1003 | node.components = componentData.data.components;
1004 | console.log(`节点 ${node.uuid} 获取到 ${componentData.data.components.length} 个组件,包含脚本组件的正确类型`);
1005 | }
1006 | }
1007 | } catch (error) {
1008 | console.warn(`获取节点 ${node.uuid} 的MCP组件信息失败:`, error);
1009 | }
1010 |
1011 | // 递归处理子节点
1012 | if (node.children && Array.isArray(node.children)) {
1013 | for (let i = 0; i < node.children.length; i++) {
1014 | node.children[i] = await this.enhanceTreeWithMCPComponents(node.children[i]);
1015 | }
1016 | }
1017 |
1018 | return node;
1019 | }
1020 |
1021 | private async buildBasicNodeInfo(nodeUuid: string): Promise<any> {
1022 | return new Promise((resolve) => {
1023 | // 构建基本的节点信息
1024 | Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeInfo: any) => {
1025 | if (!nodeInfo) {
1026 | resolve(null);
1027 | return;
1028 | }
1029 |
1030 | // 简化版本:只返回基本节点信息,不获取子节点和组件
1031 | // 这些信息将在后续的预制体处理中根据需要添加
1032 | const basicInfo = {
1033 | ...nodeInfo,
1034 | children: [],
1035 | components: []
1036 | };
1037 | resolve(basicInfo);
1038 | }).catch(() => {
1039 | resolve(null);
1040 | });
1041 | });
1042 | }
1043 |
1044 | // 验证节点数据是否有效
1045 | private isValidNodeData(nodeData: any): boolean {
1046 | if (!nodeData) return false;
1047 | if (typeof nodeData !== 'object') return false;
1048 |
1049 | // 检查基本属性 - 适配query-node-tree的数据格式
1050 | return nodeData.hasOwnProperty('uuid') ||
1051 | nodeData.hasOwnProperty('name') ||
1052 | nodeData.hasOwnProperty('__type__') ||
1053 | (nodeData.value && (
1054 | nodeData.value.hasOwnProperty('uuid') ||
1055 | nodeData.value.hasOwnProperty('name') ||
1056 | nodeData.value.hasOwnProperty('__type__')
1057 | ));
1058 | }
1059 |
1060 | // 提取子节点UUID的统一方法
1061 | private extractChildUuid(childRef: any): string | null {
1062 | if (!childRef) return null;
1063 |
1064 | // 方法1: 直接字符串
1065 | if (typeof childRef === 'string') {
1066 | return childRef;
1067 | }
1068 |
1069 | // 方法2: value属性包含字符串
1070 | if (childRef.value && typeof childRef.value === 'string') {
1071 | return childRef.value;
1072 | }
1073 |
1074 | // 方法3: value.uuid属性
1075 | if (childRef.value && childRef.value.uuid) {
1076 | return childRef.value.uuid;
1077 | }
1078 |
1079 | // 方法4: 直接uuid属性
1080 | if (childRef.uuid) {
1081 | return childRef.uuid;
1082 | }
1083 |
1084 | // 方法5: __id__引用 - 这种情况需要特殊处理
1085 | if (childRef.__id__ !== undefined) {
1086 | console.log(`发现__id__引用: ${childRef.__id__},可能需要从数据结构中查找`);
1087 | return null; // 暂时返回null,后续可以添加引用解析逻辑
1088 | }
1089 |
1090 | console.warn('无法提取子节点UUID:', JSON.stringify(childRef));
1091 | return null;
1092 | }
1093 |
1094 | // 获取需要处理的子节点数据
1095 | private getChildrenToProcess(nodeData: any): any[] {
1096 | const children: any[] = [];
1097 |
1098 | // 方法1: 直接从children数组获取(从query-node-tree返回的数据)
1099 | if (nodeData.children && Array.isArray(nodeData.children)) {
1100 | console.log(`从children数组获取子节点,数量: ${nodeData.children.length}`);
1101 | for (const child of nodeData.children) {
1102 | // query-node-tree返回的子节点通常已经是完整的数据结构
1103 | if (this.isValidNodeData(child)) {
1104 | children.push(child);
1105 | console.log(`添加子节点: ${child.name || child.value?.name || '未知'}`);
1106 | } else {
1107 | console.log('子节点数据无效:', JSON.stringify(child, null, 2));
1108 | }
1109 | }
1110 | } else {
1111 | console.log('节点没有子节点或children数组为空');
1112 | }
1113 |
1114 | return children;
1115 | }
1116 |
1117 | private generateUUID(): string {
1118 | // 生成符合Cocos Creator格式的UUID
1119 | const chars = '0123456789abcdef';
1120 | let uuid = '';
1121 | for (let i = 0; i < 32; i++) {
1122 | if (i === 8 || i === 12 || i === 16 || i === 20) {
1123 | uuid += '-';
1124 | }
1125 | uuid += chars[Math.floor(Math.random() * chars.length)];
1126 | }
1127 | return uuid;
1128 | }
1129 |
1130 | private createPrefabData(nodeData: any, prefabName: string, prefabUuid: string): any[] {
1131 | // 创建标准的预制体数据结构
1132 | const prefabAsset = {
1133 | "__type__": "cc.Prefab",
1134 | "_name": prefabName,
1135 | "_objFlags": 0,
1136 | "__editorExtras__": {},
1137 | "_native": "",
1138 | "data": {
1139 | "__id__": 1
1140 | },
1141 | "optimizationPolicy": 0,
1142 | "persistent": false
1143 | };
1144 |
1145 | // 处理节点数据,确保符合预制体格式
1146 | const processedNodeData = this.processNodeForPrefab(nodeData, prefabUuid);
1147 |
1148 | return [prefabAsset, ...processedNodeData];
1149 | }
1150 |
1151 | private processNodeForPrefab(nodeData: any, prefabUuid: string): any[] {
1152 | // 处理节点数据以符合预制体格式
1153 | const processedData: any[] = [];
1154 | let idCounter = 1;
1155 |
1156 | // 递归处理节点和组件
1157 | const processNode = (node: any, parentId: number = 0): number => {
1158 | const nodeId = idCounter++;
1159 |
1160 | // 创建节点对象
1161 | const processedNode = {
1162 | "__type__": "cc.Node",
1163 | "_name": node.name || "Node",
1164 | "_objFlags": 0,
1165 | "__editorExtras__": {},
1166 | "_parent": parentId > 0 ? { "__id__": parentId } : null,
1167 | "_children": node.children ? node.children.map(() => ({ "__id__": idCounter++ })) : [],
1168 | "_active": node.active !== false,
1169 | "_components": node.components ? node.components.map(() => ({ "__id__": idCounter++ })) : [],
1170 | "_prefab": {
1171 | "__id__": idCounter++
1172 | },
1173 | "_lpos": {
1174 | "__type__": "cc.Vec3",
1175 | "x": 0,
1176 | "y": 0,
1177 | "z": 0
1178 | },
1179 | "_lrot": {
1180 | "__type__": "cc.Quat",
1181 | "x": 0,
1182 | "y": 0,
1183 | "z": 0,
1184 | "w": 1
1185 | },
1186 | "_lscale": {
1187 | "__type__": "cc.Vec3",
1188 | "x": 1,
1189 | "y": 1,
1190 | "z": 1
1191 | },
1192 | "_mobility": 0,
1193 | "_layer": 1073741824,
1194 | "_euler": {
1195 | "__type__": "cc.Vec3",
1196 | "x": 0,
1197 | "y": 0,
1198 | "z": 0
1199 | },
1200 | "_id": ""
1201 | };
1202 |
1203 | processedData.push(processedNode);
1204 |
1205 | // 处理组件
1206 | if (node.components) {
1207 | node.components.forEach((component: any) => {
1208 | const componentId = idCounter++;
1209 | const processedComponents = this.processComponentForPrefab(component, componentId);
1210 | processedData.push(...processedComponents);
1211 | });
1212 | }
1213 |
1214 | // 处理子节点
1215 | if (node.children) {
1216 | node.children.forEach((child: any) => {
1217 | processNode(child, nodeId);
1218 | });
1219 | }
1220 |
1221 | return nodeId;
1222 | };
1223 |
1224 | processNode(nodeData);
1225 | return processedData;
1226 | }
1227 |
1228 | private processComponentForPrefab(component: any, componentId: number): any[] {
1229 | // 处理组件数据以符合预制体格式
1230 | const processedComponent = {
1231 | "__type__": component.type || "cc.Component",
1232 | "_name": "",
1233 | "_objFlags": 0,
1234 | "__editorExtras__": {},
1235 | "node": {
1236 | "__id__": componentId - 1
1237 | },
1238 | "_enabled": component.enabled !== false,
1239 | "__prefab": {
1240 | "__id__": componentId + 1
1241 | },
1242 | ...component.properties
1243 | };
1244 |
1245 | // 添加组件特定的预制体信息
1246 | const compPrefabInfo = {
1247 | "__type__": "cc.CompPrefabInfo",
1248 | "fileId": this.generateFileId()
1249 | };
1250 |
1251 | return [processedComponent, compPrefabInfo];
1252 | }
1253 |
1254 | private generateFileId(): string {
1255 | // 生成文件ID(简化版本)
1256 | const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/';
1257 | let fileId = '';
1258 | for (let i = 0; i < 22; i++) {
1259 | fileId += chars[Math.floor(Math.random() * chars.length)];
1260 | }
1261 | return fileId;
1262 | }
1263 |
1264 | private createMetaData(prefabName: string, prefabUuid: string): any {
1265 | return {
1266 | "ver": "1.1.50",
1267 | "importer": "prefab",
1268 | "imported": true,
1269 | "uuid": prefabUuid,
1270 | "files": [
1271 | ".json"
1272 | ],
1273 | "subMetas": {},
1274 | "userData": {
1275 | "syncNodeName": prefabName
1276 | }
1277 | };
1278 | }
1279 |
1280 | private async savePrefabFiles(prefabPath: string, prefabData: any[], metaData: any): Promise<{ success: boolean; error?: string }> {
1281 | return new Promise((resolve) => {
1282 | try {
1283 | // 使用Editor API保存预制体文件
1284 | const prefabContent = JSON.stringify(prefabData, null, 2);
1285 | const metaContent = JSON.stringify(metaData, null, 2);
1286 |
1287 | // 尝试使用更可靠的保存方法
1288 | this.saveAssetFile(prefabPath, prefabContent).then(() => {
1289 | // 再创建meta文件
1290 | const metaPath = `${prefabPath}.meta`;
1291 | return this.saveAssetFile(metaPath, metaContent);
1292 | }).then(() => {
1293 | resolve({ success: true });
1294 | }).catch((error: any) => {
1295 | resolve({ success: false, error: error.message || '保存预制体文件失败' });
1296 | });
1297 | } catch (error) {
1298 | resolve({ success: false, error: `保存文件时发生错误: ${error}` });
1299 | }
1300 | });
1301 | }
1302 |
1303 | private async saveAssetFile(filePath: string, content: string): Promise<void> {
1304 | return new Promise((resolve, reject) => {
1305 | // 尝试多种保存方法
1306 | const saveMethods = [
1307 | () => Editor.Message.request('asset-db', 'create-asset', filePath, content),
1308 | () => Editor.Message.request('asset-db', 'save-asset', filePath, content),
1309 | () => Editor.Message.request('asset-db', 'write-asset', filePath, content)
1310 | ];
1311 |
1312 | const trySave = (index: number) => {
1313 | if (index >= saveMethods.length) {
1314 | reject(new Error('所有保存方法都失败了'));
1315 | return;
1316 | }
1317 |
1318 | saveMethods[index]().then(() => {
1319 | resolve();
1320 | }).catch(() => {
1321 | trySave(index + 1);
1322 | });
1323 | };
1324 |
1325 | trySave(0);
1326 | });
1327 | }
1328 |
1329 | private async updatePrefab(prefabPath: string, nodeUuid: string): Promise<ToolResponse> {
1330 | return new Promise((resolve) => {
1331 | Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
1332 | if (!assetInfo) {
1333 | throw new Error('Prefab not found');
1334 | }
1335 |
1336 | return Editor.Message.request('scene', 'apply-prefab', {
1337 | node: nodeUuid,
1338 | prefab: assetInfo.uuid
1339 | });
1340 | }).then(() => {
1341 | resolve({
1342 | success: true,
1343 | message: 'Prefab updated successfully'
1344 | });
1345 | }).catch((err: Error) => {
1346 | resolve({ success: false, error: err.message });
1347 | });
1348 | });
1349 | }
1350 |
1351 | private async revertPrefab(nodeUuid: string): Promise<ToolResponse> {
1352 | return new Promise((resolve) => {
1353 | Editor.Message.request('scene', 'revert-prefab', {
1354 | node: nodeUuid
1355 | }).then(() => {
1356 | resolve({
1357 | success: true,
1358 | message: 'Prefab instance reverted successfully'
1359 | });
1360 | }).catch((err: Error) => {
1361 | resolve({ success: false, error: err.message });
1362 | });
1363 | });
1364 | }
1365 |
1366 | private async getPrefabInfo(prefabPath: string): Promise<ToolResponse> {
1367 | return new Promise((resolve) => {
1368 | Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
1369 | if (!assetInfo) {
1370 | throw new Error('Prefab not found');
1371 | }
1372 |
1373 | return Editor.Message.request('asset-db', 'query-asset-meta', assetInfo.uuid);
1374 | }).then((metaInfo: any) => {
1375 | const info: PrefabInfo = {
1376 | name: metaInfo.name,
1377 | uuid: metaInfo.uuid,
1378 | path: prefabPath,
1379 | folder: prefabPath.substring(0, prefabPath.lastIndexOf('/')),
1380 | createTime: metaInfo.createTime,
1381 | modifyTime: metaInfo.modifyTime,
1382 | dependencies: metaInfo.depends || []
1383 | };
1384 | resolve({ success: true, data: info });
1385 | }).catch((err: Error) => {
1386 | resolve({ success: false, error: err.message });
1387 | });
1388 | });
1389 | }
1390 |
1391 | private async createPrefabFromNode(args: any): Promise<ToolResponse> {
1392 | // 从 prefabPath 提取名称
1393 | const prefabPath = args.prefabPath;
1394 | const prefabName = prefabPath.split('/').pop()?.replace('.prefab', '') || 'NewPrefab';
1395 |
1396 | // 调用原来的 createPrefab 方法
1397 | return await this.createPrefab({
1398 | nodeUuid: args.nodeUuid,
1399 | savePath: prefabPath,
1400 | prefabName: prefabName
1401 | });
1402 | }
1403 |
1404 | private async validatePrefab(prefabPath: string): Promise<ToolResponse> {
1405 | return new Promise((resolve) => {
1406 | try {
1407 | // 读取预制体文件内容
1408 | Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
1409 | if (!assetInfo) {
1410 | resolve({
1411 | success: false,
1412 | error: '预制体文件不存在'
1413 | });
1414 | return;
1415 | }
1416 |
1417 | // 验证预制体格式
1418 | Editor.Message.request('asset-db', 'read-asset', prefabPath).then((content: string) => {
1419 | try {
1420 | const prefabData = JSON.parse(content);
1421 | const validationResult = this.validatePrefabFormat(prefabData);
1422 |
1423 | resolve({
1424 | success: true,
1425 | data: {
1426 | isValid: validationResult.isValid,
1427 | issues: validationResult.issues,
1428 | nodeCount: validationResult.nodeCount,
1429 | componentCount: validationResult.componentCount,
1430 | message: validationResult.isValid ? '预制体格式有效' : '预制体格式存在问题'
1431 | }
1432 | });
1433 | } catch (parseError) {
1434 | resolve({
1435 | success: false,
1436 | error: '预制体文件格式错误,无法解析JSON'
1437 | });
1438 | }
1439 | }).catch((error: any) => {
1440 | resolve({
1441 | success: false,
1442 | error: `读取预制体文件失败: ${error.message}`
1443 | });
1444 | });
1445 | }).catch((error: any) => {
1446 | resolve({
1447 | success: false,
1448 | error: `查询预制体信息失败: ${error.message}`
1449 | });
1450 | });
1451 | } catch (error) {
1452 | resolve({
1453 | success: false,
1454 | error: `验证预制体时发生错误: ${error}`
1455 | });
1456 | }
1457 | });
1458 | }
1459 |
1460 | private validatePrefabFormat(prefabData: any): { isValid: boolean; issues: string[]; nodeCount: number; componentCount: number } {
1461 | const issues: string[] = [];
1462 | let nodeCount = 0;
1463 | let componentCount = 0;
1464 |
1465 | // 检查基本结构
1466 | if (!Array.isArray(prefabData)) {
1467 | issues.push('预制体数据必须是数组格式');
1468 | return { isValid: false, issues, nodeCount, componentCount };
1469 | }
1470 |
1471 | if (prefabData.length === 0) {
1472 | issues.push('预制体数据为空');
1473 | return { isValid: false, issues, nodeCount, componentCount };
1474 | }
1475 |
1476 | // 检查第一个元素是否为预制体资产
1477 | const firstElement = prefabData[0];
1478 | if (!firstElement || firstElement.__type__ !== 'cc.Prefab') {
1479 | issues.push('第一个元素必须是cc.Prefab类型');
1480 | }
1481 |
1482 | // 统计节点和组件
1483 | prefabData.forEach((item: any, index: number) => {
1484 | if (item.__type__ === 'cc.Node') {
1485 | nodeCount++;
1486 | } else if (item.__type__ && item.__type__.includes('cc.')) {
1487 | componentCount++;
1488 | }
1489 | });
1490 |
1491 | // 检查必要的字段
1492 | if (nodeCount === 0) {
1493 | issues.push('预制体必须包含至少一个节点');
1494 | }
1495 |
1496 | return {
1497 | isValid: issues.length === 0,
1498 | issues,
1499 | nodeCount,
1500 | componentCount
1501 | };
1502 | }
1503 |
1504 | private async duplicatePrefab(args: any): Promise<ToolResponse> {
1505 | return new Promise(async (resolve) => {
1506 | try {
1507 | const { sourcePrefabPath, targetPrefabPath, newPrefabName } = args;
1508 |
1509 | // 读取源预制体
1510 | const sourceInfo = await this.getPrefabInfo(sourcePrefabPath);
1511 | if (!sourceInfo.success) {
1512 | resolve({
1513 | success: false,
1514 | error: `无法读取源预制体: ${sourceInfo.error}`
1515 | });
1516 | return;
1517 | }
1518 |
1519 | // 读取源预制体内容
1520 | const sourceContent = await this.readPrefabContent(sourcePrefabPath);
1521 | if (!sourceContent.success) {
1522 | resolve({
1523 | success: false,
1524 | error: `无法读取源预制体内容: ${sourceContent.error}`
1525 | });
1526 | return;
1527 | }
1528 |
1529 | // 生成新的UUID
1530 | const newUuid = this.generateUUID();
1531 |
1532 | // 修改预制体数据
1533 | const modifiedData = this.modifyPrefabForDuplication(sourceContent.data, newPrefabName, newUuid);
1534 |
1535 | // 创建新的meta数据
1536 | const newMetaData = this.createMetaData(newPrefabName || 'DuplicatedPrefab', newUuid);
1537 |
1538 | // 预制体复制功能暂时禁用,因为涉及复杂的序列化格式
1539 | resolve({
1540 | success: false,
1541 | error: '预制体复制功能暂时不可用',
1542 | instruction: '请在 Cocos Creator 编辑器中手动复制预制体:\n1. 在资源管理器中选择要复制的预制体\n2. 右键选择复制\n3. 在目标位置粘贴'
1543 | });
1544 |
1545 | } catch (error) {
1546 | resolve({
1547 | success: false,
1548 | error: `复制预制体时发生错误: ${error}`
1549 | });
1550 | }
1551 | });
1552 | }
1553 |
1554 | private async readPrefabContent(prefabPath: string): Promise<{ success: boolean; data?: any; error?: string }> {
1555 | return new Promise((resolve) => {
1556 | Editor.Message.request('asset-db', 'read-asset', prefabPath).then((content: string) => {
1557 | try {
1558 | const prefabData = JSON.parse(content);
1559 | resolve({ success: true, data: prefabData });
1560 | } catch (parseError) {
1561 | resolve({ success: false, error: '预制体文件格式错误' });
1562 | }
1563 | }).catch((error: any) => {
1564 | resolve({ success: false, error: error.message || '读取预制体文件失败' });
1565 | });
1566 | });
1567 | }
1568 |
1569 | private modifyPrefabForDuplication(prefabData: any[], newName: string, newUuid: string): any[] {
1570 | // 修改预制体数据以创建副本
1571 | const modifiedData = [...prefabData];
1572 |
1573 | // 修改第一个元素(预制体资产)
1574 | if (modifiedData[0] && modifiedData[0].__type__ === 'cc.Prefab') {
1575 | modifiedData[0]._name = newName || 'DuplicatedPrefab';
1576 | }
1577 |
1578 | // 更新所有UUID引用(简化版本)
1579 | // 在实际应用中,可能需要更复杂的UUID映射处理
1580 |
1581 | return modifiedData;
1582 | }
1583 |
1584 | /**
1585 | * 使用 asset-db API 创建资源文件
1586 | */
1587 | private async createAssetWithAssetDB(assetPath: string, content: string): Promise<{ success: boolean; data?: any; error?: string }> {
1588 | return new Promise((resolve) => {
1589 | Editor.Message.request('asset-db', 'create-asset', assetPath, content, {
1590 | overwrite: true,
1591 | rename: false
1592 | }).then((assetInfo: any) => {
1593 | console.log('创建资源文件成功:', assetInfo);
1594 | resolve({ success: true, data: assetInfo });
1595 | }).catch((error: any) => {
1596 | console.error('创建资源文件失败:', error);
1597 | resolve({ success: false, error: error.message || '创建资源文件失败' });
1598 | });
1599 | });
1600 | }
1601 |
1602 | /**
1603 | * 使用 asset-db API 创建 meta 文件
1604 | */
1605 | private async createMetaWithAssetDB(assetPath: string, metaContent: any): Promise<{ success: boolean; data?: any; error?: string }> {
1606 | return new Promise((resolve) => {
1607 | const metaContentString = JSON.stringify(metaContent, null, 2);
1608 | Editor.Message.request('asset-db', 'save-asset-meta', assetPath, metaContentString).then((assetInfo: any) => {
1609 | console.log('创建meta文件成功:', assetInfo);
1610 | resolve({ success: true, data: assetInfo });
1611 | }).catch((error: any) => {
1612 | console.error('创建meta文件失败:', error);
1613 | resolve({ success: false, error: error.message || '创建meta文件失败' });
1614 | });
1615 | });
1616 | }
1617 |
1618 | /**
1619 | * 使用 asset-db API 重新导入资源
1620 | */
1621 | private async reimportAssetWithAssetDB(assetPath: string): Promise<{ success: boolean; data?: any; error?: string }> {
1622 | return new Promise((resolve) => {
1623 | Editor.Message.request('asset-db', 'reimport-asset', assetPath).then((result: any) => {
1624 | console.log('重新导入资源成功:', result);
1625 | resolve({ success: true, data: result });
1626 | }).catch((error: any) => {
1627 | console.error('重新导入资源失败:', error);
1628 | resolve({ success: false, error: error.message || '重新导入资源失败' });
1629 | });
1630 | });
1631 | }
1632 |
1633 | /**
1634 | * 使用 asset-db API 更新资源文件内容
1635 | */
1636 | private async updateAssetWithAssetDB(assetPath: string, content: string): Promise<{ success: boolean; data?: any; error?: string }> {
1637 | return new Promise((resolve) => {
1638 | Editor.Message.request('asset-db', 'save-asset', assetPath, content).then((result: any) => {
1639 | console.log('更新资源文件成功:', result);
1640 | resolve({ success: true, data: result });
1641 | }).catch((error: any) => {
1642 | console.error('更新资源文件失败:', error);
1643 | resolve({ success: false, error: error.message || '更新资源文件失败' });
1644 | });
1645 | });
1646 | }
1647 |
1648 | /**
1649 | * 创建符合 Cocos Creator 标准的预制体内容
1650 | * 完整实现递归节点树处理,匹配引擎标准格式
1651 | */
1652 | private async createStandardPrefabContent(nodeData: any, prefabName: string, prefabUuid: string, includeChildren: boolean, includeComponents: boolean): Promise<any[]> {
1653 | console.log('开始创建引擎标准预制体内容...');
1654 |
1655 | const prefabData: any[] = [];
1656 | let currentId = 0;
1657 |
1658 | // 1. 创建预制体资产对象 (index 0)
1659 | const prefabAsset = {
1660 | "__type__": "cc.Prefab",
1661 | "_name": prefabName || "", // 确保预制体名称不为空
1662 | "_objFlags": 0,
1663 | "__editorExtras__": {},
1664 | "_native": "",
1665 | "data": {
1666 | "__id__": 1
1667 | },
1668 | "optimizationPolicy": 0,
1669 | "persistent": false
1670 | };
1671 | prefabData.push(prefabAsset);
1672 | currentId++;
1673 |
1674 | // 2. 递归创建完整的节点树结构
1675 | const context = {
1676 | prefabData,
1677 | currentId: currentId + 1, // 根节点占用索引1,子节点从索引2开始
1678 | prefabAssetIndex: 0,
1679 | nodeFileIds: new Map<string, string>(), // 存储节点ID到fileId的映射
1680 | nodeUuidToIndex: new Map<string, number>(), // 存储节点UUID到索引的映射
1681 | componentUuidToIndex: new Map<string, number>() // 存储组件UUID到索引的映射
1682 | };
1683 |
1684 | // 创建根节点和整个节点树 - 注意:根节点的父节点应该是null,不是预制体对象
1685 | await this.createCompleteNodeTree(nodeData, null, 1, context, includeChildren, includeComponents, prefabName);
1686 |
1687 | console.log(`预制体内容创建完成,总共 ${prefabData.length} 个对象`);
1688 | console.log('节点fileId映射:', Array.from(context.nodeFileIds.entries()));
1689 |
1690 | return prefabData;
1691 | }
1692 |
1693 | /**
1694 | * 递归创建完整的节点树,包括所有子节点和对应的PrefabInfo
1695 | */
1696 | private async createCompleteNodeTree(
1697 | nodeData: any,
1698 | parentNodeIndex: number | null,
1699 | nodeIndex: number,
1700 | context: {
1701 | prefabData: any[],
1702 | currentId: number,
1703 | prefabAssetIndex: number,
1704 | nodeFileIds: Map<string, string>,
1705 | nodeUuidToIndex: Map<string, number>,
1706 | componentUuidToIndex: Map<string, number>
1707 | },
1708 | includeChildren: boolean,
1709 | includeComponents: boolean,
1710 | nodeName?: string
1711 | ): Promise<void> {
1712 | const { prefabData } = context;
1713 |
1714 | // 创建节点对象
1715 | const node = this.createEngineStandardNode(nodeData, parentNodeIndex, nodeName);
1716 |
1717 | // 确保节点在指定的索引位置
1718 | while (prefabData.length <= nodeIndex) {
1719 | prefabData.push(null);
1720 | }
1721 | console.log(`设置节点到索引 ${nodeIndex}: ${node._name}, _parent:`, node._parent, `_children count: ${node._children.length}`);
1722 | prefabData[nodeIndex] = node;
1723 |
1724 | // 为当前节点生成fileId并记录UUID到索引的映射
1725 | const nodeUuid = this.extractNodeUuid(nodeData);
1726 | const fileId = nodeUuid || this.generateFileId();
1727 | context.nodeFileIds.set(nodeIndex.toString(), fileId);
1728 |
1729 | // 记录节点UUID到索引的映射
1730 | if (nodeUuid) {
1731 | context.nodeUuidToIndex.set(nodeUuid, nodeIndex);
1732 | console.log(`记录节点UUID映射: ${nodeUuid} -> ${nodeIndex}`);
1733 | }
1734 |
1735 | // 先处理子节点(保持与手动创建的索引顺序一致)
1736 | const childrenToProcess = this.getChildrenToProcess(nodeData);
1737 | if (includeChildren && childrenToProcess.length > 0) {
1738 | console.log(`处理节点 ${node._name} 的 ${childrenToProcess.length} 个子节点`);
1739 |
1740 | // 为每个子节点分配索引
1741 | const childIndices: number[] = [];
1742 | console.log(`准备为 ${childrenToProcess.length} 个子节点分配索引,当前ID: ${context.currentId}`);
1743 | for (let i = 0; i < childrenToProcess.length; i++) {
1744 | console.log(`处理第 ${i+1} 个子节点,当前currentId: ${context.currentId}`);
1745 | const childIndex = context.currentId++;
1746 | childIndices.push(childIndex);
1747 | node._children.push({ "__id__": childIndex });
1748 | console.log(`✅ 添加子节点引用到 ${node._name}: {__id__: ${childIndex}}`);
1749 | }
1750 | console.log(`✅ 节点 ${node._name} 最终的子节点数组:`, node._children);
1751 |
1752 | // 递归创建子节点
1753 | for (let i = 0; i < childrenToProcess.length; i++) {
1754 | const childData = childrenToProcess[i];
1755 | const childIndex = childIndices[i];
1756 | await this.createCompleteNodeTree(
1757 | childData,
1758 | nodeIndex,
1759 | childIndex,
1760 | context,
1761 | includeChildren,
1762 | includeComponents,
1763 | childData.name || `Child${i+1}`
1764 | );
1765 | }
1766 | }
1767 |
1768 | // 然后处理组件
1769 | if (includeComponents && nodeData.components && Array.isArray(nodeData.components)) {
1770 | console.log(`处理节点 ${node._name} 的 ${nodeData.components.length} 个组件`);
1771 |
1772 | const componentIndices: number[] = [];
1773 | for (const component of nodeData.components) {
1774 | const componentIndex = context.currentId++;
1775 | componentIndices.push(componentIndex);
1776 | node._components.push({ "__id__": componentIndex });
1777 |
1778 | // 记录组件UUID到索引的映射
1779 | const componentUuid = component.uuid || (component.value && component.value.uuid);
1780 | if (componentUuid) {
1781 | context.componentUuidToIndex.set(componentUuid, componentIndex);
1782 | console.log(`记录组件UUID映射: ${componentUuid} -> ${componentIndex}`);
1783 | }
1784 |
1785 | // 创建组件对象,传入context以处理引用
1786 | const componentObj = this.createComponentObject(component, nodeIndex, context);
1787 | prefabData[componentIndex] = componentObj;
1788 |
1789 | // 为组件创建 CompPrefabInfo
1790 | const compPrefabInfoIndex = context.currentId++;
1791 | prefabData[compPrefabInfoIndex] = {
1792 | "__type__": "cc.CompPrefabInfo",
1793 | "fileId": this.generateFileId()
1794 | };
1795 |
1796 | // 如果组件对象有 __prefab 属性,设置引用
1797 | if (componentObj && typeof componentObj === 'object') {
1798 | componentObj.__prefab = { "__id__": compPrefabInfoIndex };
1799 | }
1800 | }
1801 |
1802 | console.log(`✅ 节点 ${node._name} 添加了 ${componentIndices.length} 个组件`);
1803 | }
1804 |
1805 |
1806 | // 为当前节点创建PrefabInfo
1807 | const prefabInfoIndex = context.currentId++;
1808 | node._prefab = { "__id__": prefabInfoIndex };
1809 |
1810 | const prefabInfo: any = {
1811 | "__type__": "cc.PrefabInfo",
1812 | "root": { "__id__": 1 },
1813 | "asset": { "__id__": context.prefabAssetIndex },
1814 | "fileId": fileId,
1815 | "targetOverrides": null,
1816 | "nestedPrefabInstanceRoots": null
1817 | };
1818 |
1819 | // 根节点的特殊处理
1820 | if (nodeIndex === 1) {
1821 | // 根节点没有instance,但可能有targetOverrides
1822 | prefabInfo.instance = null;
1823 | } else {
1824 | // 子节点通常有instance为null
1825 | prefabInfo.instance = null;
1826 | }
1827 |
1828 | prefabData[prefabInfoIndex] = prefabInfo;
1829 | context.currentId = prefabInfoIndex + 1;
1830 | }
1831 |
1832 | /**
1833 | * 将UUID转换为Cocos Creator的压缩格式
1834 | * 基于真实Cocos Creator编辑器的压缩算法实现
1835 | * 前5个hex字符保持不变,剩余27个字符压缩成18个字符
1836 | */
1837 | private uuidToCompressedId(uuid: string): string {
1838 | const BASE64_KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
1839 |
1840 | // 移除连字符并转为小写
1841 | const cleanUuid = uuid.replace(/-/g, '').toLowerCase();
1842 |
1843 | // 确保UUID有效
1844 | if (cleanUuid.length !== 32) {
1845 | return uuid; // 如果不是有效的UUID,返回原始值
1846 | }
1847 |
1848 | // Cocos Creator的压缩算法:前5个字符保持不变,剩余27个字符压缩成18个字符
1849 | let result = cleanUuid.substring(0, 5);
1850 |
1851 | // 剩余27个字符需要压缩成18个字符
1852 | const remainder = cleanUuid.substring(5);
1853 |
1854 | // 每3个hex字符压缩成2个字符
1855 | for (let i = 0; i < remainder.length; i += 3) {
1856 | const hex1 = remainder[i] || '0';
1857 | const hex2 = remainder[i + 1] || '0';
1858 | const hex3 = remainder[i + 2] || '0';
1859 |
1860 | // 将3个hex字符(12位)转换为2个base64字符
1861 | const value = parseInt(hex1 + hex2 + hex3, 16);
1862 |
1863 | // 12位分成两个6位
1864 | const high6 = (value >> 6) & 63;
1865 | const low6 = value & 63;
1866 |
1867 | result += BASE64_KEYS[high6] + BASE64_KEYS[low6];
1868 | }
1869 |
1870 | return result;
1871 | }
1872 |
1873 | /**
1874 | * 创建组件对象
1875 | */
1876 | private createComponentObject(componentData: any, nodeIndex: number, context?: {
1877 | nodeUuidToIndex?: Map<string, number>,
1878 | componentUuidToIndex?: Map<string, number>
1879 | }): any {
1880 | let componentType = componentData.type || componentData.__type__ || 'cc.Component';
1881 | const enabled = componentData.enabled !== undefined ? componentData.enabled : true;
1882 |
1883 | // console.log(`创建组件对象 - 原始类型: ${componentType}`);
1884 | // console.log('组件完整数据:', JSON.stringify(componentData, null, 2));
1885 |
1886 | // 处理脚本组件 - MCP接口已经返回正确的压缩UUID格式
1887 | if (componentType && !componentType.startsWith('cc.')) {
1888 | console.log(`使用脚本组件压缩UUID类型: ${componentType}`);
1889 | }
1890 |
1891 | // 基础组件结构
1892 | const component: any = {
1893 | "__type__": componentType,
1894 | "_name": "",
1895 | "_objFlags": 0,
1896 | "__editorExtras__": {},
1897 | "node": { "__id__": nodeIndex },
1898 | "_enabled": enabled
1899 | };
1900 |
1901 | // 提前设置 __prefab 属性占位符,后续会被正确设置
1902 | component.__prefab = null;
1903 |
1904 | // 根据组件类型添加特定属性
1905 | if (componentType === 'cc.UITransform') {
1906 | const contentSize = componentData.properties?.contentSize?.value || { width: 100, height: 100 };
1907 | const anchorPoint = componentData.properties?.anchorPoint?.value || { x: 0.5, y: 0.5 };
1908 |
1909 | component._contentSize = {
1910 | "__type__": "cc.Size",
1911 | "width": contentSize.width,
1912 | "height": contentSize.height
1913 | };
1914 | component._anchorPoint = {
1915 | "__type__": "cc.Vec2",
1916 | "x": anchorPoint.x,
1917 | "y": anchorPoint.y
1918 | };
1919 | } else if (componentType === 'cc.Sprite') {
1920 | // 处理Sprite组件的spriteFrame引用
1921 | const spriteFrameProp = componentData.properties?._spriteFrame || componentData.properties?.spriteFrame;
1922 | if (spriteFrameProp) {
1923 | component._spriteFrame = this.processComponentProperty(spriteFrameProp, context);
1924 | } else {
1925 | component._spriteFrame = null;
1926 | }
1927 |
1928 | component._type = componentData.properties?._type?.value ?? 0;
1929 | component._fillType = componentData.properties?._fillType?.value ?? 0;
1930 | component._sizeMode = componentData.properties?._sizeMode?.value ?? 1;
1931 | component._fillCenter = { "__type__": "cc.Vec2", "x": 0, "y": 0 };
1932 | component._fillStart = componentData.properties?._fillStart?.value ?? 0;
1933 | component._fillRange = componentData.properties?._fillRange?.value ?? 0;
1934 | component._isTrimmedMode = componentData.properties?._isTrimmedMode?.value ?? true;
1935 | component._useGrayscale = componentData.properties?._useGrayscale?.value ?? false;
1936 |
1937 | // 调试:打印Sprite组件的所有属性(已注释)
1938 | // console.log('Sprite组件属性:', JSON.stringify(componentData.properties, null, 2));
1939 | component._atlas = null;
1940 | component._id = "";
1941 | } else if (componentType === 'cc.Button') {
1942 | component._interactable = true;
1943 | component._transition = 3;
1944 | component._normalColor = { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 };
1945 | component._hoverColor = { "__type__": "cc.Color", "r": 211, "g": 211, "b": 211, "a": 255 };
1946 | component._pressedColor = { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 };
1947 | component._disabledColor = { "__type__": "cc.Color", "r": 124, "g": 124, "b": 124, "a": 255 };
1948 | component._normalSprite = null;
1949 | component._hoverSprite = null;
1950 | component._pressedSprite = null;
1951 | component._disabledSprite = null;
1952 | component._duration = 0.1;
1953 | component._zoomScale = 1.2;
1954 | // 处理Button的target引用
1955 | const targetProp = componentData.properties?._target || componentData.properties?.target;
1956 | if (targetProp) {
1957 | component._target = this.processComponentProperty(targetProp, context);
1958 | } else {
1959 | component._target = { "__id__": nodeIndex }; // 默认指向自身节点
1960 | }
1961 | component._clickEvents = [];
1962 | component._id = "";
1963 | } else if (componentType === 'cc.Label') {
1964 | component._string = componentData.properties?._string?.value || "Label";
1965 | component._horizontalAlign = 1;
1966 | component._verticalAlign = 1;
1967 | component._actualFontSize = 20;
1968 | component._fontSize = 20;
1969 | component._fontFamily = "Arial";
1970 | component._lineHeight = 25;
1971 | component._overflow = 0;
1972 | component._enableWrapText = true;
1973 | component._font = null;
1974 | component._isSystemFontUsed = true;
1975 | component._spacingX = 0;
1976 | component._isItalic = false;
1977 | component._isBold = false;
1978 | component._isUnderline = false;
1979 | component._underlineHeight = 2;
1980 | component._cacheMode = 0;
1981 | component._id = "";
1982 | } else if (componentData.properties) {
1983 | // 处理所有组件的属性(包括内置组件和自定义脚本组件)
1984 | for (const [key, value] of Object.entries(componentData.properties)) {
1985 | if (key === 'node' || key === 'enabled' || key === '__type__' ||
1986 | key === 'uuid' || key === 'name' || key === '__scriptAsset' || key === '_objFlags') {
1987 | continue; // 跳过这些特殊属性,包括_objFlags
1988 | }
1989 |
1990 | // 对于以下划线开头的属性,需要特殊处理
1991 | if (key.startsWith('_')) {
1992 | // 确保属性名保持原样(包括下划线)
1993 | const propValue = this.processComponentProperty(value, context);
1994 | if (propValue !== undefined) {
1995 | component[key] = propValue;
1996 | }
1997 | } else {
1998 | // 非下划线开头的属性正常处理
1999 | const propValue = this.processComponentProperty(value, context);
2000 | if (propValue !== undefined) {
2001 | component[key] = propValue;
2002 | }
2003 | }
2004 | }
2005 | }
2006 |
2007 | // 确保 _id 在最后位置
2008 | const _id = component._id || "";
2009 | delete component._id;
2010 | component._id = _id;
2011 |
2012 | return component;
2013 | }
2014 |
2015 | /**
2016 | * 处理组件属性值,确保格式与手动创建的预制体一致
2017 | */
2018 | private processComponentProperty(propData: any, context?: {
2019 | nodeUuidToIndex?: Map<string, number>,
2020 | componentUuidToIndex?: Map<string, number>
2021 | }): any {
2022 | if (!propData || typeof propData !== 'object') {
2023 | return propData;
2024 | }
2025 |
2026 | const value = propData.value;
2027 | const type = propData.type;
2028 |
2029 | // 处理null值
2030 | if (value === null || value === undefined) {
2031 | return null;
2032 | }
2033 |
2034 | // 处理空UUID对象,转换为null
2035 | if (value && typeof value === 'object' && value.uuid === '') {
2036 | return null;
2037 | }
2038 |
2039 | // 处理节点引用
2040 | if (type === 'cc.Node' && value?.uuid) {
2041 | // 在预制体中,节点引用需要转换为 __id__ 形式
2042 | if (context?.nodeUuidToIndex && context.nodeUuidToIndex.has(value.uuid)) {
2043 | // 内部引用:转换为__id__格式
2044 | return {
2045 | "__id__": context.nodeUuidToIndex.get(value.uuid)
2046 | };
2047 | }
2048 | // 外部引用:设置为null,因为外部节点不属于预制体结构
2049 | console.warn(`Node reference UUID ${value.uuid} not found in prefab context, setting to null (external reference)`);
2050 | return null;
2051 | }
2052 |
2053 | // 处理资源引用(预制体、纹理、精灵帧等)
2054 | if (value?.uuid && (
2055 | type === 'cc.Prefab' ||
2056 | type === 'cc.Texture2D' ||
2057 | type === 'cc.SpriteFrame' ||
2058 | type === 'cc.Material' ||
2059 | type === 'cc.AnimationClip' ||
2060 | type === 'cc.AudioClip' ||
2061 | type === 'cc.Font' ||
2062 | type === 'cc.Asset'
2063 | )) {
2064 | // 对于预制体引用,保持原始UUID格式
2065 | const uuidToUse = type === 'cc.Prefab' ? value.uuid : this.uuidToCompressedId(value.uuid);
2066 | return {
2067 | "__uuid__": uuidToUse,
2068 | "__expectedType__": type
2069 | };
2070 | }
2071 |
2072 | // 处理组件引用(包括具体的组件类型如cc.Label, cc.Button等)
2073 | if (value?.uuid && (type === 'cc.Component' ||
2074 | type === 'cc.Label' || type === 'cc.Button' || type === 'cc.Sprite' ||
2075 | type === 'cc.UITransform' || type === 'cc.RigidBody2D' ||
2076 | type === 'cc.BoxCollider2D' || type === 'cc.Animation' ||
2077 | type === 'cc.AudioSource' || (type?.startsWith('cc.') && !type.includes('@')))) {
2078 | // 在预制体中,组件引用也需要转换为 __id__ 形式
2079 | if (context?.componentUuidToIndex && context.componentUuidToIndex.has(value.uuid)) {
2080 | // 内部引用:转换为__id__格式
2081 | console.log(`Component reference ${type} UUID ${value.uuid} found in prefab context, converting to __id__`);
2082 | return {
2083 | "__id__": context.componentUuidToIndex.get(value.uuid)
2084 | };
2085 | }
2086 | // 外部引用:设置为null,因为外部组件不属于预制体结构
2087 | console.warn(`Component reference ${type} UUID ${value.uuid} not found in prefab context, setting to null (external reference)`);
2088 | return null;
2089 | }
2090 |
2091 | // 处理复杂类型,添加__type__标记
2092 | if (value && typeof value === 'object') {
2093 | if (type === 'cc.Color') {
2094 | return {
2095 | "__type__": "cc.Color",
2096 | "r": Math.min(255, Math.max(0, Number(value.r) || 0)),
2097 | "g": Math.min(255, Math.max(0, Number(value.g) || 0)),
2098 | "b": Math.min(255, Math.max(0, Number(value.b) || 0)),
2099 | "a": value.a !== undefined ? Math.min(255, Math.max(0, Number(value.a))) : 255
2100 | };
2101 | } else if (type === 'cc.Vec3') {
2102 | return {
2103 | "__type__": "cc.Vec3",
2104 | "x": Number(value.x) || 0,
2105 | "y": Number(value.y) || 0,
2106 | "z": Number(value.z) || 0
2107 | };
2108 | } else if (type === 'cc.Vec2') {
2109 | return {
2110 | "__type__": "cc.Vec2",
2111 | "x": Number(value.x) || 0,
2112 | "y": Number(value.y) || 0
2113 | };
2114 | } else if (type === 'cc.Size') {
2115 | return {
2116 | "__type__": "cc.Size",
2117 | "width": Number(value.width) || 0,
2118 | "height": Number(value.height) || 0
2119 | };
2120 | } else if (type === 'cc.Quat') {
2121 | return {
2122 | "__type__": "cc.Quat",
2123 | "x": Number(value.x) || 0,
2124 | "y": Number(value.y) || 0,
2125 | "z": Number(value.z) || 0,
2126 | "w": value.w !== undefined ? Number(value.w) : 1
2127 | };
2128 | }
2129 | }
2130 |
2131 | // 处理数组类型
2132 | if (Array.isArray(value)) {
2133 | // 节点数组
2134 | if (propData.elementTypeData?.type === 'cc.Node') {
2135 | return value.map(item => {
2136 | if (item?.uuid && context?.nodeUuidToIndex?.has(item.uuid)) {
2137 | return { "__id__": context.nodeUuidToIndex.get(item.uuid) };
2138 | }
2139 | return null;
2140 | }).filter(item => item !== null);
2141 | }
2142 |
2143 | // 资源数组
2144 | if (propData.elementTypeData?.type && propData.elementTypeData.type.startsWith('cc.')) {
2145 | return value.map(item => {
2146 | if (item?.uuid) {
2147 | return {
2148 | "__uuid__": this.uuidToCompressedId(item.uuid),
2149 | "__expectedType__": propData.elementTypeData.type
2150 | };
2151 | }
2152 | return null;
2153 | }).filter(item => item !== null);
2154 | }
2155 |
2156 | // 基础类型数组
2157 | return value.map(item => item?.value !== undefined ? item.value : item);
2158 | }
2159 |
2160 | // 其他复杂对象类型,保持原样但确保有__type__标记
2161 | if (value && typeof value === 'object' && type && type.startsWith('cc.')) {
2162 | return {
2163 | "__type__": type,
2164 | ...value
2165 | };
2166 | }
2167 |
2168 | return value;
2169 | }
2170 |
2171 | /**
2172 | * 创建符合引擎标准的节点对象
2173 | */
2174 | private createEngineStandardNode(nodeData: any, parentNodeIndex: number | null, nodeName?: string): any {
2175 | // 调试:打印原始节点数据(已注释)
2176 | // console.log('原始节点数据:', JSON.stringify(nodeData, null, 2));
2177 |
2178 | // 提取节点的基本属性
2179 | const getValue = (prop: any) => {
2180 | if (prop?.value !== undefined) return prop.value;
2181 | if (prop !== undefined) return prop;
2182 | return null;
2183 | };
2184 |
2185 | const position = getValue(nodeData.position) || getValue(nodeData.value?.position) || { x: 0, y: 0, z: 0 };
2186 | const rotation = getValue(nodeData.rotation) || getValue(nodeData.value?.rotation) || { x: 0, y: 0, z: 0, w: 1 };
2187 | const scale = getValue(nodeData.scale) || getValue(nodeData.value?.scale) || { x: 1, y: 1, z: 1 };
2188 | const active = getValue(nodeData.active) ?? getValue(nodeData.value?.active) ?? true;
2189 | const name = nodeName || getValue(nodeData.name) || getValue(nodeData.value?.name) || 'Node';
2190 | const layer = getValue(nodeData.layer) || getValue(nodeData.value?.layer) || 1073741824;
2191 |
2192 | // 调试输出
2193 | console.log(`创建节点: ${name}, parentNodeIndex: ${parentNodeIndex}`);
2194 |
2195 | const parentRef = parentNodeIndex !== null ? { "__id__": parentNodeIndex } : null;
2196 | console.log(`节点 ${name} 的父节点引用:`, parentRef);
2197 |
2198 | return {
2199 | "__type__": "cc.Node",
2200 | "_name": name,
2201 | "_objFlags": 0,
2202 | "__editorExtras__": {},
2203 | "_parent": parentRef,
2204 | "_children": [], // 子节点引用将在递归过程中动态添加
2205 | "_active": active,
2206 | "_components": [], // 组件引用将在处理组件时动态添加
2207 | "_prefab": { "__id__": 0 }, // 临时值,后续会被正确设置
2208 | "_lpos": {
2209 | "__type__": "cc.Vec3",
2210 | "x": position.x,
2211 | "y": position.y,
2212 | "z": position.z
2213 | },
2214 | "_lrot": {
2215 | "__type__": "cc.Quat",
2216 | "x": rotation.x,
2217 | "y": rotation.y,
2218 | "z": rotation.z,
2219 | "w": rotation.w
2220 | },
2221 | "_lscale": {
2222 | "__type__": "cc.Vec3",
2223 | "x": scale.x,
2224 | "y": scale.y,
2225 | "z": scale.z
2226 | },
2227 | "_mobility": 0,
2228 | "_layer": layer,
2229 | "_euler": {
2230 | "__type__": "cc.Vec3",
2231 | "x": 0,
2232 | "y": 0,
2233 | "z": 0
2234 | },
2235 | "_id": ""
2236 | };
2237 | }
2238 |
2239 | /**
2240 | * 从节点数据中提取UUID
2241 | */
2242 | private extractNodeUuid(nodeData: any): string | null {
2243 | if (!nodeData) return null;
2244 |
2245 | // 尝试多种方式获取UUID
2246 | const sources = [
2247 | nodeData.uuid,
2248 | nodeData.value?.uuid,
2249 | nodeData.__uuid__,
2250 | nodeData.value?.__uuid__,
2251 | nodeData.id,
2252 | nodeData.value?.id
2253 | ];
2254 |
2255 | for (const source of sources) {
2256 | if (typeof source === 'string' && source.length > 0) {
2257 | return source;
2258 | }
2259 | }
2260 |
2261 | return null;
2262 | }
2263 |
2264 | /**
2265 | * 创建最小化的节点对象,不包含任何组件以避免依赖问题
2266 | */
2267 | private createMinimalNode(nodeData: any, nodeName?: string): any {
2268 | // 提取节点的基本属性
2269 | const getValue = (prop: any) => {
2270 | if (prop?.value !== undefined) return prop.value;
2271 | if (prop !== undefined) return prop;
2272 | return null;
2273 | };
2274 |
2275 | const position = getValue(nodeData.position) || getValue(nodeData.value?.position) || { x: 0, y: 0, z: 0 };
2276 | const rotation = getValue(nodeData.rotation) || getValue(nodeData.value?.rotation) || { x: 0, y: 0, z: 0, w: 1 };
2277 | const scale = getValue(nodeData.scale) || getValue(nodeData.value?.scale) || { x: 1, y: 1, z: 1 };
2278 | const active = getValue(nodeData.active) ?? getValue(nodeData.value?.active) ?? true;
2279 | const name = nodeName || getValue(nodeData.name) || getValue(nodeData.value?.name) || 'Node';
2280 | const layer = getValue(nodeData.layer) || getValue(nodeData.value?.layer) || 33554432;
2281 |
2282 | return {
2283 | "__type__": "cc.Node",
2284 | "_name": name,
2285 | "_objFlags": 0,
2286 | "_parent": null,
2287 | "_children": [],
2288 | "_active": active,
2289 | "_components": [], // 空的组件数组,避免组件依赖问题
2290 | "_prefab": {
2291 | "__id__": 2
2292 | },
2293 | "_lpos": {
2294 | "__type__": "cc.Vec3",
2295 | "x": position.x,
2296 | "y": position.y,
2297 | "z": position.z
2298 | },
2299 | "_lrot": {
2300 | "__type__": "cc.Quat",
2301 | "x": rotation.x,
2302 | "y": rotation.y,
2303 | "z": rotation.z,
2304 | "w": rotation.w
2305 | },
2306 | "_lscale": {
2307 | "__type__": "cc.Vec3",
2308 | "x": scale.x,
2309 | "y": scale.y,
2310 | "z": scale.z
2311 | },
2312 | "_layer": layer,
2313 | "_euler": {
2314 | "__type__": "cc.Vec3",
2315 | "x": 0,
2316 | "y": 0,
2317 | "z": 0
2318 | },
2319 | "_id": ""
2320 | };
2321 | }
2322 |
2323 | /**
2324 | * 创建标准的 meta 文件内容
2325 | */
2326 | private createStandardMetaContent(prefabName: string, prefabUuid: string): any {
2327 | return {
2328 | "ver": "2.0.3",
2329 | "importer": "prefab",
2330 | "imported": true,
2331 | "uuid": prefabUuid,
2332 | "files": [
2333 | ".json"
2334 | ],
2335 | "subMetas": {},
2336 | "userData": {
2337 | "syncNodeName": prefabName,
2338 | "hasIcon": false
2339 | }
2340 | };
2341 | }
2342 |
2343 | /**
2344 | * 尝试将原始节点转换为预制体实例
2345 | */
2346 | private async convertNodeToPrefabInstance(nodeUuid: string, prefabUuid: string, prefabPath: string): Promise<{ success: boolean; error?: string }> {
2347 | return new Promise((resolve) => {
2348 | // 这个功能需要深入的场景编辑器集成,暂时返回失败
2349 | // 在实际的引擎中,这涉及到复杂的预制体实例化和节点替换逻辑
2350 | console.log('节点转换为预制体实例的功能需要更深入的引擎集成');
2351 | resolve({
2352 | success: false,
2353 | error: '节点转换为预制体实例需要更深入的引擎集成支持'
2354 | });
2355 | });
2356 | }
2357 |
2358 | private async restorePrefabNode(nodeUuid: string, assetUuid: string): Promise<ToolResponse> {
2359 | return new Promise((resolve) => {
2360 | // 使用官方API restore-prefab 还原预制体节点
2361 | (Editor.Message.request as any)('scene', 'restore-prefab', nodeUuid, assetUuid).then(() => {
2362 | resolve({
2363 | success: true,
2364 | data: {
2365 | nodeUuid: nodeUuid,
2366 | assetUuid: assetUuid,
2367 | message: '预制体节点还原成功'
2368 | }
2369 | });
2370 | }).catch((error: any) => {
2371 | resolve({
2372 | success: false,
2373 | error: `预制体节点还原失败: ${error.message}`
2374 | });
2375 | });
2376 | });
2377 | }
2378 |
2379 | // 基于官方预制体格式的新实现方法
2380 | private async getNodeDataForPrefab(nodeUuid: string): Promise<{ success: boolean; data?: any; error?: string }> {
2381 | return new Promise((resolve) => {
2382 | Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeData: any) => {
2383 | if (!nodeData) {
2384 | resolve({ success: false, error: '节点不存在' });
2385 | return;
2386 | }
2387 | resolve({ success: true, data: nodeData });
2388 | }).catch((error: any) => {
2389 | resolve({ success: false, error: error.message });
2390 | });
2391 | });
2392 | }
2393 |
2394 | private async createStandardPrefabData(nodeData: any, prefabName: string, prefabUuid: string): Promise<any[]> {
2395 | // 基于官方Canvas.prefab格式创建预制体数据结构
2396 | const prefabData: any[] = [];
2397 | let currentId = 0;
2398 |
2399 | // 第一个元素:cc.Prefab 资源对象
2400 | const prefabAsset = {
2401 | "__type__": "cc.Prefab",
2402 | "_name": prefabName,
2403 | "_objFlags": 0,
2404 | "__editorExtras__": {},
2405 | "_native": "",
2406 | "data": {
2407 | "__id__": 1
2408 | },
2409 | "optimizationPolicy": 0,
2410 | "persistent": false
2411 | };
2412 | prefabData.push(prefabAsset);
2413 | currentId++;
2414 |
2415 | // 第二个元素:根节点
2416 | const rootNode = await this.createNodeObject(nodeData, null, prefabData, currentId);
2417 | prefabData.push(rootNode.node);
2418 | currentId = rootNode.nextId;
2419 |
2420 | // 添加根节点的 PrefabInfo - 修复asset引用使用UUID
2421 | const rootPrefabInfo = {
2422 | "__type__": "cc.PrefabInfo",
2423 | "root": {
2424 | "__id__": 1
2425 | },
2426 | "asset": {
2427 | "__uuid__": prefabUuid
2428 | },
2429 | "fileId": this.generateFileId(),
2430 | "instance": null,
2431 | "targetOverrides": [],
2432 | "nestedPrefabInstanceRoots": []
2433 | };
2434 | prefabData.push(rootPrefabInfo);
2435 |
2436 | return prefabData;
2437 | }
2438 |
2439 |
2440 | private async createNodeObject(nodeData: any, parentId: number | null, prefabData: any[], currentId: number): Promise<{ node: any; nextId: number }> {
2441 | const nodeId = currentId++;
2442 |
2443 | // 提取节点的基本属性 - 适配query-node-tree的数据格式
2444 | const getValue = (prop: any) => {
2445 | if (prop?.value !== undefined) return prop.value;
2446 | if (prop !== undefined) return prop;
2447 | return null;
2448 | };
2449 |
2450 | const position = getValue(nodeData.position) || getValue(nodeData.value?.position) || { x: 0, y: 0, z: 0 };
2451 | const rotation = getValue(nodeData.rotation) || getValue(nodeData.value?.rotation) || { x: 0, y: 0, z: 0, w: 1 };
2452 | const scale = getValue(nodeData.scale) || getValue(nodeData.value?.scale) || { x: 1, y: 1, z: 1 };
2453 | const active = getValue(nodeData.active) ?? getValue(nodeData.value?.active) ?? true;
2454 | const name = getValue(nodeData.name) || getValue(nodeData.value?.name) || 'Node';
2455 | const layer = getValue(nodeData.layer) || getValue(nodeData.value?.layer) || 33554432;
2456 |
2457 | const node: any = {
2458 | "__type__": "cc.Node",
2459 | "_name": name,
2460 | "_objFlags": 0,
2461 | "__editorExtras__": {},
2462 | "_parent": parentId !== null ? { "__id__": parentId } : null,
2463 | "_children": [],
2464 | "_active": active,
2465 | "_components": [],
2466 | "_prefab": parentId === null ? {
2467 | "__id__": currentId++
2468 | } : null,
2469 | "_lpos": {
2470 | "__type__": "cc.Vec3",
2471 | "x": position.x,
2472 | "y": position.y,
2473 | "z": position.z
2474 | },
2475 | "_lrot": {
2476 | "__type__": "cc.Quat",
2477 | "x": rotation.x,
2478 | "y": rotation.y,
2479 | "z": rotation.z,
2480 | "w": rotation.w
2481 | },
2482 | "_lscale": {
2483 | "__type__": "cc.Vec3",
2484 | "x": scale.x,
2485 | "y": scale.y,
2486 | "z": scale.z
2487 | },
2488 | "_mobility": 0,
2489 | "_layer": layer,
2490 | "_euler": {
2491 | "__type__": "cc.Vec3",
2492 | "x": 0,
2493 | "y": 0,
2494 | "z": 0
2495 | },
2496 | "_id": ""
2497 | };
2498 |
2499 | // 暂时跳过UITransform组件以避免_getDependComponent错误
2500 | // 后续通过Engine API动态添加
2501 | console.log(`节点 ${name} 暂时跳过UITransform组件,避免引擎依赖错误`);
2502 |
2503 | // 处理其他组件(暂时跳过,专注于修复UITransform问题)
2504 | const components = this.extractComponentsFromNode(nodeData);
2505 | if (components.length > 0) {
2506 | console.log(`节点 ${name} 包含 ${components.length} 个其他组件,暂时跳过以专注于UITransform修复`);
2507 | }
2508 |
2509 | // 处理子节点 - 使用query-node-tree获取的完整结构
2510 | const childrenToProcess = this.getChildrenToProcess(nodeData);
2511 | if (childrenToProcess.length > 0) {
2512 | console.log(`=== 处理子节点 ===`);
2513 | console.log(`节点 ${name} 包含 ${childrenToProcess.length} 个子节点`);
2514 |
2515 | for (let i = 0; i < childrenToProcess.length; i++) {
2516 | const childData = childrenToProcess[i];
2517 | const childName = childData.name || childData.value?.name || '未知';
2518 | console.log(`处理第${i + 1}个子节点: ${childName}`);
2519 |
2520 | try {
2521 | const childId = currentId;
2522 | node._children.push({ "__id__": childId });
2523 |
2524 | // 递归创建子节点
2525 | const childResult = await this.createNodeObject(childData, nodeId, prefabData, currentId);
2526 | prefabData.push(childResult.node);
2527 | currentId = childResult.nextId;
2528 |
2529 | // 子节点不需要PrefabInfo,只有根节点需要
2530 | // 子节点的_prefab应该设置为null
2531 | childResult.node._prefab = null;
2532 |
2533 | console.log(`✅ 成功添加子节点: ${childName}`);
2534 | } catch (error) {
2535 | console.error(`处理子节点 ${childName} 时出错:`, error);
2536 | }
2537 | }
2538 | }
2539 |
2540 | return { node, nextId: currentId };
2541 | }
2542 |
2543 | // 从节点数据中提取组件信息
2544 | private extractComponentsFromNode(nodeData: any): any[] {
2545 | const components: any[] = [];
2546 |
2547 | // 从不同位置尝试获取组件数据
2548 | const componentSources = [
2549 | nodeData.__comps__,
2550 | nodeData.components,
2551 | nodeData.value?.__comps__,
2552 | nodeData.value?.components
2553 | ];
2554 |
2555 | for (const source of componentSources) {
2556 | if (Array.isArray(source)) {
2557 | components.push(...source.filter(comp => comp && (comp.__type__ || comp.type)));
2558 | break; // 找到有效的组件数组就退出
2559 | }
2560 | }
2561 |
2562 | return components;
2563 | }
2564 |
2565 | // 创建标准的组件对象
2566 | private createStandardComponentObject(componentData: any, nodeId: number, prefabInfoId: number): any {
2567 | const componentType = componentData.__type__ || componentData.type;
2568 |
2569 | if (!componentType) {
2570 | console.warn('组件缺少类型信息:', componentData);
2571 | return null;
2572 | }
2573 |
2574 | // 基础组件结构 - 基于官方预制体格式
2575 | const component: any = {
2576 | "__type__": componentType,
2577 | "_name": "",
2578 | "_objFlags": 0,
2579 | "node": {
2580 | "__id__": nodeId
2581 | },
2582 | "_enabled": this.getComponentPropertyValue(componentData, 'enabled', true),
2583 | "__prefab": {
2584 | "__id__": prefabInfoId
2585 | }
2586 | };
2587 |
2588 | // 根据组件类型添加特定属性
2589 | this.addComponentSpecificProperties(component, componentData, componentType);
2590 |
2591 | // 添加_id属性
2592 | component._id = "";
2593 |
2594 | return component;
2595 | }
2596 |
2597 | // 添加组件特定的属性
2598 | private addComponentSpecificProperties(component: any, componentData: any, componentType: string): void {
2599 | switch (componentType) {
2600 | case 'cc.UITransform':
2601 | this.addUITransformProperties(component, componentData);
2602 | break;
2603 | case 'cc.Sprite':
2604 | this.addSpriteProperties(component, componentData);
2605 | break;
2606 | case 'cc.Label':
2607 | this.addLabelProperties(component, componentData);
2608 | break;
2609 | case 'cc.Button':
2610 | this.addButtonProperties(component, componentData);
2611 | break;
2612 | default:
2613 | // 对于未知类型的组件,复制所有安全的属性
2614 | this.addGenericProperties(component, componentData);
2615 | break;
2616 | }
2617 | }
2618 |
2619 | // UITransform组件属性
2620 | private addUITransformProperties(component: any, componentData: any): void {
2621 | component._contentSize = this.createSizeObject(
2622 | this.getComponentPropertyValue(componentData, 'contentSize', { width: 100, height: 100 })
2623 | );
2624 | component._anchorPoint = this.createVec2Object(
2625 | this.getComponentPropertyValue(componentData, 'anchorPoint', { x: 0.5, y: 0.5 })
2626 | );
2627 | }
2628 |
2629 | // Sprite组件属性
2630 | private addSpriteProperties(component: any, componentData: any): void {
2631 | component._visFlags = 0;
2632 | component._customMaterial = null;
2633 | component._srcBlendFactor = 2;
2634 | component._dstBlendFactor = 4;
2635 | component._color = this.createColorObject(
2636 | this.getComponentPropertyValue(componentData, 'color', { r: 255, g: 255, b: 255, a: 255 })
2637 | );
2638 | component._spriteFrame = this.getComponentPropertyValue(componentData, 'spriteFrame', null);
2639 | component._type = this.getComponentPropertyValue(componentData, 'type', 0);
2640 | component._fillType = 0;
2641 | component._sizeMode = this.getComponentPropertyValue(componentData, 'sizeMode', 1);
2642 | component._fillCenter = this.createVec2Object({ x: 0, y: 0 });
2643 | component._fillStart = 0;
2644 | component._fillRange = 0;
2645 | component._isTrimmedMode = true;
2646 | component._useGrayscale = false;
2647 | component._atlas = null;
2648 | }
2649 |
2650 | // Label组件属性
2651 | private addLabelProperties(component: any, componentData: any): void {
2652 | component._visFlags = 0;
2653 | component._customMaterial = null;
2654 | component._srcBlendFactor = 2;
2655 | component._dstBlendFactor = 4;
2656 | component._color = this.createColorObject(
2657 | this.getComponentPropertyValue(componentData, 'color', { r: 0, g: 0, b: 0, a: 255 })
2658 | );
2659 | component._string = this.getComponentPropertyValue(componentData, 'string', 'Label');
2660 | component._horizontalAlign = 1;
2661 | component._verticalAlign = 1;
2662 | component._actualFontSize = 20;
2663 | component._fontSize = this.getComponentPropertyValue(componentData, 'fontSize', 20);
2664 | component._fontFamily = 'Arial';
2665 | component._lineHeight = 40;
2666 | component._overflow = 1;
2667 | component._enableWrapText = false;
2668 | component._font = null;
2669 | component._isSystemFontUsed = true;
2670 | component._isItalic = false;
2671 | component._isBold = false;
2672 | component._isUnderline = false;
2673 | component._underlineHeight = 2;
2674 | component._cacheMode = 0;
2675 | }
2676 |
2677 | // Button组件属性
2678 | private addButtonProperties(component: any, componentData: any): void {
2679 | component.clickEvents = [];
2680 | component._interactable = true;
2681 | component._transition = 2;
2682 | component._normalColor = this.createColorObject({ r: 214, g: 214, b: 214, a: 255 });
2683 | component._hoverColor = this.createColorObject({ r: 211, g: 211, b: 211, a: 255 });
2684 | component._pressedColor = this.createColorObject({ r: 255, g: 255, b: 255, a: 255 });
2685 | component._disabledColor = this.createColorObject({ r: 124, g: 124, b: 124, a: 255 });
2686 | component._duration = 0.1;
2687 | component._zoomScale = 1.2;
2688 | }
2689 |
2690 | // 添加通用属性
2691 | private addGenericProperties(component: any, componentData: any): void {
2692 | // 只复制安全的、已知的属性
2693 | const safeProperties = ['enabled', 'color', 'string', 'fontSize', 'spriteFrame', 'type', 'sizeMode'];
2694 |
2695 | for (const prop of safeProperties) {
2696 | if (componentData.hasOwnProperty(prop)) {
2697 | const value = this.getComponentPropertyValue(componentData, prop);
2698 | if (value !== undefined) {
2699 | component[`_${prop}`] = value;
2700 | }
2701 | }
2702 | }
2703 | }
2704 |
2705 | // 创建Vec2对象
2706 | private createVec2Object(data: any): any {
2707 | return {
2708 | "__type__": "cc.Vec2",
2709 | "x": data?.x || 0,
2710 | "y": data?.y || 0
2711 | };
2712 | }
2713 |
2714 | // 创建Vec3对象
2715 | private createVec3Object(data: any): any {
2716 | return {
2717 | "__type__": "cc.Vec3",
2718 | "x": data?.x || 0,
2719 | "y": data?.y || 0,
2720 | "z": data?.z || 0
2721 | };
2722 | }
2723 |
2724 | // 创建Size对象
2725 | private createSizeObject(data: any): any {
2726 | return {
2727 | "__type__": "cc.Size",
2728 | "width": data?.width || 100,
2729 | "height": data?.height || 100
2730 | };
2731 | }
2732 |
2733 | // 创建Color对象
2734 | private createColorObject(data: any): any {
2735 | return {
2736 | "__type__": "cc.Color",
2737 | "r": data?.r ?? 255,
2738 | "g": data?.g ?? 255,
2739 | "b": data?.b ?? 255,
2740 | "a": data?.a ?? 255
2741 | };
2742 | }
2743 |
2744 | // 判断是否应该复制组件属性
2745 | private shouldCopyComponentProperty(key: string, value: any): boolean {
2746 | // 跳过内部属性和已处理的属性
2747 | if (key.startsWith('__') || key === '_enabled' || key === 'node' || key === 'enabled') {
2748 | return false;
2749 | }
2750 |
2751 | // 跳过函数和undefined值
2752 | if (typeof value === 'function' || value === undefined) {
2753 | return false;
2754 | }
2755 |
2756 | return true;
2757 | }
2758 |
2759 |
2760 | // 获取组件属性值 - 重命名以避免冲突
2761 | private getComponentPropertyValue(componentData: any, propertyName: string, defaultValue?: any): any {
2762 | // 尝试直接获取属性
2763 | if (componentData[propertyName] !== undefined) {
2764 | return this.extractValue(componentData[propertyName]);
2765 | }
2766 |
2767 | // 尝试从value属性中获取
2768 | if (componentData.value && componentData.value[propertyName] !== undefined) {
2769 | return this.extractValue(componentData.value[propertyName]);
2770 | }
2771 |
2772 | // 尝试带下划线前缀的属性名
2773 | const prefixedName = `_${propertyName}`;
2774 | if (componentData[prefixedName] !== undefined) {
2775 | return this.extractValue(componentData[prefixedName]);
2776 | }
2777 |
2778 | return defaultValue;
2779 | }
2780 |
2781 | // 提取属性值
2782 | private extractValue(data: any): any {
2783 | if (data === null || data === undefined) {
2784 | return data;
2785 | }
2786 |
2787 | // 如果有value属性,优先使用value
2788 | if (typeof data === 'object' && data.hasOwnProperty('value')) {
2789 | return data.value;
2790 | }
2791 |
2792 | // 如果是引用对象,保持原样
2793 | if (typeof data === 'object' && (data.__id__ !== undefined || data.__uuid__ !== undefined)) {
2794 | return data;
2795 | }
2796 |
2797 | return data;
2798 | }
2799 |
2800 | private createStandardMetaData(prefabName: string, prefabUuid: string): any {
2801 | return {
2802 | "ver": "1.1.50",
2803 | "importer": "prefab",
2804 | "imported": true,
2805 | "uuid": prefabUuid,
2806 | "files": [
2807 | ".json"
2808 | ],
2809 | "subMetas": {},
2810 | "userData": {
2811 | "syncNodeName": prefabName
2812 | }
2813 | };
2814 | }
2815 |
2816 | private async savePrefabWithMeta(prefabPath: string, prefabData: any[], metaData: any): Promise<{ success: boolean; error?: string }> {
2817 | try {
2818 | const prefabContent = JSON.stringify(prefabData, null, 2);
2819 | const metaContent = JSON.stringify(metaData, null, 2);
2820 |
2821 | // 确保路径以.prefab结尾
2822 | const finalPrefabPath = prefabPath.endsWith('.prefab') ? prefabPath : `${prefabPath}.prefab`;
2823 | const metaPath = `${finalPrefabPath}.meta`;
2824 |
2825 | // 使用asset-db API创建预制体文件
2826 | await new Promise((resolve, reject) => {
2827 | Editor.Message.request('asset-db', 'create-asset', finalPrefabPath, prefabContent).then(() => {
2828 | resolve(true);
2829 | }).catch((error: any) => {
2830 | reject(error);
2831 | });
2832 | });
2833 |
2834 | // 创建meta文件
2835 | await new Promise((resolve, reject) => {
2836 | Editor.Message.request('asset-db', 'create-asset', metaPath, metaContent).then(() => {
2837 | resolve(true);
2838 | }).catch((error: any) => {
2839 | reject(error);
2840 | });
2841 | });
2842 |
2843 | console.log(`=== 预制体保存完成 ===`);
2844 | console.log(`预制体文件已保存: ${finalPrefabPath}`);
2845 | console.log(`Meta文件已保存: ${metaPath}`);
2846 | console.log(`预制体数组总长度: ${prefabData.length}`);
2847 | console.log(`预制体根节点索引: ${prefabData.length - 1}`);
2848 |
2849 | return { success: true };
2850 | } catch (error: any) {
2851 | console.error('保存预制体文件时出错:', error);
2852 | return { success: false, error: error.message };
2853 | }
2854 | }
2855 |
2856 | }
```