cocos2d-cli 1.1.0 → 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,21 +1,30 @@
1
1
  /**
2
- * create-scene 命令 - 从树形文本结构创建场景文件
2
+ * create-scene 命令 - 从 JSON 结构创建场景文件
3
3
  *
4
- * 示例输入:
5
- * Canvas
6
- * ├─ TopBar (sprite, widget)
7
- * ├─ ScoreLabel (label)
8
- * ├─ LivesContainer
9
- * └─ GoldLabel (label)
10
- * ├─ GameArea
11
- * └─ BottomBar
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 节点
12
21
  */
13
22
 
14
23
  const fs = require('fs');
15
24
  const path = require('path');
16
- const { Components, generateId, createNodeData } = require('../lib/components');
25
+ const { Components, generateId } = require('../lib/components');
17
26
 
18
- // 支持的组件类型映射
27
+ // 支持的组件类型
19
28
  const COMPONENT_TYPES = {
20
29
  'sprite': 'sprite',
21
30
  'label': 'label',
@@ -29,136 +38,32 @@ const COMPONENT_TYPES = {
29
38
  };
30
39
 
31
40
  /**
32
- * 解析树形文本结构
33
- * 支持格式:
34
- * - 节点名称
35
- * - 节点名称 (组件1, 组件2)
36
- * - 节点名称 #width=100 #height=50 #x=10 #y=20
37
- *
38
- * 树形符号支持:
39
- * ├─ └─ │ 以及 Windows 下可能出现的 ? 乱码形式
41
+ * 解析颜色字符串 #RRGGBB 或 #RRGGBBAA
40
42
  */
41
- function parseTreeText(text) {
42
- const lines = text.split('\n').filter(line => line.trim());
43
- const rootNodes = [];
44
- const stack = []; // [{ depth, node }]
45
-
46
- for (const line of lines) {
47
- if (!line.trim()) continue;
48
-
49
- // 计算深度:通过测量前导非内容字符
50
- // Windows 下树形符号可能被编码成 ?? 或 ? ??
51
- // 策略:计算 ?? 或 ├─ 这样的分支标记数量
52
-
53
- let depth = 0;
54
- let contentStart = 0;
55
-
56
- // 首先尝试匹配树形模式
57
- // 模式1: Unicode 树形符号 ├ └
58
- // 模式2: Windows 乱码 ??
59
- const branchPattern = /([├└]─|├|└|\?\?|\?)\s*/g;
60
- const branches = [];
61
- let match;
62
- let lastBranchEnd = 0;
63
-
64
- while ((match = branchPattern.exec(line)) !== null) {
65
- branches.push(match[1]);
66
- lastBranchEnd = match.index + match[0].length;
67
- }
68
-
69
- if (branches.length > 0) {
70
- // 找到了分支符号,深度 = 分支数量
71
- depth = branches.length;
72
- contentStart = lastBranchEnd;
73
- } else {
74
- // 没有分支符号,检查缩进
75
- for (let i = 0; i < line.length; i++) {
76
- const char = line[i];
77
- if (char !== ' ' && char !== '\t') {
78
- contentStart = i;
79
- break;
80
- }
81
- }
82
- depth = Math.floor(contentStart / 4);
83
- }
84
-
85
- // 提取节点内容
86
- let content = line.substring(contentStart).trim();
87
-
88
- // 清理可能残留的符号(- 和空格)
89
- content = content.replace(/^[\-\s]+/, '').trim();
90
-
91
- if (!content) continue;
92
-
93
- // 解析节点信息
94
- const nodeInfo = parseNodeLine(content);
95
-
96
- // 构建树结构
97
- while (stack.length > depth) {
98
- stack.pop();
99
- }
100
-
101
- const node = {
102
- name: nodeInfo.name,
103
- components: nodeInfo.components,
104
- options: nodeInfo.options,
105
- children: []
43
+ function parseColor(colorStr) {
44
+ if (!colorStr || typeof colorStr !== 'string') return null;
45
+
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)
106
60
  };
107
-
108
- if (stack.length === 0) {
109
- rootNodes.push(node);
110
- } else {
111
- stack[stack.length - 1].node.children.push(node);
112
- }
113
-
114
- stack.push({ depth, node });
115
- }
116
-
117
- return rootNodes;
118
- }
119
-
120
- /**
121
- * 解析单行节点定义
122
- * 格式:NodeName (comp1, comp2) #key=value
123
- */
124
- function parseNodeLine(line) {
125
- let name = line;
126
- let components = [];
127
- let options = {};
128
-
129
- // 提取组件 (xxx)
130
- const compMatch = line.match(/\(([^)]+)\)/);
131
- if (compMatch) {
132
- name = name.replace(compMatch[0], '').trim();
133
- const compList = compMatch[1].split(',').map(c => c.trim().toLowerCase());
134
- for (const comp of compList) {
135
- if (COMPONENT_TYPES[comp]) {
136
- components.push(COMPONENT_TYPES[comp]);
137
- }
138
- }
139
- }
140
-
141
- // 提取选项 #key=value
142
- const optionMatches = name.matchAll(/#(\w+)=([^\s#]+)/g);
143
- for (const match of optionMatches) {
144
- const key = match[1];
145
- let value = match[2];
146
-
147
- // 类型转换
148
- if (/^\d+$/.test(value)) value = parseInt(value);
149
- else if (/^\d+\.\d+$/.test(value)) value = parseFloat(value);
150
- else if (value === 'true') value = true;
151
- else if (value === 'false') value = false;
152
-
153
- options[key] = value;
154
61
  }
155
- name = name.replace(/#\w+=[^\s#]+/g, '').trim();
156
-
157
- return { name, components, options };
62
+ return null;
158
63
  }
159
64
 
160
65
  /**
161
- * 生成 UUID(简化版)
66
+ * 生成 UUID
162
67
  */
163
68
  function generateUUID() {
164
69
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
@@ -169,67 +74,343 @@ function generateUUID() {
169
74
  }
170
75
 
171
76
  /**
172
- * 创建场景文件数据结构(基于模板)
77
+ * 解析组件定义
173
78
  */
174
- function createSceneData(rootNodes, sceneName) {
175
- // 加载模板
176
- const templatePath = path.join(__dirname, '..', '..', 'data', 'scene-template.json');
177
- let data;
79
+ function parseComponent(compDef) {
80
+ if (typeof compDef === 'string') {
81
+ const type = COMPONENT_TYPES[compDef.toLowerCase()];
82
+ return type ? { type, props: {} } : null;
83
+ }
178
84
 
179
- try {
180
- const templateContent = fs.readFileSync(templatePath, 'utf8');
181
- data = JSON.parse(templateContent);
182
- } catch (e) {
183
- // 如果模板加载失败,使用内置基础结构
184
- data = createBasicSceneTemplate();
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 };
185
92
  }
93
+
94
+ return null;
95
+ }
186
96
 
187
- // 设置场景名称
188
- data[0]._name = sceneName || "NewScene";
189
- data[1]._name = sceneName || "NewScene";
97
+ /**
98
+ * 应用组件属性
99
+ */
100
+ function applyComponentProps(comp, props) {
101
+ if (!props || !comp) return;
102
+
103
+ const type = comp.__type__;
104
+
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 };
195
+ }
196
+ } else {
197
+ comp[compKey] = value;
198
+ }
199
+ }
200
+ }
201
+ }
190
202
 
