cocos2d-cli 1.1.1 → 1.1.2

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,46 +1,458 @@
1
1
  /**
2
- * prefab-create 命令 - 创建新预制体文件
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 + 各组件特有属性
3
19
  */
4
20
 
5
- const { saveScene, createPrefab } = require('../lib/fire-utils');
6
21
  const fs = require('fs');
7
22
  const path = require('path');
23
+ const { Components, generateId } = require('../lib/components');
24
+ const { createPrefab, generateFileId } = require('../lib/fire-utils');
8
25
 
9
- function run(args) {
10
- if (args.length < 2) {
11
- console.log(JSON.stringify({ error: '用法: cocos2.4 prefab-create <预制体路径> <根节点名称>' }));
12
- return;
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
+ };
38
+
39
+ /**
40
+ * 解析颜色字符串 #RRGGBB 或 #RRGGBBAA
41
+ */
42
+ function parseColor(colorStr) {
43
+ if (!colorStr || typeof colorStr !== 'string') return null;
44
+
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
+ };
60
+ }
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
+ });
73
+ }
74
+
75
+ /**
76
+ * 解析组件定义
77
+ * 支持两种格式:
78
+ * 1. 字符串: "sprite"
79
+ * 2. 对象: { "type": "sprite", "sizeMode": 1 }
80
+ */
81
+ function parseComponent(compDef) {
82
+ if (typeof compDef === 'string') {
83
+ const type = COMPONENT_TYPES[compDef.toLowerCase()];
84
+ return type ? { type, props: {} } : null;
13
85
  }
14
86
 
15
- const prefabPath = args[0];
16
- const rootName = args[1] || 'RootNode';
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 };
94
+ }
17
95
 
18
- try {
19
- // 检查文件是否已存在
20
- if (fs.existsSync(prefabPath)) {
21
- console.log(JSON.stringify({ error: `文件已存在: ${prefabPath}` }));
22
- return;
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * 应用组件属性
101
+ */
102
+ function applyComponentProps(comp, props) {
103
+ if (!props || !comp) return;
104
+
105
+ const type = comp.__type__;
106
+
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];
124
+ }
23
125
  }
24
126
 
25
- // 确保目录存在
26
- const dir = path.dirname(prefabPath);
27
- if (!fs.existsSync(dir)) {
28
- fs.mkdirSync(dir, { recursive: true });
127
+ if (alignFlags > 0) {
128
+ comp._alignFlags = alignFlags;
29
129
  }
130
+ }
131
+
132
+ // 通用属性映射
133
+ const propMap = {
134
+ // Sprite
135
+ 'sizeMode': '_sizeMode',
136
+ 'fillType': '_fillType',
137
+ 'fillCenter': '_fillCenter',
138
+ 'fillStart': '_fillStart',
139
+ 'fillRange': '_fillRange',
140
+ 'trim': '_isTrimmedMode',
30
141
 
31
- // 创建预制体数据
32
- const data = createPrefab(rootName);
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',
33
151
 
34
- // 保存文件
35
- saveScene(prefabPath, data);
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',
36
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'
193
+ };
194
+
195
+ // 特殊处理:label 的 string 属性需要同时设置 _N$string
196
+ if (type === 'cc.Label' && props.string !== undefined) {
197
+ comp._N$string = props.string;
198
+ }
199
+
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
+ }
219
+ }
220
+ }
221
+ }
222
+
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
+ function run(args) {
372
+ if (args.length < 1) {
37
373
  console.log(JSON.stringify({
38
- success: true,
39
- path: prefabPath,
40
- rootName: rootName,
41
- message: `预制体创建成功: ${rootName}`
374
+ error: '用法: cocos2d-cli create-prefab <输出路径.prefab>',
375
+ hint: '从 stdin 读取 JSON 结构生成预制体',
376
+ example: 'type panel.json | cocos2d-cli create-prefab assets/panel.prefab'
42
377
  }));
378
+ return;
379
+ }
380
+
381
+ const outputPath = args[0];
382
+
383
+ // 从 stdin 读取 JSON
384
+ let input = '';
385
+
386
+ if (!process.stdin.isTTY) {
387
+ input = fs.readFileSync(0, 'utf8');
388
+ }
389
+
390
+ // 没有 stdin 输入时创建空预制体
391
+ if (!input.trim()) {
392
+ const prefabName = path.basename(outputPath, '.prefab');
43
393
 
394
+ try {
395
+ if (fs.existsSync(outputPath)) {
396
+ console.log(JSON.stringify({ error: `文件已存在: ${outputPath}` }));
397
+ return;
398
+ }
399
+
400
+ const dir = path.dirname(outputPath);
401
+ if (!fs.existsSync(dir)) {
402
+ fs.mkdirSync(dir, { recursive: true });
403
+ }
404
+
405
+ const data = createPrefab(prefabName);
406
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
407
+
408
+ console.log(JSON.stringify({
409
+ success: true,
410
+ path: outputPath,
411
+ rootName: prefabName
412
+ }));
413
+ return;
414
+ } catch (err) {
415
+ console.log(JSON.stringify({ error: err.message }));
416
+ return;
417
+ }
418
+ }
419
+
420
+ try {
421
+ // 移除 BOM 并解析 JSON
422
+ input = input.replace(/^\uFEFF/, '').trim();
423
+ const nodeDef = JSON.parse(input);
424
+
425
+ // 支持数组格式(第一个作为根节点)
426
+ const rootNode = Array.isArray(nodeDef) ? nodeDef[0] : nodeDef;
427
+
428
+ // 生成预制体
429
+ const prefabData = createPrefabData(rootNode);
430
+
431
+ // 确保目录存在
432
+ const outputDir = path.dirname(outputPath);
433
+ if (!fs.existsSync(outputDir)) {
434
+ fs.mkdirSync(outputDir, { recursive: true });
435
+ }
436
+
437
+ // 保存
438
+ fs.writeFileSync(outputPath, JSON.stringify(prefabData, null, 2), 'utf8');
439
+
440
+ // 统计
441
+ let nodeCount = 0, compCount = 0;
442
+ for (const item of prefabData) {
443
+ if (item.__type__ === 'cc.Node') nodeCount++;
444
+ else if (item.__type__?.startsWith('cc.') && !['cc.Prefab', 'cc.PrefabInfo'].includes(item.__type__)) {
445
+ compCount++;
446
+ }
447
+ }
448
+
449
+ console.log(JSON.stringify({
450
+ success: true,
451
+ path: outputPath,
452
+ nodes: nodeCount,
453
+ components: compCount
454
+ }));
455
+
44
456
  } catch (err) {
45
457
  console.log(JSON.stringify({ error: err.message }));
46
458
  }