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