191
- // 生成新的 UUID
192
- data[1]._id = generateUUID();
193
- data[2]._id = generateUUID();
194
- data[3]._id = generateUUID();
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
+
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
+ });
316
+
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
+ });
195
356
 
196
- // Canvas 节点在索引 2
357
+ // Canvas 节点索引
197
358
  const canvasIndex = 2;
198
- const canvas = data[canvasIndex];
199
359
 
200
- // 递归添加节点
201
- function addNode(nodeDef, parentIndex) {
360
+ // 递归添加用户节点
361
+ function addNode(def, parentIndex) {
202
362
  const nodeIndex = data.length;
203
363
  const uuid = generateUUID();
204
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
+
205
388
  // 创建节点
206
389
  const node = {
207
390
  "__type__": "cc.Node",
208
- "_name": nodeDef.name,
391
+ "_name": def.name || 'Node',
209
392
  "_objFlags": 0,
210
393
  "_parent": { "__id__": parentIndex },
211
394
  "_children": [],
212
- "_active": true,
395
+ "_active": def.active !== false,
213
396
  "_components": [],
214
397
  "_prefab": null,
215
- "_opacity": 255,
216
- "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
217
- "_contentSize": {
218
- "__type__": "cc.Size",
219
- "width": nodeDef.options.width || 0,
220
- "height": nodeDef.options.height || 0
221
- },
222
- "_anchorPoint": { "__type__": "cc.Vec2", "x": 0.5, "y": 0.5 },
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 },
223
402
  "_trs": {
224
403
  "__type__": "TypedArray",
225
404
  "ctor": "Float64Array",
226
405
  "array": [
227
- nodeDef.options.x || 0,
228
- nodeDef.options.y || 0,
229
- 0, 0, 0, 0, 1, 1, 1, 1
406
+ def.x || 0,
407
+ def.y || 0,
408
+ 0, 0, 0,
409
+ rotation * Math.PI / 180,
410
+ 1, scaleX, scaleY, 1
230
411
  ]
231
412
  },
232
- "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": 0 },
413
+ "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": rotation },
233
414
  "_skewX": 0,
