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,411 +1,140 @@
1
1
  /**
2
2
  * prefab-create 命令 - 从 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
3
  */
20
4
 
21
5
  const fs = require('fs');
22
6
  const path = require('path');
23
- const { Components, generateId } = require('../lib/components');
24
- const { createPrefab, generateFileId } = require('../lib/fire-utils');
25
-
26
- // 支持的组件类型
27
- const COMPONENT_TYPES = {
28
- 'sprite': 'sprite',
29
- 'label': 'label',
30
- 'button': 'button',
31
- 'layout': 'layout',
32
- 'widget': 'widget',
33
- 'camera': 'camera',
34
- 'canvas': 'canvas',
35
- 'particle': 'particleSystem',
36
- 'particlesystem': 'particleSystem'
37
- };
7
+ const { outputError, outputSuccess } = require('../lib/utils');
8
+ const { createNodeData } = require('../lib/node-utils');
9
+ const { parseComponent, createComponent, applyComponentProps } = require('../lib/components');
10
+ const { createPrefab, generateFileId } = require('../lib/templates');
38
11
 
39
12
  /**
40
- * 解析颜色字符串 #RRGGBB 或 #RRGGBBAA
13
+ * JSON 定义创建预制体数据
41
14
  */
42
- function parseColor(colorStr) {
43
- if (!colorStr || typeof colorStr !== 'string') return null;
15
+ function createPrefabData(nodeDef) {
16
+ const data = createPrefab(nodeDef.name || 'Node');
44
17
 
45
- let hex = colorStr.replace('#', '');
46
- if (hex.length === 6) {
47
- return {
48
- r: parseInt(hex.substring(0, 2), 16),
49
- g: parseInt(hex.substring(2, 4), 16),
50
- b: parseInt(hex.substring(4, 6), 16),
51
- a: 255
52
- };
53
- } else if (hex.length === 8) {
54
- return {
55
- r: parseInt(hex.substring(0, 2), 16),
56
- g: parseInt(hex.substring(2, 4), 16),
57
- b: parseInt(hex.substring(4, 6), 16),
58
- a: parseInt(hex.substring(6, 8), 16)
59
- };
18
+ // 更新根节点
19
+ const root = data[1];
20
+ applyNodeDefToNode(root, nodeDef, null);
21
+
22
+ // 递归添加子节点
23
+ if (nodeDef.children && nodeDef.children.length > 0) {
24
+ for (const childDef of nodeDef.children) {
25
+ addChildNode(data, childDef, 1);
26
+ }
60
27
  }
61
- return null;
62
- }
63
-
64
- /**
65
- * 生成 UUID
66
- */
67
- function generateUUID() {
68
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
69
- const r = Math.random() * 16 | 0;
70
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
71
- return v.toString(16);
72
- });
28
+
29
+ return data;
73
30
  }
74
31
 
75
32
  /**
76
- * 解析组件定义
77
- * 支持两种格式:
78
- * 1. 字符串: "sprite"
79
- * 2. 对象: { "type": "sprite", "sizeMode": 1 }
33
+ * 应用 JSON 定义到节点
80
34
  */
