cocos2d-cli 1.0.4 → 1.1.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,14 +1,38 @@
1
1
  /**
2
- * add 命令 - 添加节点
3
- * 按照 Cocos Creator 的深度优先遍历顺序插入节点
2
+ * add 命令 - 添加节点(支持场景和预制体)
3
+ *
4
+ * 预制体正确结构(深度优先遍历):
5
+ * 节点 → 子节点(递归) → 组件 → PrefabInfo
6
+ *
7
+ * 示例:
8
+ * [1] Node (根) _prefab -> [最后]
9
+ * [2] Node (子1) _prefab -> [6]
10
+ * [3] Node (孙) _prefab -> [5]
11
+ * [4] Component (孙的组件)
12
+ * [5] PrefabInfo (孙的)
13
+ * [6] Component (子1的组件)
14
+ * [7] PrefabInfo (子1的)
15
+ * [8] PrefabInfo (根的,在最后)
4
16
  */
5
17
 
6
- const { loadScene, saveScene, buildMaps, findNodeIndex, refreshEditor } = require('../lib/fire-utils');
18
+ const { loadScene, saveScene, buildMaps, refreshEditor, isPrefab, generateFileId } = require('../lib/fire-utils');
7
19
  const { Components, createNodeData } = require('../lib/components');
8
20
 
9
21
  /**
10
- * 获取节点及其所有子树的最后一个索引(用于确定插入位置)
11
- * 按照深度优先遍历,返回该节点子树的结束位置
22
+ * 获取根节点 PrefabInfo 的索引(预制体最后一个元素)
23
+ */
24
+ function getRootPrefabInfoIndex(data) {
25
+ for (let i = data.length - 1; i >= 0; i--) {
26
+ if (data[i].__type__ === 'cc.PrefabInfo') {
27
+ const rootRef = data[i].root?.__id__;
28
+ if (rootRef === 1) return i;
29
+ }
30
+ }
31
+ return data.length - 1;
32
+ }
33
+
34
+ /**
35
+ * 获取节点子树的结束索引(子节点 → 组件 → PrefabInfo)
12
36
  */
13
37
  function getSubtreeEndIndex(data, nodeIndex) {
14
38
  const node = data[nodeIndex];
@@ -24,33 +48,35 @@ function getSubtreeEndIndex(data, nodeIndex) {
24
48
  }
25
49
  }
26
50
 
27
- // 处理组件(组件在子节点之后)
51
+ // 处理组件
28
52
  if (node._components) {
29
53
  for (const compRef of node._components) {
30
54
  lastIndex = Math.max(lastIndex, compRef.__id__);
31
55
  }
32
56
  }
33
57
 
58
+ // PrefabInfo 在最后(根节点的除外,它在整个数组最后)
59
+ if (node._prefab && nodeIndex !== 1) {
60
+ lastIndex = Math.max(lastIndex, node._prefab.__id__);
61
+ }
62
+
34
63
  return lastIndex;
35
64
  }
36
65
 
37
66
  /**
38
- * 重建所有 __id__ 引用(插入元素后索引变化)
39
- * 返回新旧索引的映射表
67
+ * 重建所有 __id__ 引用
40
68
  */
