cocos2d-cli 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,518 +1,145 @@
1
1
  /**
2
2
  * create-scene 命令 - 从 JSON 结构创建场景文件
3
- *
4
- * JSON 格式示例:
5
- * {
6
- * "name": "Panel",
7
- * "width": 400,
8
- * "height": 300,
9
- * "color": "#ffffff",
10
- * "components": [
11
- * { "type": "sprite", "sizeMode": 1 },
12
- * { "type": "widget", "top": 0, "bottom": 0 }
13
- * ],
14
- * "children": [...]
15
- * }
16
- *
17
- * 节点属性:name, width, height, x, y, color, opacity, anchorX, anchorY, rotation, scaleX, scaleY, active
18
- * 组件属性:type + 各组件特有属性
19
- *
20
- * 场景会自动包含 Canvas 和 Main Camera 节点
21
3
  */
22
4
 
23
5
  const fs = require('fs');
24
6
  const path = require('path');
25
- const { Components, generateId } = require('../lib/components');
26
-
27
- // 支持的组件类型
28
- const COMPONENT_TYPES = {
29
- 'sprite': 'sprite',
30
- 'label': 'label',
31
- 'button': 'button',
32
- 'layout': 'layout',
33
- 'widget': 'widget',
34
- 'camera': 'camera',
35
- 'canvas': 'canvas',
36
- 'particle': 'particleSystem',
37
- 'particlesystem': 'particleSystem'
38
- };
7
+ const { outputError, outputSuccess } = require('../lib/utils');
8
+ const { createNodeData } = require('../lib/node-utils');
9
+ const { parseComponent, createComponent, applyComponentProps } = require('../lib/components');
10
+ const { createScene } = require('../lib/templates');
39
11
 
40
12
  /**
41
- * 解析颜色字符串 #RRGGBB 或 #RRGGBBAA
13
+ * JSON 定义创建场景数据
42
14
  */
43
- function parseColor(colorStr) {
44
- if (!colorStr || typeof colorStr !== 'string') return null;
15
+ function createSceneData(nodeDefs, sceneName) {
16
+ const data = createScene(sceneName);
45
17
 
46
- let hex = colorStr.replace('#', '');
47
- if (hex.length === 6) {
48
- return {
49
- r: parseInt(hex.substring(0, 2), 16),
50
- g: parseInt(hex.substring(2, 4), 16),
51
- b: parseInt(hex.substring(4, 6), 16),
52
- a: 255
53
- };
54
- } else if (hex.length === 8) {
55
- return {
56
- r: parseInt(hex.substring(0, 2), 16),
57
- g: parseInt(hex.substring(2, 4), 16),
58
- b: parseInt(hex.substring(4, 6), 16),
59
- a: parseInt(hex.substring(6, 8), 16)
60
- };
61
- }
62
- return null;
63
- }
64
-
65
- /**
66
- * 生成 UUID
67
- */
68
- function generateUUID() {
69
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
70
- const r = Math.random() * 16 | 0;
71
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
72
- return v.toString(16);
73
- });
74
- }
18
+ // Canvas 节点索引
19
+ const canvasIndex = 2;
75
20
 