81
- function parseComponent(compDef) {
82
- if (typeof compDef === 'string') {
83
- const type = COMPONENT_TYPES[compDef.toLowerCase()];
84
- return type ? { type, props: {} } : null;
35
+ function applyNodeDefToNode(node, def, parentId) {
36
+ if (def.name) node._name = def.name;
37
+ if (def.active !== undefined) node._active = def.active;
38
+ if (def.opacity !== undefined) node._opacity = def.opacity;
39
+ if (def.width !== undefined) node._contentSize.width = def.width;
40
+ if (def.height !== undefined) node._contentSize.height = def.height;
41
+ if (def.x !== undefined) node._trs.array[0] = def.x;
42
+ if (def.y !== undefined) node._trs.array[1] = def.y;
43
+ if (def.rotation !== undefined) {
44
+ node._trs.array[5] = def.rotation * Math.PI / 180;
45
+ node._eulerAngles.z = def.rotation;
85
46
  }
86
-
87
- if (typeof compDef === 'object' && compDef.type) {
88
- const type = COMPONENT_TYPES[compDef.type.toLowerCase()];
89
- if (!type) return null;
90
-
91
- const props = { ...compDef };
92
- delete props.type;
93
- return { type, props };
47
+ if (def.scaleX !== undefined) node._trs.array[7] = def.scaleX;
48
+ if (def.scaleY !== undefined) node._trs.array[8] = def.scaleY;
49
+ if (def.anchorX !== undefined) node._anchorPoint.x = def.anchorX;
50
+ if (def.anchorY !== undefined) node._anchorPoint.y = def.anchorY;
51
+ if (def.color) {
52
+ const { parseColor } = require('../lib/utils');
53
+ const parsed = parseColor(def.color);
54
+ if (parsed) {
55
+ node._color = { "__type__": "cc.Color", ...parsed };
56
+ }
57
+ }
58
+ if (parentId !== null) {
59
+ node._parent = { "__id__": parentId };
94
60
  }
95
-
96
- return null;
97
61
  }
98
62
 
99
63
  /**
100
- * 应用组件属性
101
- * @param comp 组件对象
102
- * @param props 属性对象
103
- * @param node 节点对象(可选,用于 label 的 color 设置到节点)
64
+ * 添加子节点
104
65
  */
105
- function applyComponentProps(comp, props, node) {
106
- if (!props || !comp) return;
66
+ function addChildNode(data, def, parentIndex) {
67
+ const nodeIndex = data.length;
68
+ const node = createNodeData(def.name || 'Node', parentIndex, def);
107
69
 
108
- const type = comp.__type__;
70
+ // 设置 _prefab 为 null,后面会添加 PrefabInfo
71
+ node._prefab = null;
109
72
 
110
- // Label 的 color 属性应该设置到节点上
111
- if (type === 'cc.Label' && props.color && node) {
112
- const parsed = parseColor(props.color);
113
- if (parsed) {
114
- node._color = { "__type__": "cc.Color", ...parsed };
115
- }
116
- delete props.color; // 不再在组件中处理
117
- }
73
+ data.push(node);
118
74
 
119
- // Widget 特殊处理:根据设置的方向计算 alignFlags
120
- // 位掩码定义 (来自 Cocos Creator 源码)
121
- // TOP=1, MID(verticalCenter)=2, BOT=4, LEFT=8, CENTER(horizontalCenter)=16, RIGHT=32
122
- if (type === 'cc.Widget') {
123
- const ALIGN = {
124
- top: 1,
125
- verticalCenter: 2,
126
- bottom: 4,
127
- left: 8,
128
- horizontalCenter: 16,
129
- right: 32
130
- };
131
- let alignFlags = 0;
132
-
133
- for (const dir of ['top', 'bottom', 'left', 'right', 'horizontalCenter', 'verticalCenter']) {
134
- if (props[dir] !== undefined && props[dir] !== null) {
135
- alignFlags |= ALIGN[dir];
75
+ // 添加组件
76
+ if (def.components) {
77
+ for (const compDef of def.components) {
78
+ const parsed = parseComponent(compDef);
79
+ if (parsed) {
80
+ const comp = createComponent(parsed.type, nodeIndex);
81
+ if (comp) {
82
+ applyComponentProps(comp, parsed.props, node);
83
+ data.push(comp);
84
+ node._components.push({ "__id__": data.length - 1 });
85
+ }
136
86
  }
137
87
  }
138
-
139
- if (alignFlags > 0) {
140
- comp._alignFlags = alignFlags;
141
- }
142
88
  }
143
89
 
144
- // 通用属性映射
145
- const propMap = {
146
- // Sprite
147
- 'sizeMode': '_sizeMode',
148
- 'fillType': '_fillType',
149
- 'fillCenter': '_fillCenter',
150
- 'fillStart': '_fillStart',
151
- 'fillRange': '_fillRange',
152
- 'trim': '_isTrimmedMode',
153
-
154
- // Label
155
- 'string': '_string',
156
- 'fontSize': '_fontSize',
157
- 'lineHeight': '_lineHeight',
158
- 'horizontalAlign': '_N$horizontalAlign',
159
- 'verticalAlign': '_N$verticalAlign',
160
- 'overflow': '_N$overflow',
161
- 'fontFamily': '_N$fontFamily',
162
- 'wrap': '_enableWrapText',
163
-
164
- // Widget
165
- 'alignFlags': '_alignFlags',
166
- 'left': '_left',
167
- 'right': '_right',
168
- 'top': '_top',
169
- 'bottom': '_bottom',
170
- 'horizontalCenter': '_horizontalCenter',
171
- 'verticalCenter': '_verticalCenter',
172
- 'isAbsLeft': '_isAbsLeft',
173
- 'isAbsRight': '_isAbsRight',
174
- 'isAbsTop': '_isAbsTop',
175
- 'isAbsBottom': '_isAbsBottom',
176
-
177
- // Button
178
- 'interactable': '_N$interactable',
179
- 'transition': '_N$transition',
180
- 'zoomScale': 'zoomScale',
181
- 'duration': 'duration',
182
-
183
- // Layout
184
- 'layoutType': '_N$layoutType',
185
- 'cellSize': '_N$cellSize',
186
- 'startAxis': '_N$startAxis',
187
- 'paddingLeft': '_N$paddingLeft',
188
- 'paddingRight': '_N$paddingRight',
189
- 'paddingTop': '_N$paddingTop',
190
- 'paddingBottom': '_N$paddingBottom',
191
- 'spacingX': '_N$spacingX',
192
- 'spacingY': '_N$spacingY',
193
- 'resize': '_resize',
194
-
195
- // Canvas
196
- 'designResolution': '_designResolution',
197
- 'fitWidth': '_fitWidth',
198
- 'fitHeight': '_fitHeight',
199
-
200
- // Camera
201
- 'orthoSize': '_orthoSize',
202
- 'backgroundColor': '_backgroundColor',
203
- 'cullingMask': '_cullingMask',
204
- 'zoomRatio': '_zoomRatio'
90
+ // 添加 PrefabInfo
91
+ const prefabInfo = {
92
+ "__type__": "cc.PrefabInfo",
93
+ "root": { "__id__": 1 },
94
+ "asset": { "__id__": 0 },
95
+ "fileId": generateFileId(),
96
+ "sync": false
205
97
  };
98
+ data.push(prefabInfo);
99
+ node._prefab = { "__id__": data.length - 1 };
206
100
 
207
- // 特殊处理:label 的 string 属性需要同时设置 _N$string
208
- if (type === 'cc.Label' && props.string !== undefined) {
209
- comp._N$string = props.string;
210
- }
211
-
212
- // 应用属性
213
- for (const [key, value] of Object.entries(props)) {
214
- const compKey = propMap[key];
215
- if (compKey) {
216
- // 处理特殊类型
217
- if (key === 'fillCenter' && Array.isArray(value)) {
218
- comp[compKey] = { "__type__": "cc.Vec2", "x": value[0], "y": value[1] };
219
- } else if (key === 'cellSize' && Array.isArray(value)) {
220
- comp[compKey] = { "__type__": "cc.Size", "width": value[0], "height": value[1] };
221
- } else if (key === 'designResolution' && Array.isArray(value)) {
222
- comp[compKey] = { "__type__": "cc.Size", "width": value[0], "height": value[1] };
223
- } else if ((key === 'backgroundColor' || key === 'color') && typeof value === 'string') {
224
- const color = parseColor(value);
225
- if (color) {
226
- comp[compKey] = { "__type__": "cc.Color", ...color };
227
- }
228
- } else {
229
- comp[compKey] = value;
230
- }
231
- }
232
- }
233
- }
234
-
235
- /**
236
- * 从 JSON 定义创建预制体数据
237
- */
238
- function createPrefabData(nodeDef) {
239
- const data = [];
101
+ // 更新父节点
102
+ data[parentIndex]._children.push({ "__id__": nodeIndex });
240
103
 
241
- // 索引 0: cc.Prefab
242
- data.push({
243
- "__type__": "cc.Prefab",
244
- "_name": "",
245
- "_objFlags": 0,
246
- "_native": "",
247
- "data": { "__id__": 1 },
248
- "optimizationPolicy": 0,
249
- "asyncLoadAssets": false,
250
- "readonly": false
251
- });
252
-
253
- const prefabInfoList = [];
254
-
255
- function addNode(def, parentIndex) {
256
- const nodeIndex = data.length;
257
- const uuid = generateUUID();
258
- const fileId = generateFileId();
259
-
260
- // 解析组件
261
- const compList = (def.components || [])
262
- .map(parseComponent)
263
- .filter(Boolean);
264
-
265
- // 解析颜色
266
- let color = { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 };
267
- if (def.color) {
268
- const parsed = parseColor(def.color);
269
- if (parsed) {
270
- color = { "__type__": "cc.Color", ...parsed };
271
- }
272
- }
273
-
274
- // 解析尺寸
275
- const width = def.width || 0;
276
- const height = def.height || 0;
277
-
278
- // 解析锚点
279
- const anchorX = def.anchorX !== undefined ? def.anchorX : 0.5;
280
- const anchorY = def.anchorY !== undefined ? def.anchorY : 0.5;
281
-
282
- // 解析旋转和缩放
283
- const rotation = def.rotation || 0;
284
- const scaleX = def.scaleX !== undefined ? def.scaleX : 1;
285
- const scaleY = def.scaleY !== undefined ? def.scaleY : 1;
286
-
287
- // 创建节点
288
- const node = {
289
- "__type__": "cc.Node",
290
- "_name": def.name || 'Node',
291
- "_objFlags": 0,
292
- "_parent": parentIndex === null ? null : { "__id__": parentIndex },
293
- "_children": [],
294
- "_active": def.active !== false,
295
- "_components": [],
296
- "_prefab": null,
297
- "_opacity": def.opacity !== undefined ? def.opacity : 255,
298
- "_color": color,
299
- "_contentSize": {
300
- "__type__": "cc.Size",
301
- width,
302
- height
303
- },
304
- "_anchorPoint": { "__type__": "cc.Vec2", "x": anchorX, "y": anchorY },
305
- "_trs": {
306
- "__type__": "TypedArray",
307
- "ctor": "Float64Array",
308
- "array": [
309
- def.x || 0,
310
- def.y || 0,
311
- 0, // z
312
- 0, // quat x
313
- 0, // quat y
314
- rotation * Math.PI / 180, // quat z (rotation in radians)
315
- 1, // quat w
316
- scaleX,
317
- scaleY,
318
- 1 // scale z
319
- ]
320
- },
321
- "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": rotation },
322
- "_skewX": 0,
323
- "_skewY": 0,
324
- "_is3DNode": false,
325
- "_groupIndex": 0,
326
- "groupIndex": 0,
327
- "_id": uuid
328
- };
329
-
330
- data.push(node);
331
-
332
- // 记录 PrefabInfo
333
- prefabInfoList.push({
334
- nodeIndex,
335
- prefabInfo: {
336
- "__type__": "cc.PrefabInfo",
337
- "root": { "__id__": 1 },
338
- "asset": { "__id__": 0 },
339
- "fileId": fileId,
340
- "sync": false
341
- }
342
- });
343
-
344
- // 添加组件
345
- for (const { type, props } of compList) {
346
- if (Components[type]) {
347
- const comp = Components[type](nodeIndex);
348
- // 应用组件属性
349
- applyComponentProps(comp, props, node);
350
- const compIndex = data.length;
351
- data.push(comp);
352
- node._components.push({ "__id__": compIndex });
353
- }
354
- }
355
-
356
- // 更新父节点
357
- if (parentIndex !== null) {
358
- data[parentIndex]._children.push({ "__id__": nodeIndex });
359
- }
360
-
361
- // 递归处理子节点
362
- if (def.children && def.children.length > 0) {
363
- for (const child of def.children) {
364
- addNode(child, nodeIndex);
365
- }
104
+ // 递归处理子节点
105
+ if (def.children && def.children.length > 0) {
106
+ for (const childDef of def.children) {
107
+ addChildNode(data, childDef, nodeIndex);
366
108
  }
367
-
368
- return nodeIndex;
369
- }
370
-
371
- // 添加根节点
372
- addNode(nodeDef, null);
373
-
374
- // 添加所有 PrefabInfo
375
- for (const { nodeIndex, prefabInfo } of prefabInfoList) {
376
- data.push(prefabInfo);
377
- data[nodeIndex]._prefab = { "__id__": data.length - 1 };
378
109
  }
379
-
380
- return data;
381
110
  }
382
111
 
383
112
  function run(args) {
384
113
  if (args.length < 1) {
385
- console.log(JSON.stringify({
386
- error: '用法: cocos2d-cli create-prefab <输出路径.prefab>',
387
- hint: ' stdin 读取 JSON 结构生成预制体',
388
- example: 'type panel.json | cocos2d-cli create-prefab assets/panel.prefab'
389
- }));
114
+ outputError({
115
+ message: '用法: cocos2d-cli create-prefab [JSON文件路径] <输出路径.prefab>',
116
+ hint: '不传 JSON 则创建默认预制体'
117
+ });
390
118
  return;
391
119
  }
392
120
 
393
- const outputPath = args[0];
121
+ let jsonPath = null;
122
+ let outputPath;
394
123
 
395
- // stdin 读取 JSON
396
- let input = '';
397
-
398
- if (!process.stdin.isTTY) {
399
- input = fs.readFileSync(0, 'utf8');
124
+ if (args.length === 1) {
125
+ outputPath = args[0];
126
+ } else {
127
+ jsonPath = args[0];
128
+ outputPath = args[1];
400
129
  }
401
130
 
402
- // 没有 stdin 输入时创建空预制体
403
- if (!input.trim()) {
131
+ // 没有传 JSON,创建默认预制体
132
+ if (!jsonPath) {
404
133
  const prefabName = path.basename(outputPath, '.prefab');
405
134
 
406
135
  try {
407
136
  if (fs.existsSync(outputPath)) {
408
- console.log(JSON.stringify({ error: `文件已存在: ${outputPath}` }));
137
+ outputError(`文件已存在: ${outputPath}`);
409
138
  return;
410
139
  }
411
140
 
@@ -417,39 +146,39 @@ function run(args) {
417
146
  const data = createPrefab(prefabName);
418
147
  fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
419
148
 
420
- console.log(JSON.stringify({
421
- success: true,
149
+ outputSuccess({
422
150
  path: outputPath,
423
- rootName: prefabName
424
- }));
151
+ rootName: prefabName,
152
+ nodes: 1,
153
+ components: 0
154
+ });
425
155
  return;
426
156
  } catch (err) {
427
- console.log(JSON.stringify({ error: err.message }));
157
+ outputError(err.message);
428
158
  return;
429
159
  }
430
160
  }
431
161
 
162
+ if (!fs.existsSync(jsonPath)) {
163
+ outputError(`JSON 文件不存在: ${jsonPath}`);
164
+ return;
165
+ }
166
+
432
167
  try {
433
- // 移除 BOM 并解析 JSON
434
- input = input.replace(/^\uFEFF/, '').trim();
435
- const nodeDef = JSON.parse(input);
168
+ const input = fs.readFileSync(jsonPath, 'utf8');
169
+ const cleanInput = input.replace(/^\uFEFF/, '').trim();
170
+ const nodeDef = JSON.parse(cleanInput);
436
171
 
437
- // 支持数组格式(第一个作为根节点)
438
172
  const rootNode = Array.isArray(nodeDef) ? nodeDef[0] : nodeDef;
439
-
440
- // 生成预制体
441
173
  const prefabData = createPrefabData(rootNode);
442
174
 
443
- // 确保目录存在
444
175
  const outputDir = path.dirname(outputPath);
445
176
  if (!fs.existsSync(outputDir)) {
446
177
  fs.mkdirSync(outputDir, { recursive: true });
447
178
  }
448
179
 
449
- // 保存
450
180
  fs.writeFileSync(outputPath, JSON.stringify(prefabData, null, 2), 'utf8');
451
181
 
452
- // 统计
453
182
  let nodeCount = 0, compCount = 0;
454
183
  for (const item of prefabData) {
455
184
  if (item.__type__ === 'cc.Node') nodeCount++;
@@ -458,15 +187,14 @@ function run(args) {
458
187
  }
459
188
  }
460
189
 
461
- console.log(JSON.stringify({
462
- success: true,
190
+ outputSuccess({
463
191
  path: outputPath,
464
192
  nodes: nodeCount,
465
193
  components: compCount
466
- }));
194
+ });
467
195
 
468
196
  } catch (err) {
469
- console.log(JSON.stringify({ error: err.message }));
197
+ outputError(err.message);
470
198
  }
471
199
  }
472
200