234
415
  "_skewY": 0,
235
416
  "_is3DNode": false,
@@ -241,9 +422,10 @@ function createSceneData(rootNodes, sceneName) {
241
422
  data.push(node);
242
423
 
243
424
  // 添加组件
244
- for (const compType of nodeDef.components) {
245
- if (Components[compType]) {
246
- const comp = Components[compType](nodeIndex);
425
+ for (const { type, props } of compList) {
426
+ if (Components[type]) {
427
+ const comp = Components[type](nodeIndex);
428
+ applyComponentProps(comp, props);
247
429
  const compIndex = data.length;
248
430
  data.push(comp);
249
431
  node._components.push({ "__id__": compIndex });
@@ -251,213 +433,63 @@ function createSceneData(rootNodes, sceneName) {
251
433
  }
252
434
 
253
435
  // 更新父节点的 _children
254
- const parent = data[parentIndex];
255
- if (parent && parent._children) {
256
- parent._children.push({ "__id__": nodeIndex });
257
- }
436
+ data[parentIndex]._children.push({ "__id__": nodeIndex });
258
437
 
259
438
  // 递归处理子节点
260
- for (const child of nodeDef.children) {
261
- addNode(child, nodeIndex);
439
+ if (def.children && def.children.length > 0) {
440
+ for (const child of def.children) {
441
+ addNode(child, nodeIndex);
442
+ }
262
443
  }
263
444
 
264
445
  return nodeIndex;
265
446
  }
266
447
 
267
- // 处理输入的节点结构
268
- // 如果第一个节点是 Canvas,把它的子节点添加到模板的 Canvas
269
- if (rootNodes.length > 0 && rootNodes[0].name === 'Canvas') {
270
- // 用户定义了 Canvas 节点,把它的子节点添加到模板的 Canvas
271
- for (const child of rootNodes[0].children) {
272
- addNode(child, canvasIndex);
273
- }
274
- } else {
275
- // 用户定义的是其他节点,直接添加到 Canvas 下
276
- for (const rootNode of rootNodes) {
277
- addNode(rootNode, canvasIndex);
278
- }
448
+ // 支持数组或单个节点
449
+ const nodes = Array.isArray(nodeDefs) ? nodeDefs : [nodeDefs];
450
+
451
+ // 添加用户节点到 Canvas
452
+ for (const nodeDef of nodes) {
453
+ addNode(nodeDef, canvasIndex);
279
454
  }
280
455
 
281
456
  return data;
282
457
  }
283
458
 
284
- /**
285
- * 创建基础场景模板(当模板文件不存在时使用)
286
- */
287
- function createBasicSceneTemplate() {
288
- return [
289
- {
290
- "__type__": "cc.SceneAsset",
291
- "_name": "",
292
- "_objFlags": 0,
293
- "_native": "",
294
- "scene": { "__id__": 1 }
295
- },
296
- {
297
- "__type__": "cc.Scene",
298
- "_objFlags": 0,
299
- "_parent": null,
300
- "_children": [{ "__id__": 2 }],
301
- "_active": true,
302
- "_components": [],
303
- "_prefab": null,
304
- "_opacity": 255,
305
- "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
306
- "_contentSize": { "__type__": "cc.Size", "width": 0, "height": 0 },
307
- "_anchorPoint": { "__type__": "cc.Vec2", "x": 0, "y": 0 },
308
- "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] },
309
- "_is3DNode": true,
310
- "_groupIndex": 0,
311
- "groupIndex": 0,
312
- "autoReleaseAssets": false,
313
- "_id": ""
314
- },
315
- {
316
- "__type__": "cc.Node",
317
- "_name": "Canvas",
318
- "_objFlags": 0,
319
- "_parent": { "__id__": 1 },
320
- "_children": [{ "__id__": 3 }],
321
- "_active": true,
322
- "_components": [{ "__id__": 5 }, { "__id__": 6 }],
323
- "_prefab": null,
324
- "_opacity": 255,
325
- "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
326
- "_contentSize": { "__type__": "cc.Size", "width": 960, "height": 640 },
327
- "_anchorPoint": { "__type__": "cc.Vec2", "x": 0.5, "y": 0.5 },
328
- "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [480, 320, 0, 0, 0, 0, 1, 1, 1, 1] },
329
- "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": 0 },
330
- "_skewX": 0,
331
- "_skewY": 0,
332
- "_is3DNode": false,
333
- "_groupIndex": 0,
334
- "groupIndex": 0,
335
- "_id": ""
336
- },
337
- {
338
- "__type__": "cc.Node",
339
- "_name": "Main Camera",
340
- "_objFlags": 0,
341
- "_parent": { "__id__": 2 },
342
- "_children": [],
343
- "_active": true,
344
- "_components": [{ "__id__": 4 }],
345
- "_prefab": null,
346
- "_opacity": 255,
347
- "_color": { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
348
- "_contentSize": { "__type__": "cc.Size", "width": 0, "height": 0 },
349
- "_anchorPoint": { "__type__": "cc.Vec2", "x": 0.5, "y": 0.5 },
350
- "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] },
351
- "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": 0 },
352
- "_skewX": 0,
353
- "_skewY": 0,
354
- "_is3DNode": false,
355
- "_groupIndex": 0,
356
- "groupIndex": 0,
357
- "_id": ""
358
- },
359
- {
360
- "__type__": "cc.Camera",
361
- "_name": "",
362
- "_objFlags": 0,
363
- "node": { "__id__": 3 },
364
- "_enabled": true,
365
- "_cullingMask": 4294967295,
366
- "_clearFlags": 7,
367
- "_backgroundColor": { "__type__": "cc.Color", "r": 0, "g": 0, "b": 0, "a": 255 },
368
- "_depth": -1,
369
- "_zoomRatio": 1,
370
- "_targetTexture": null,
371
- "_fov": 60,
372
- "_orthoSize": 10,
373
- "_nearClip": 1,
374
- "_farClip": 4096,
375
- "_ortho": true,
376
- "_rect": { "__type__": "cc.Rect", "x": 0, "y": 0, "width": 1, "height": 1 },
377
- "_renderStages": 1,
378
- "_alignWithScreen": true,
379
- "_id": ""
380
- },
381
- {
382
- "__type__": "cc.Canvas",
383
- "_name": "",
384
- "_objFlags": 0,
385
- "node": { "__id__": 2 },
386
- "_enabled": true,
387
- "_designResolution": { "__type__": "cc.Size", "width": 960, "height": 640 },
388
- "_fitWidth": false,
389
- "_fitHeight": true,
390
- "_id": ""
391
- },
392
- {
393
- "__type__": "cc.Widget",
394
- "_name": "",
395
- "_objFlags": 0,
396
- "node": { "__id__": 2 },
397
- "_enabled": true,
398
- "alignMode": 1,
399
- "_target": null,
400
- "_alignFlags": 45,
401
- "_left": 0,
402
- "_right": 0,
403
- "_top": 0,
404
- "_bottom": 0,
405
- "_verticalCenter": 0,
406
- "_horizontalCenter": 0,
407
- "_isAbsLeft": true,
408
- "_isAbsRight": true,
409
- "_isAbsTop": true,
410
- "_isAbsBottom": true,
411
- "_isAbsHorizontalCenter": true,
412
- "_isAbsVerticalCenter": true,
413
- "_originalWidth": 0,
414
- "_originalHeight": 0,
415
- "_id": ""
416
- }
417
- ];
418
- }
419
-
420
459
  function run(args) {
421
460
  if (args.length < 1) {
422
461
  console.log(JSON.stringify({
423
- error: '用法: cocos2.4 create-scene <输出路径.fire> [场景名称]',
424
- hint: '从 stdin 读取树形结构,例如:\n echo "Canvas\\n├─ TopBar (sprite)" | cocos2.4 create-scene assets/scene.fire'
462
+ error: '用法: cocos2d-cli create-scene <输出路径.fire>',
463
+ hint: '从 stdin 读取 JSON 结构生成场景',
464
+ example: 'type scene.json | cocos2d-cli create-scene assets/scene.fire'
425
465
  }));
426
466
  return;
427
467
  }
428
468
 
429
469
  const outputPath = args[0];
430
- const sceneName = args[1] || path.basename(outputPath, '.fire');
470
+ const sceneName = path.basename(outputPath, '.fire');
431
471
 
432
- // 从 stdin 读取树形结构
472
+ // 从 stdin 读取 JSON
433
473
  let input = '';
434
474
 
435
- // 检查是否是管道输入
436
475
  if (!process.stdin.isTTY) {
437
- // 同步读取 stdin(简化处理)
438
- const fs = require('fs');
439
476
  input = fs.readFileSync(0, 'utf8');
440
477
  }
441
478
 
442
479
  if (!input.trim()) {
443
480
  console.log(JSON.stringify({
444
- error: '请通过 stdin 提供场景结构',
445
- example: `echo "Canvas (canvas)\\n├─ TopBar (sprite, widget)\\n│ └─ ScoreLabel (label)" | cocos2.4 create-scene assets/game.fire`
481
+ error: '请通过 stdin 提供 JSON 结构'
446
482
  }));
447
483
  return;
448
484
  }
449
485
 
450
486
  try {
451
- // 解析树形结构
452
- const rootNodes = parseTreeText(input);
453
-
454
- if (rootNodes.length === 0) {
455
- console.log(JSON.stringify({ error: '未能解析出任何节点' }));
456
- return;
457
- }
487
+ // 移除 BOM 并解析 JSON
488
+ input = input.replace(/^\uFEFF/, '').trim();
489
+ const nodeDef = JSON.parse(input);
458
490
 
459
491
  // 生成场景数据
460
- const sceneData = createSceneData(rootNodes, sceneName);
492
+ const sceneData = createSceneData(nodeDef, sceneName);
461
493
 
462
494
  // 确保输出目录存在
463
495
  const outputDir = path.dirname(outputPath);
@@ -469,11 +501,10 @@ function run(args) {
469
501
  fs.writeFileSync(outputPath, JSON.stringify(sceneData, null, 2), 'utf8');
470
502
 
471
503
  // 统计信息
472
- let nodeCount = 0;
473
- let compCount = 0;
504
+ let nodeCount = 0, compCount = 0;
474
505
  for (const item of sceneData) {
475
506
  if (item.__type__ === 'cc.Node') nodeCount++;
476
- else if (item.__type__?.startsWith('cc.') && item.__type__ !== 'cc.Scene' && item.__type__ !== 'cc.SceneAsset') {
507
+ else if (item.__type__?.startsWith('cc.') && !['cc.Scene', 'cc.SceneAsset'].includes(item.__type__)) {
477
508
  compCount++;
478
509
  }
479
510
  }
@@ -482,8 +513,7 @@ function run(args) {
482
513
  success: true,
483
514
  path: outputPath,
484
515
  nodes: nodeCount,
485
- components: compCount,
486
- structure: rootNodes.map(n => n.name)
516
+ components: compCount
487
517
  }));
488
518
 
489
519
  } catch (err) {
@@ -491,4 +521,4 @@ function run(args) {
491
521
  }
492
522
  }
493
523
 
494
- module.exports = { run };
524
+ module.exports = { run };