cocos2d-cli 1.0.5 → 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,20 +121,21 @@ 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
134
  if (!/^\d+$/.test(parentRef)) {
107
135
  console.log(JSON.stringify({ error: '父节点必须使用数字索引,请先用 tree 命令查看节点索引' }));
108
136
  return;
109
137
  }
110
138
 
111
- // 查找父节点
112
139
  const parentIndex = parseInt(parentRef);
113
140
 
114
141
  if (parentIndex < 0 || parentIndex >= data.length || !data[parentIndex]) {
@@ -117,115 +144,213 @@ function run(args) {
117
144
  }
118
145
 
119
146
  const parentNode = data[parentIndex];
147
+ const isRootChild = prefab && parentIndex === 1;
120
148
 
121
- // 确定插入位置
122
- // 按照 _children 顺序,找到应该插入的位置
149
+ // 确定插入位置:紧跟父节点之后(在父节点的子节点/组件/PrefabInfo之前)
123
150
  let insertIndex;
151
+
124
152
  if (!parentNode._children || parentNode._children.length === 0) {
125
- // 父节点没有子节点,新节点紧跟父节点之后
153
+ // 父节点还没有子节点,插入到父节点之后
126
154
  insertIndex = parentIndex + 1;
127
155
  } else {
128
- // 根据 --at 参数确定插入位置
156
+ // 父节点已有子节点,根据 --at 参数确定位置
129
157
  const targetPosition = options.at >= 0 ? options.at : parentNode._children.length;
130
158
 
131
159
  if (targetPosition === 0) {
132
- // 插入到第一个子节点位置,紧跟父节点之后
133
- insertIndex = parentIndex + 1;
160
+ // 插入到第一个子节点之前
161
+ insertIndex = parentNode._children[0].__id__;
134
162
  } else if (targetPosition >= parentNode._children.length) {
135
- // 插入到最后,在所有现有子节点之后
163
+ // 插入到最后一个子节点之后
136
164
  const lastChildRef = parentNode._children[parentNode._children.length - 1];
137
165
  insertIndex = getSubtreeEndIndex(data, lastChildRef.__id__) + 1;
138
166
  } else {
139
- // 插入到中间某个位置
140
- const beforeChildRef = parentNode._children[targetPosition - 1];
141
- insertIndex = getSubtreeEndIndex(data, beforeChildRef.__id__) + 1;
167
+ // 插入到中间
168
+ insertIndex = parentNode._children[targetPosition].__id__;
142
169
  }
143
170
  }
144
171
 
145
- // 创建新节点
146
- const newNode = createNodeData(nodeName, parentIndex, options);
147
-
148
- // 在正确位置插入新节点
149
- data.splice(insertIndex, 0, newNode);
150
-
151
- // 重建索引引用(因为插入了新元素,后续索引都+1)
152
- const insertIndexMap = rebuildReferencesForInsert(data, insertIndex);
172
+ let newNodeIndex;
153
173
 
154
- // 新节点的实际索引
155
- const newNodeIndex = insertIndex;
156
-
157
- // 更新新节点的 _parent 引用(使用映射后的父节点索引)
158
- const newParentIndex = insertIndexMap[parentIndex] !== undefined ? insertIndexMap[parentIndex] : parentIndex;
159
- newNode._parent = { "__id__": newParentIndex };
160
-
161
- // 添加组件(如果有)
162
- let componentIndex = -1;
163
- if (options.type) {
164
- 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
+ // 设置引用
165
257
  if (compData) {
166
- // 组件应该放在新节点子树之后(这里新节点没有子节点,所以紧跟节点后)
167
- const compInsertIndex = newNodeIndex + 1;
168
- data.splice(compInsertIndex, 0, compData);
169
-
170
- // 再次重建引用
171
- rebuildReferencesForInsert(data, compInsertIndex);
172
-
173
- componentIndex = compInsertIndex;
174
- newNode._components.push({ "__id__": componentIndex });
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
+
309
+ if (compData) {
310
+ compData.node = { "__id__": newNodeIndex };
311
+ newNode._components.push({ "__id__": newNodeIndex + 1 });
175
312
  }
176
313
  }
177
314
 
178
315
  // 更新父节点的 _children
179
316
  if (!parentNode._children) parentNode._children = [];
180
-
181
317
  const insertPosition = options.at >= 0 ? options.at : parentNode._children.length;
182
318
  parentNode._children.splice(insertPosition, 0, { "__id__": newNodeIndex });
183
319
 
184
- // 保存场景
185
- saveScene(scenePath, data);
320
+ // 保存文件
321
+ saveScene(filePath, data);
186
322
 
187
- // 触发编辑器刷新(传入场景路径以重新打开场景)
188
- refreshEditor(scenePath);
323
+ // 触发编辑器刷新
324
+ refreshEditor(filePath);
189
325
 
190
- // 构建节点树(类似 tree 命令)
191
- function buildTree(nodeIndex, prefix = '', isLast = true, isRoot = true) {
192
- const node = data[nodeIndex];
193
- if (!node) return '';
194
-
195
- const nodeName = isRoot ? 'Root' : (node._name || '(unnamed)');
196
- const active = node._active !== false ? '●' : '○';
197
- let result = prefix + (isRoot ? '' : active + ' ') + nodeName + ' #' + nodeIndex;
198
-
199
- // 添加组件信息
200
- if (node._components && node._components.length > 0) {
201
- const comps = node._components.map(c => {
202
- const comp = data[c.__id__];
203
- if (!comp) return `? #${c.__id__}`;
204
- return `${comp.__type__.replace('cc.', '')} #${c.__id__}`;
205
- }).join(', ');
206
- result += ` (${comps})`;
207
- }
208
-
209
- result += '\n';
210
-
211
- // 处理子节点
212
- if (node._children && node._children.length > 0) {
213
- node._children.forEach((childRef, idx) => {
214
- const childIsLast = idx === node._children.length - 1;
215
- const childPrefix = prefix + (isRoot ? '' : (isLast ? ' ' : '│ '));
216
- result += buildTree(childRef.__id__, childPrefix, childIsLast, false);
217
- });
218
- }
219
-
220
- return result;
221
- }
326
+ console.log(JSON.stringify({
327
+ success: true,
328
+ nodeIndex: newNodeIndex,
329
+ name: nodeName,
330
+ parent: parentRef,
331
+ type: prefab ? 'prefab' : 'scene'
332
+ }));
222
333
 
223
- const treeStr = buildTree(1);
224
-
225
- console.log(treeStr);
226
334
  } catch (err) {
227
335
  console.log(JSON.stringify({ error: err.message }));
228
336
  }
229
337
  }
230
338
 
231
- 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 };