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