41
- function rebuildReferencesForInsert(data, insertIndex) {
69
+ function rebuildReferences(data, insertIndex, count) {
42
70
  const indexMap = {};
43
71
 
44
- // 构建映射:旧索引 -> 新索引
45
72
  for (let oldIndex = 0; oldIndex < data.length; oldIndex++) {
46
73
  if (oldIndex < insertIndex) {
47
74
  indexMap[oldIndex] = oldIndex;
48
75
  } else {
49
- indexMap[oldIndex] = oldIndex + 1;
76
+ indexMap[oldIndex] = oldIndex + count;
50
77
  }
51
78
  }
52
79
 
53
- // 更新所有 __id__ 引用
54
80
  function updateRef(obj) {
55
81
  if (!obj || typeof obj !== 'object') return;
56
82
 
@@ -75,11 +101,11 @@ function rebuildReferencesForInsert(data, insertIndex) {
75
101
 
76
102
  function run(args) {
77
103
  if (args.length < 3) {
78
- console.log(JSON.stringify({ error: '用法: cocos2.4 add <场景文件路径> <父节点> <节点名称> [选项]' }));
104
+ console.log(JSON.stringify({ error: '用法: cocos2.4 add <场景.fire | 预制体.prefab> <父节点索引> <节点名称> [选项]' }));
79
105
  return;
80
106
  }
81
107
 
82
- const scenePath = args[0];
108
+ const filePath = args[0];
83
109
  const parentRef = args[1];
84
110
  const nodeName = args[2];
85
111
 
@@ -95,133 +121,236 @@ function run(args) {
95
121
  else if (key === 'height') options.height = parseFloat(value) || 0;
96
122
  else if (key === 'at') options.at = parseInt(value);
97
123
  else if (key === 'active') options.active = value !== 'false';
124
+ else if (key === 'color') options.color = value;
125
+ else if (key === 'fontSize') options.fontSize = parseInt(value) || 40;
126
+ else if (key === 'string') options.string = value || '';
98
127
  }
99
128
  });
100
129
 
101
130
  try {
102
- const data = loadScene(scenePath);
103
- const { indexMap } = buildMaps(data);
131
+ const data = loadScene(filePath);
132
+ const { prefab } = buildMaps(data);
104
133
 
105
- // 查找父节点
106
- const parentIndex = findNodeIndex(data, indexMap, parentRef);
134
+ if (!/^\d+$/.test(parentRef)) {
135
+ console.log(JSON.stringify({ error: '父节点必须使用数字索引,请先用 tree 命令查看节点索引' }));
136
+ return;
137
+ }
107
138
 
108
- if (parentIndex === null || !data[parentIndex]) {
109
- console.log(JSON.stringify({ error: `找不到父节点: ${parentRef}` }));
139
+ const parentIndex = parseInt(parentRef);
140
+
141
+ if (parentIndex < 0 || parentIndex >= data.length || !data[parentIndex]) {
142
+ console.log(JSON.stringify({ error: `无效的节点索引: ${parentRef}` }));
110
143
  return;
111
144
  }
112
145
 
113
146
  const parentNode = data[parentIndex];
147
+ const isRootChild = prefab && parentIndex === 1;
114
148
 
115
- // 确定插入位置
116
- // 按照 _children 顺序,找到应该插入的位置
149
+ // 确定插入位置:紧跟父节点之后(在父节点的子节点/组件/PrefabInfo之前)
117
150
  let insertIndex;
151
+
118
152
  if (!parentNode._children || parentNode._children.length === 0) {
119
- // 父节点没有子节点,新节点紧跟父节点之后
153
+ // 父节点还没有子节点,插入到父节点之后
120
154
  insertIndex = parentIndex + 1;
121
155
  } else {
122
- // 根据 --at 参数确定插入位置
156
+ // 父节点已有子节点,根据 --at 参数确定位置
123
157
  const targetPosition = options.at >= 0 ? options.at : parentNode._children.length;
124
158
 
125
159
  if (targetPosition === 0) {
126
- // 插入到第一个子节点位置,紧跟父节点之后
127
- insertIndex = parentIndex + 1;
160
+ // 插入到第一个子节点之前
161
+ insertIndex = parentNode._children[0].__id__;
128
162
  } else if (targetPosition >= parentNode._children.length) {
129
- // 插入到最后,在所有现有子节点之后
163
+ // 插入到最后一个子节点之后
130
164
  const lastChildRef = parentNode._children[parentNode._children.length - 1];
131
165
  insertIndex = getSubtreeEndIndex(data, lastChildRef.__id__) + 1;
132
166
  } else {
133
- // 插入到中间某个位置
134
- const beforeChildRef = parentNode._children[targetPosition - 1];
135
- insertIndex = getSubtreeEndIndex(data, beforeChildRef.__id__) + 1;
167
+ // 插入到中间
168
+ insertIndex = parentNode._children[targetPosition].__id__;
136
169
  }
137
170
  }
138
171
 
139
- // 创建新节点
140
- const newNode = createNodeData(nodeName, parentIndex, options);
141
-
142
- // 在正确位置插入新节点
143
- data.splice(insertIndex, 0, newNode);
144
-
145
- // 重建索引引用(因为插入了新元素,后续索引都+1)
146
- const insertIndexMap = rebuildReferencesForInsert(data, insertIndex);
147
-
148
- // 新节点的实际索引
149
- const newNodeIndex = insertIndex;
172
+ let newNodeIndex;
150
173
 
151
- // 更新新节点的 _parent 引用(使用映射后的父节点索引)
152
- const newParentIndex = insertIndexMap[parentIndex] !== undefined ? insertIndexMap[parentIndex] : parentIndex;
153
- newNode._parent = { "__id__": newParentIndex };
154
-
155
- // 添加组件(如果有)
156
- let componentIndex = -1;
157
- if (options.type) {
158
- const compData = Components[options.type]?.(newNodeIndex);
174
+ if (prefab) {
175
+ // 预制体模式
176
+ // 找到根节点的 PrefabInfo
177
+ const rootPrefabInfoOldIdx = getRootPrefabInfoIndex(data);
178
+ const rootPrefabInfo = data[rootPrefabInfoOldIdx];
179
+
180
+ // 创建节点
181
+ const nodeData = {
182
+ "__type__": "cc.Node",
183
+ "_name": nodeName,
184
+ "_objFlags": 0,
185
+ "_parent": { "__id__": parentIndex },
186
+ "_children": [],
187
+ "_active": options.active !== false,
188
+ "_components": [],
189
+ "_prefab": null,
190
+ "_opacity": 255,
191
+ "_color": parseColor(options.color) || { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 },
192
+ "_contentSize": { "__type__": "cc.Size", "width": options.width || 0, "height": options.height || 0 },
193
+ "_anchorPoint": { "__type__": "cc.Vec2", "x": 0.5, "y": 0.5 },
194
+ "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [options.x || 0, options.y || 0, 0, 0, 0, 0, 1, 1, 1, 1] },
195
+ "_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": 0 },
196
+ "_skewX": 0,
197
+ "_skewY": 0,
198
+ "_is3DNode": false,
199
+ "_groupIndex": 0,
200
+ "groupIndex": 0,
201
+ "_id": ""
202
+ };
203
+
204
+ // 创建组件(如果有)
205
+ let compData = null;
206
+ if (options.type) {
207
+ compData = Components[options.type]?.(insertIndex);
208
+ // 修改组件属性
209
+ if (compData) {
210
+ if (options.type === 'label') {
211
+ if (options.fontSize) {
212
+ compData._fontSize = options.fontSize;
213
+ compData._lineHeight = options.fontSize;
214
+ }
215
+ if (options.string) {
216
+ compData._string = options.string;
217
+ compData._N$string = options.string;
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ // 创建 PrefabInfo
224
+ const prefabInfo = {
225
+ "__type__": "cc.PrefabInfo",
226
+ "root": { "__id__": 1 },
227
+ "asset": { "__id__": 0 },
228
+ "fileId": generateFileId(),
229
+ "sync": false
230
+ };
231
+
232
+ // 构建要插入的元素:节点 → 组件 → PrefabInfo
233
+ const itemsToInsert = [nodeData];
234
+ if (compData) itemsToInsert.push(compData);
235
+ itemsToInsert.push(prefabInfo);
236
+
237
+ // 如果是根节点的子节点,先移除根 PrefabInfo
238
+ if (isRootChild) {
239
+ // 如果插入位置在根 PrefabInfo 之后,需要调整
240
+ if (insertIndex > rootPrefabInfoOldIdx) {
241
+ insertIndex--;
242
+ }
243
+ data.splice(rootPrefabInfoOldIdx, 1);
244
+ }
245
+
246
+ // 插入元素
247
+ for (let i = 0; i < itemsToInsert.length; i++) {
248
+ data.splice(insertIndex + i, 0, itemsToInsert[i]);
249
+ }
250
+
251
+ // 重建引用
252
+ rebuildReferences(data, insertIndex, itemsToInsert.length);
253
+
254
+ newNodeIndex = insertIndex;
255
+
256
+ // 设置引用
257
+ if (compData) {
258
+ compData.node = { "__id__": newNodeIndex };
259
+ nodeData._components.push({ "__id__": newNodeIndex + 1 });
260
+ nodeData._prefab = { "__id__": newNodeIndex + 2 };
261
+ } else {
262
+ nodeData._prefab = { "__id__": newNodeIndex + 1 };
263
+ }
264
+
265
+ // 如果是根节点的子节点,把根 PrefabInfo 添加到最后
266
+ if (isRootChild) {
267
+ data.push(rootPrefabInfo);
268
+ data[1]._prefab = { "__id__": data.length - 1 };
269
+ }
270
+
271
+ } else {
272
+ // 场景模式:节点 + 组件
273
+ const newNode = createNodeData(nodeName, parentIndex, options);
274
+
275
+ if (options.color) {
276
+ const color = parseColor(options.color);
277
+ if (color) newNode._color = color;
278
+ }
279
+
280
+ const itemsToInsert = [newNode];
281
+
282
+ let compData = null;
283
+ if (options.type) {
284
+ compData = Components[options.type]?.(insertIndex);
285
+ // 修改组件属性
286
+ if (compData) {
287
+ if (options.type === 'label') {
288
+ if (options.fontSize) {
289
+ compData._fontSize = options.fontSize;
290
+ compData._lineHeight = options.fontSize;
291
+ }
292
+ if (options.string) {
293
+ compData._string = options.string;
294
+ compData._N$string = options.string;
295
+ }
296
+ }
297
+ }
298
+ if (compData) itemsToInsert.push(compData);
299
+ }
300
+
301
+ for (let i = 0; i < itemsToInsert.length; i++) {
302
+ data.splice(insertIndex + i, 0, itemsToInsert[i]);
303
+ }
304
+
305
+ rebuildReferences(data, insertIndex, itemsToInsert.length);
306
+
307
+ newNodeIndex = insertIndex;
308
+
159
309
  if (compData) {
160
- // 组件应该放在新节点子树之后(这里新节点没有子节点,所以紧跟节点后)
161
- const compInsertIndex = newNodeIndex + 1;
162
- data.splice(compInsertIndex, 0, compData);
163
-
164
- // 再次重建引用
165
- rebuildReferencesForInsert(data, compInsertIndex);
166
-
167
- componentIndex = compInsertIndex;
168
- newNode._components.push({ "__id__": componentIndex });
310
+ compData.node = { "__id__": newNodeIndex };
311
+ newNode._components.push({ "__id__": newNodeIndex + 1 });
169
312
  }
170
313
  }
171
314
 
172
315
  // 更新父节点的 _children
173
316
  if (!parentNode._children) parentNode._children = [];
174
-
175
317
  const insertPosition = options.at >= 0 ? options.at : parentNode._children.length;
176
318
  parentNode._children.splice(insertPosition, 0, { "__id__": newNodeIndex });
177
319
 
178
- // 保存场景
179
- saveScene(scenePath, data);
320
+ // 保存文件
321
+ saveScene(filePath, data);
180
322
 
181
- // 触发编辑器刷新(传入场景路径以重新打开场景)
182
- refreshEditor(scenePath);
183
-
184
- // 构建节点树(类似 tree 命令)
185
- function buildTree(nodeIndex, prefix = '', isLast = true, isRoot = true) {
186
- const node = data[nodeIndex];
187
- if (!node) return '';
188
-
189
- const nodeName = isRoot ? 'Root' : (node._name || '(unnamed)');
190
- const active = node._active !== false ? '●' : '○';
191
- const connector = isRoot ? '' : (isLast ? '└── ' : '├── ');
192
-
193
- let result = prefix + (isRoot ? '' : active + ' ') + nodeName + ' #' + nodeIndex;
194
-
195
- // 添加组件信息
196
- if (node._components && node._components.length > 0) {
197
- const comps = node._components.map(c => {
198
- const comp = data[c.__id__];
199
- if (!comp) return `? #${c.__id__}`;
200
- return `${comp.__type__.replace('cc.', '')} #${c.__id__}`;
201
- }).join(', ');
202
- result += ` (${comps})`;
203
- }
204
-
205
- result += '\n';
206
-
207
- // 处理子节点
208
- if (node._children && node._children.length > 0) {
209
- node._children.forEach((childRef, idx) => {
210
- const childIsLast = idx === node._children.length - 1;
211
- const childPrefix = prefix + (isRoot ? '' : (isLast ? ' ' : '│ '));
212
- result += buildTree(childRef.__id__, childPrefix, childIsLast, false);
213
- });
214
- }
215
-
216
- return result;
217
- }
323
+ // 触发编辑器刷新
324
+ refreshEditor(filePath);
218
325
 
219
- const treeStr = buildTree(1);
326
+ console.log(JSON.stringify({
327
+ success: true,
328
+ nodeIndex: newNodeIndex,
329
+ name: nodeName,
330
+ parent: parentRef,
331
+ type: prefab ? 'prefab' : 'scene'
332
+ }));
220
333
 
221
- console.log(treeStr);
222
334
  } catch (err) {
223
335
  console.log(JSON.stringify({ error: err.message }));
224
336
  }
225
337
  }
226
338
 
227
- module.exports = { run };
339
+ function parseColor(colorStr) {
340
+ if (!colorStr) return null;
341
+ let color = colorStr;
342
+ if (typeof color === 'string') {
343
+ if (color.startsWith('#')) color = color.slice(1);
344
+ if (color.length === 6) {
345
+ const r = parseInt(color.slice(0, 2), 16);
346
+ const g = parseInt(color.slice(2, 4), 16);
347
+ const b = parseInt(color.slice(4, 6), 16);
348
+ if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
349
+ return { "__type__": "cc.Color", r, g, b, a: 255 };
350
+ }
351
+ }
352
+ }
353
+ return null;
354
+ }
355
+
356
+ module.exports = { run };