76
- /**
77
- * 解析组件定义
78
- */
79
- function parseComponent(compDef) {
80
- if (typeof compDef === 'string') {
81
- const type = COMPONENT_TYPES[compDef.toLowerCase()];
82
- return type ? { type, props: {} } : null;
83
- }
21
+ // 支持数组或单个节点
22
+ const nodes = Array.isArray(nodeDefs) ? nodeDefs : [nodeDefs];
84
23
 
85
- if (typeof compDef === 'object' && compDef.type) {
86
- const type = COMPONENT_TYPES[compDef.type.toLowerCase()];
87
- if (!type) return null;
88
-
89
- const props = { ...compDef };
90
- delete props.type;
91
- return { type, props };
24
+ // 添加用户节点到 Canvas
25
+ for (const nodeDef of nodes) {
26
+ addUserNode(data, nodeDef, canvasIndex);
92
27
  }
93
-
94
- return null;
28
+
29
+ return data;
95
30
  }
96
31
 
97
32
  /**
98
- * 应用组件属性
99
- * @param comp 组件对象
100
- * @param props 属性对象
101
- * @param node 节点对象(可选,用于 label 的 color 设置到节点)
33
+ * 添加用户节点
102
34
  */
103
- function applyComponentProps(comp, props, node) {
104
- if (!props || !comp) return;
35
+ function addUserNode(data, def, parentIndex) {
36
+ const nodeIndex = data.length;
37
+ const node = createNodeData(def.name || 'Node', parentIndex, def);
105
38
 
106
- const type = comp.__type__;
39
+ data.push(node);
107
40
 
108
- // Label 的 color 属性应该设置到节点上
109
- if (type === 'cc.Label' && props.color && node) {
110
- const parsed = parseColor(props.color);
111
- if (parsed) {
112
- node._color = { "__type__": "cc.Color", ...parsed };
113
- }
114
- delete props.color; // 不再在组件中处理
115
- }
116
-
117
- // Widget 特殊处理:根据设置的方向计算 alignFlags
118
- if (type === 'cc.Widget') {
119
- const ALIGN = {
120
- top: 1,
121
- verticalCenter: 2,
122
- bottom: 4,
123
- left: 8,
124
- horizontalCenter: 16,
125
- right: 32
126
- };
127
- let alignFlags = 0;
128
-
129
- for (const dir of ['top', 'bottom', 'left', 'right', 'horizontalCenter', 'verticalCenter']) {
130
- if (props[dir] !== undefined && props[dir] !== null) {
131
- alignFlags |= ALIGN[dir];
132
- }
133
- }
134
-
135
- if (alignFlags > 0) {
136
- comp._alignFlags = alignFlags;
137
- }
138
- }
139
-
140
- // 通用属性映射
141
- const propMap = {
142
- 'sizeMode': '_sizeMode',
143
- 'fillType': '_fillType',
144
- 'fillCenter': '_fillCenter',
145
- 'fillStart': '_fillStart',
146
- 'fillRange': '_fillRange',
147
- 'trim': '_isTrimmedMode',
148
- 'string': '_string',
149
- 'fontSize': '_fontSize',
150
- 'lineHeight': '_lineHeight',
151
- 'horizontalAlign': '_N$horizontalAlign',
152
- 'verticalAlign': '_N$verticalAlign',
153
- 'overflow': '_N$overflow',
154
- 'fontFamily': '_N$fontFamily',
155
- 'wrap': '_enableWrapText',
156
- 'alignFlags': '_alignFlags',
157
- 'left': '_left',
158
- 'right': '_right',
159
- 'top': '_top',
160
- 'bottom': '_bottom',
161
- 'horizontalCenter': '_horizontalCenter',
162
- 'verticalCenter': '_verticalCenter',
163
- 'isAbsLeft': '_isAbsLeft',
164
- 'isAbsRight': '_isAbsRight',
165
- 'isAbsTop': '_isAbsTop',
166
- 'isAbsBottom': '_isAbsBottom',
167
- 'interactable': '_N$interactable',
168
- 'transition': '_N$transition',
169
- 'zoomScale': 'zoomScale',
170
- 'duration': 'duration',
171
- 'layoutType': '_N$layoutType',
172
- 'cellSize': '_N$cellSize',
173
- 'startAxis': '_N$startAxis',
174
- 'paddingLeft': '_N$paddingLeft',
175
- 'paddingRight': '_N$paddingRight',
176
- 'paddingTop': '_N$paddingTop',
177
- 'paddingBottom': '_N$paddingBottom',
178
- 'spacingX': '_N$spacingX',
179
- 'spacingY': '_N$spacingY',
180
- 'resize': '_resize',
181
- 'designResolution': '_designResolution',
182
- 'fitWidth': '_fitWidth',
183
- 'fitHeight': '_fitHeight',
184
- 'orthoSize': '_orthoSize',
185
- 'backgroundColor': '_backgroundColor',
186
- 'cullingMask': '_cullingMask',
187
- 'zoomRatio': '_zoomRatio'
188
- };
189
-
190
- if (type === 'cc.Label' && props.string !== undefined) {
191
- comp._N$string = props.string;
192
- }
193
-
194
- for (const [key, value] of Object.entries(props)) {
195
- const compKey = propMap[key];
196
- if (compKey) {
197
- if (key === 'fillCenter' && Array.isArray(value)) {
198
- comp[compKey] = { "__type__": "cc.Vec2", "x": value[0], "y": value[1] };
199
- } else if (key === 'cellSize' && Array.isArray(value)) {
200
- comp[compKey] = { "__type__": "cc.Size", "width": value[0], "height": value[1] };
201
- } else if (key === 'designResolution' && Array.isArray(value)) {
202
- comp[compKey] = { "__type__": "cc.Size", "width": value[0], "height": value[1] };
203
- } else if ((key === 'backgroundColor' || key === 'color') && typeof value === 'string') {
204
- const color = parseColor(value);
205
- if (color) {
206
- comp[compKey] = { "__type__": "cc.Color", ...color };
41
+ // 添加组件
42
+ if (def.components) {
43
+ for (const compDef of def.components) {
44
+ const parsed = parseComponent(compDef);
45
+ if (parsed) {
46
+ const comp = createComponent(parsed.type, nodeIndex);
47
+ if (comp) {
48
+ applyComponentProps(comp, parsed.props, node);
49
+ data.push(comp);
50
+ node._components.push({ "__id__": data.length - 1 });
207
51
  }
208
- } else {
209
- comp[compKey] = value;
210
52
  }
211
53
  }
212
54
  }
213
- }
214
-
215
- /**
216
- * 创建场景数据结构
217
- */
218
- function createSceneData(nodeDefs, sceneName) {
219
- const data = [];
220
-
221
- // 场景 UUID
222
- const sceneUUID = generateUUID();
223
- const canvasUUID = generateUUID();
224
- const cameraUUID = generateUUID();
225
-
226
- // 索引 0: cc.SceneAsset
227
- data.push({
228
- "__type__": "cc.SceneAsset",
229
- "_name": sceneName || "NewScene",
230
- "_objFlags": 0,
231
- "_native": "",
232
- "scene": { "__id__": 1 }
233
- });
234
-
235
- // 索引 1: cc.Scene
236
- data.push({
237
- "__type__": "cc.Scene",
238
- "_name": sceneName || "NewScene",
239
- "_objFlags": 0,
240
- "_parent": null,
241
- "_children": [{ "__id__": 2 }],
242
- "_active": true,
243
- "_components": [],
244
- "_prefab": null,
245
- "_opacity": 255,
246
- "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
247
- "_contentSize": { "__type__": "cc.Size", "width": 0, "height": 0 },
248
- "_anchorPoint": { "__type__": "cc.Vec2", "x": 0, "y": 0 },
249
- "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] },
250
- "_is3DNode": true,
251
- "_groupIndex": 0,
252
- "groupIndex": 0,
253
- "autoReleaseAssets": false,
254
- "_id": sceneUUID
255
- });
256
55
 
257
- // 索引 2: Canvas 节点
258
- data.push({
259
- "__type__": "cc.Node",
260
- "_name": "Canvas",
261
- "_objFlags": 0,
262
- "_parent": { "__id__": 1 },
263
- "_children": [{ "__id__": 3 }], // Main Camera
264
- "_active": true,
265
- "_components": [{ "__id__": 5 }, { "__id__": 6 }], // Canvas, Widget
266
- "_prefab": null,
267
- "_opacity": 255,
268
- "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
269
- "_contentSize": { "__type__": "cc.Size", "width": 960, "height": 640 },
270
- "_anchorPoint": { "__type__": "cc.Vec2", "x": 0.5, "y": 0.5 },
271
- "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [480, 320, 0, 0, 0, 0, 1, 1, 1, 1] },
272
- "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": 0 },
273
- "_skewX": 0,
274
- "_skewY": 0,
275
- "_is3DNode": false,
276
- "_groupIndex": 0,
277
- "groupIndex": 0,
278
- "_id": canvasUUID
279
- });
56
+ // 更新父节点的 _children
57
+ data[parentIndex]._children.push({ "__id__": nodeIndex });
280
58
 
281
- // 索引 3: Main Camera 节点
282
- data.push({
283
- "__type__": "cc.Node",
284
- "_name": "Main Camera",
285
- "_objFlags": 0,
286
- "_parent": { "__id__": 2 },
287
- "_children": [],
288
- "_active": true,
289
- "_components": [{ "__id__": 4 }], // Camera
290
- "_prefab": null,
291
- "_opacity": 255,
292
- "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
293
- "_contentSize": { "__type__": "cc.Size", "width": 0, "height": 0 },
294
- "_anchorPoint": { "__type__": "cc.Vec2", "x": 0.5, "y": 0.5 },
295
- "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] },
296
- "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": 0 },
297
- "_skewX": 0,
298
- "_skewY": 0,
299
- "_is3DNode": false,
300
- "_groupIndex": 0,
301
- "groupIndex": 0,
302
- "_id": cameraUUID
303
- });
304
-
305
- // 索引 4: Camera 组件
306
- data.push({
307
- "__type__": "cc.Camera",
308
- "_name": "",
309
- "_objFlags": 0,
310
- "node": { "__id__": 3 },
311
- "_enabled": true,
312
- "_cullingMask": 4294967295,
313
- "_clearFlags": 7,
314
- "_backgroundColor": { "__type__": "cc.Color", "r": 0, "g": 0, "b": 0, "a": 255 },
315
- "_depth": -1,
316
- "_zoomRatio": 1,
317
- "_targetTexture": null,
318
- "_fov": 60,
319
- "_orthoSize": 10,
320
- "_nearClip": 1,
321
- "_farClip": 4096,
322
- "_ortho": true,
323
- "_rect": { "__type__": "cc.Rect", "x": 0, "y": 0, "width": 1, "height": 1 },
324
- "_renderStages": 1,
325
- "_alignWithScreen": true,
326
- "_id": generateId()
327
- });
328
-
329
- // 索引 5: Canvas 组件
330
- data.push({
331
- "__type__": "cc.Canvas",
332
- "_name": "",
333
- "_objFlags": 0,
334
- "node": { "__id__": 2 },
335
- "_enabled": true,
336
- "_designResolution": { "__type__": "cc.Size", "width": 960, "height": 640 },
337
- "_fitWidth": false,
338
- "_fitHeight": true,
339
- "_id": generateId()
340
- });
341
-
342
- // 索引 6: Widget 组件 (Canvas)
343
- data.push({
344
- "__type__": "cc.Widget",
345
- "_name": "",
346
- "_objFlags": 0,
347
- "node": { "__id__": 2 },
348
- "_enabled": true,
349
- "alignMode": 1,
350
- "_target": null,
351
- "_alignFlags": 45,
352
- "_left": 0,
353
- "_right": 0,
354
- "_top": 0,
355
- "_bottom": 0,
356
- "_verticalCenter": 0,
357
- "_horizontalCenter": 0,
358
- "_isAbsLeft": true,
359
- "_isAbsRight": true,
360
- "_isAbsTop": true,
361
- "_isAbsBottom": true,
362
- "_isAbsHorizontalCenter": true,
363
- "_isAbsVerticalCenter": true,
364
- "_originalWidth": 0,
365
- "_originalHeight": 0,
366
- "_id": generateId()
367
- });
368
-
369
- // Canvas 节点索引
370
- const canvasIndex = 2;
371
-
372
- // 递归添加用户节点
373
- function addNode(def, parentIndex) {
374
- const nodeIndex = data.length;
375
- const uuid = generateUUID();
376
-
377
- // 解析组件
378
- const compList = (def.components || [])
379
- .map(parseComponent)
380
- .filter(Boolean);
381
-
382
- // 解析颜色
383
- let color = { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 };
384
- if (def.color) {
385
- const parsed = parseColor(def.color);
386
- if (parsed) {
387
- color = { "__type__": "cc.Color", ...parsed };
388
- }
389
- }
390
-
391
- // 解析尺寸和位置
392
- const width = def.width || 0;
393
- const height = def.height || 0;
394
- const anchorX = def.anchorX !== undefined ? def.anchorX : 0.5;
395
- const anchorY = def.anchorY !== undefined ? def.anchorY : 0.5;
396
- const rotation = def.rotation || 0;
397
- const scaleX = def.scaleX !== undefined ? def.scaleX : 1;
398
- const scaleY = def.scaleY !== undefined ? def.scaleY : 1;
399
-
400
- // 创建节点
401
- const node = {
402
- "__type__": "cc.Node",
403
- "_name": def.name || 'Node',
404
- "_objFlags": 0,
405
- "_parent": { "__id__": parentIndex },
406
- "_children": [],
407
- "_active": def.active !== false,
408
- "_components": [],
409
- "_prefab": null,
410
- "_opacity": def.opacity !== undefined ? def.opacity : 255,
411
- "_color": color,
412
- "_contentSize": { "__type__": "cc.Size", width, height },
413
- "_anchorPoint": { "__type__": "cc.Vec2", "x": anchorX, "y": anchorY },
414
- "_trs": {
415
- "__type__": "TypedArray",
416
- "ctor": "Float64Array",
417
- "array": [
418
- def.x || 0,
419
- def.y || 0,
420
- 0, 0, 0,
421
- rotation * Math.PI / 180,
422
- 1, scaleX, scaleY, 1
423
- ]
424
- },
425
- "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": rotation },
426
- "_skewX": 0,
427
- "_skewY": 0,
428
- "_is3DNode": false,
429
- "_groupIndex": 0,
430
- "groupIndex": 0,
431
- "_id": uuid
432
- };
433
-
434
- data.push(node);
435
-
436
- // 添加组件
437
- for (const { type, props } of compList) {
438
- if (Components[type]) {
439
- const comp = Components[type](nodeIndex);
440
- applyComponentProps(comp, props, node);
441
- const compIndex = data.length;
442
- data.push(comp);
443
- node._components.push({ "__id__": compIndex });
444
- }
445
- }
446
-
447
- // 更新父节点的 _children
448
- data[parentIndex]._children.push({ "__id__": nodeIndex });
449
-
450
- // 递归处理子节点
451
- if (def.children && def.children.length > 0) {
452
- for (const child of def.children) {
453
- addNode(child, nodeIndex);
454
- }
59
+ // 递归处理子节点
60
+ if (def.children && def.children.length > 0) {
61
+ for (const childDef of def.children) {
62
+ addUserNode(data, childDef, nodeIndex);
455
63
  }
456
-
457
- return nodeIndex;
458
- }
459
-
460
- // 支持数组或单个节点
461
- const nodes = Array.isArray(nodeDefs) ? nodeDefs : [nodeDefs];
462
-
463
- // 添加用户节点到 Canvas
464
- for (const nodeDef of nodes) {
465
- addNode(nodeDef, canvasIndex);
466
64
  }
467
-
468
- return data;
469
65
  }
470
66
 
471
67
  function run(args) {
472
68
  if (args.length < 1) {
473
- console.log(JSON.stringify({
474
- error: '用法: cocos2d-cli create-scene <输出路径.fire>',
475
- hint: ' stdin 读取 JSON 结构生成场景',
476
- example: 'type scene.json | cocos2d-cli create-scene assets/scene.fire'
477
- }));
69
+ outputError({
70
+ message: '用法: cocos2d-cli create-scene [JSON文件路径] <输出路径.fire>',
71
+ hint: '不传 JSON 则创建默认场景'
72
+ });
478
73
  return;
479
74
  }
480
75
 
481
- const outputPath = args[0];
76
+ let jsonPath = null;
77
+ let outputPath;
78
+
79
+ if (args.length === 1) {
80
+ outputPath = args[0];
81
+ } else {
82
+ jsonPath = args[0];
83
+ outputPath = args[1];
84
+ }
85
+
482
86
  const sceneName = path.basename(outputPath, '.fire');
483
87
 
484
- // stdin 读取 JSON
485
- let input = '';
486
-
487
- if (!process.stdin.isTTY) {
488
- input = fs.readFileSync(0, 'utf8');
88
+ // 没有传 JSON,创建默认场景
89
+ if (!jsonPath) {
90
+ try {
91
+ if (fs.existsSync(outputPath)) {
92
+ outputError(`文件已存在: ${outputPath}`);
93
+ return;
94
+ }
95
+
96
+ const dir = path.dirname(outputPath);
97
+ if (!fs.existsSync(dir)) {
98
+ fs.mkdirSync(dir, { recursive: true });
99
+ }
100
+
101
+ const data = createScene(sceneName);
102
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
103
+
104
+ let nodeCount = 0, compCount = 0;
105
+ for (const item of data) {
106
+ if (item.__type__ === 'cc.Node') nodeCount++;
107
+ else if (item.__type__?.startsWith('cc.') && !['cc.Scene', 'cc.SceneAsset'].includes(item.__type__)) {
108
+ compCount++;
109
+ }
110
+ }
111
+
112
+ outputSuccess({
113
+ path: outputPath,
114
+ nodes: nodeCount,
115
+ components: compCount
116
+ });
117
+ return;
118
+ } catch (err) {
119
+ outputError(err.message);
120
+ return;
121
+ }
489
122
  }
490
123
 
491
- if (!input.trim()) {
492
- console.log(JSON.stringify({
493
- error: '请通过 stdin 提供 JSON 结构'
494
- }));
124
+ if (!fs.existsSync(jsonPath)) {
125
+ outputError(`JSON 文件不存在: ${jsonPath}`);
495
126
  return;
496
127
  }
497
128
 
498
129
  try {
499
- // 移除 BOM 并解析 JSON
500
- input = input.replace(/^\uFEFF/, '').trim();
501
- const nodeDef = JSON.parse(input);
130
+ const input = fs.readFileSync(jsonPath, 'utf8');
131
+ const cleanInput = input.replace(/^\uFEFF/, '').trim();
132
+ const nodeDef = JSON.parse(cleanInput);
502
133
 
503
- // 生成场景数据
504
134
  const sceneData = createSceneData(nodeDef, sceneName);
505
135
 
506
- // 确保输出目录存在
507
136
  const outputDir = path.dirname(outputPath);
508
137
  if (!fs.existsSync(outputDir)) {
509
138
  fs.mkdirSync(outputDir, { recursive: true });
510
139
  }
511
140
 
512
- // 保存文件
513
141
  fs.writeFileSync(outputPath, JSON.stringify(sceneData, null, 2), 'utf8');
514
142
 
515
- // 统计信息
516
143
  let nodeCount = 0, compCount = 0;
517
144
  for (const item of sceneData) {
518
145
  if (item.__type__ === 'cc.Node') nodeCount++;
@@ -521,15 +148,14 @@ function run(args) {
521
148
  }
522
149
  }
523
150
 
524
- console.log(JSON.stringify({
525
- success: true,
151
+ outputSuccess({
526
152
  path: outputPath,
527
153
  nodes: nodeCount,
528
154
  components: compCount
529
- }));
155
+ });
530
156
 
531
157
  } catch (err) {
532
- console.log(JSON.stringify({ error: err.message }));
158
+ outputError(err.message);
533
159
  }
534
160
  }
535
161