cocos2d-cli 1.0.4 → 1.0.5

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.
package/bin/cocos-cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /**
2
+ /**
3
3
  * Cocos Creator CLI
4
4
  * Command-line tools for AI to read and manipulate Cocos Creator 2.4.x project scenes
5
5
  */
@@ -27,14 +27,14 @@ Cocos Creator CLI - 场景操作工具集
27
27
  cocos2.4 <command> [options]
28
28
 
29
29
  命令:
30
- tree <场景文件路径> 查看节点树
31
- get <场景文件路径> <节点> 获取节点信息
32
- set <场景文件路径> <节点> [选项] 修改节点属性
33
- add <场景文件路径> <父节点> <名称> 添加节点
34
- add-component <场景文件路径> <节点> <类型> 给节点添加组件
35
- remove <场景路径> <索引> 删除节点或组件
36
- delete <场景文件路径> <节点> 删除节点
37
- build <项目目录> 构建组件映射
30
+ tree <场景文件路径> 查看节点树(获取索引)
31
+ get <场景文件路径> <节点索引> 获取节点属性
32
+ set <场景文件路径> <节点索引> [选项] 修改节点属性
33
+ add <场景文件路径> <父节点索引> <名称> 添加节点到指定父节点
34
+ add-component <场景文件路径> <节点索引> <类型> 给节点添加组件
35
+ remove <场景路径> <索引> 删除节点或组件
36
+ delete <场景文件路径> <节点索引> 删除节点
37
+ build <项目目录> 构建组件映射
38
38
 
39
39
  选项:
40
40
  --name=<名称> 修改节点名称
@@ -50,19 +50,20 @@ Cocos Creator CLI - 场景操作工具集
50
50
  --rotation=<角度> 修改旋转角度
51
51
  --scaleX=<数值> 修改 X 缩放
52
52
  --scaleY=<数值> 修改 Y 缩放
53
- --type=sprite/label 添加节点时指定组件类型
54
- --at=<位置> 添加节点时指定插入位置
53
+ --type=sprite/label/button 添加节点时指定组件类型
54
+ --at=<索引> 添加节点时插入到子节点的指定位置(0=第一个)
55
55
 
56
56
  示例:
57
57
  cocos2.4 tree assets/main.fire
58
- cocos2.4 get assets/main.fire Canvas
59
- cocos2.4 set assets/main.fire Canvas/Player --x=100 --y=200
60
- cocos2.4 add assets/main.fire Canvas NewSprite --type=sprite --x=100
61
- cocos2.4 add-component assets/main.fire Canvas/Player sprite
62
- cocos2.4 delete assets/main.fire OldNode
58
+ cocos2.4 get assets/main.fire 5
59
+ cocos2.4 set assets/main.fire 8 --x=100 --y=200
60
+ cocos2.4 add assets/main.fire 5 NewSprite --type=sprite --x=100
61
+ cocos2.4 add assets/main.fire 5 FirstChild --at=0
62
+ cocos2.4 add-component assets/main.fire 8 sprite
63
+ cocos2.4 delete assets/main.fire 12
63
64
  cocos2.4 build ./my-project
64
65
 
65
- 版本: 1.0.2
66
+ 版本: 1.0.4
66
67
  `);
67
68
  }
68
69
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "name": "cocos2d-cli",
3
- "version": "1.0.4",
2
+ "name": "cocos2d-cli",
3
+ "version": "1.0.5",
4
4
  "description": "Command-line tools for AI to read and manipulate Cocos Creator 2.4.x project scenes",
5
- "main": "src/lib/index.js",
5
+ "main": "bin/cocos-cli.js",
6
6
  "bin": {
7
7
  "cocos2d-cli": "./bin/cocos-cli.js"
8
8
  },
@@ -12,9 +12,7 @@
12
12
  "data"
13
13
  ],
14
14
  "scripts": {
15
- "test": "bash test/run.sh",
16
- "test:structure": "node test/verify_structure.js",
17
- "test:cli": "bash test/run.sh"
15
+ "test": "echo \"No tests specified\" && exit 1"
18
16
  },
19
17
  "keywords": [
20
18
  "cocos",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * add 命令 - 添加节点
2
+ * add 命令 - 添加节点
3
3
  * 按照 Cocos Creator 的深度优先遍历顺序插入节点
4
4
  */
5
5
 
@@ -102,11 +102,17 @@ function run(args) {
102
102
  const data = loadScene(scenePath);
103
103
  const { indexMap } = buildMaps(data);
104
104
 
105
+ // 父节点必须使用数字索引
106
+ if (!/^\d+$/.test(parentRef)) {
107
+ console.log(JSON.stringify({ error: '父节点必须使用数字索引,请先用 tree 命令查看节点索引' }));
108
+ return;
109
+ }
110
+
105
111
  // 查找父节点
106
- const parentIndex = findNodeIndex(data, indexMap, parentRef);
112
+ const parentIndex = parseInt(parentRef);
107
113
 
108
- if (parentIndex === null || !data[parentIndex]) {
109
- console.log(JSON.stringify({ error: `找不到父节点: ${parentRef}` }));
114
+ if (parentIndex < 0 || parentIndex >= data.length || !data[parentIndex]) {
115
+ console.log(JSON.stringify({ error: `无效的节点索引: ${parentRef}` }));
110
116
  return;
111
117
  }
112
118
 
@@ -188,8 +194,6 @@ function run(args) {
188
194
 
189
195
  const nodeName = isRoot ? 'Root' : (node._name || '(unnamed)');
190
196
  const active = node._active !== false ? '●' : '○';
191
- const connector = isRoot ? '' : (isLast ? '└── ' : '├── ');
192
-
193
197
  let result = prefix + (isRoot ? '' : active + ' ') + nodeName + ' #' + nodeIndex;
194
198
 
195
199
  // 添加组件信息
@@ -1,27 +1,31 @@
1
1
  /**
2
- * delete 命令 - 删除节点
2
+ * delete 命令 - 删除节点
3
3
  */
4
4
 
5
- const { loadScene, saveScene, buildMaps, collectNodeAndChildren, rebuildReferences, findNodeIndex, refreshEditor } = require('../lib/fire-utils');
5
+ const { loadScene, saveScene, collectNodeAndChildren, rebuildReferences, refreshEditor, loadScriptMap, buildTree } = require('../lib/fire-utils');
6
6
 
7
7
  function run(args) {
8
8
  if (args.length < 2) {
9
- console.log(JSON.stringify({ error: '用法: cocos2.4 delete <场景文件路径> <节点索引|名称>' }));
9
+ console.log(JSON.stringify({ error: '用法: cocos2.4 delete <场景文件路径> <节点索引>' }));
10
10
  return;
11
11
  }
12
12
 
13
13
  const scenePath = args[0];
14
14
  const nodeRef = args[1];
15
15
 
16
+ // 节点必须使用数字索引
17
+ if (!/^\d+$/.test(nodeRef)) {
18
+ console.log(JSON.stringify({ error: '节点索引必须是数字,请先用 tree 命令查看节点索引' }));
19
+ return;
20
+ }
21
+
16
22
  try {
17
23
  const data = loadScene(scenePath);
18
- const { indexMap } = buildMaps(data);
19
24
 
20
- // 查找节点
21
- const nodeIndex = findNodeIndex(data, indexMap, nodeRef);
25
+ const nodeIndex = parseInt(nodeRef);
22
26
 
23
- if (nodeIndex === null || !data[nodeIndex]) {
24
- console.log(JSON.stringify({ error: `找不到节点: ${nodeRef}` }));
27
+ if (!data[nodeIndex]) {
28
+ console.log(JSON.stringify({ error: `无效的节点索引: ${nodeRef}` }));
25
29
  return;
26
30
  }
27
31
 
@@ -31,7 +35,6 @@ function run(args) {
31
35
  }
32
36
 
33
37
  const node = data[nodeIndex];
34
- const nodeName = node._name;
35
38
 
36
39
  // 收集所有需要删除的索引(节点 + 子节点 + 组件)
37
40
  const indicesToDelete = collectNodeAndChildren(data, nodeIndex);
@@ -57,14 +60,12 @@ function run(args) {
57
60
  // 保存场景
58
61
  saveScene(scenePath, data);
59
62
 
60
- // 触发编辑器刷新(传入场景路径以重新打开场景)
63
+ // 触发编辑器刷新
61
64
  refreshEditor(scenePath);
62
65
 
63
- console.log(JSON.stringify({
64
- success: true,
65
- message: `节点 "${nodeName}" 已删除`,
66
- deletedCount: indicesToDelete.size
67
- }, null, 2));
66
+ // 返回删除后的节点树
67
+ const scriptMap = loadScriptMap(scenePath);
68
+ console.log(buildTree(data, scriptMap, 1));
68
69
  } catch (err) {
69
70
  console.log(JSON.stringify({ error: err.message }));
70
71
  }
@@ -1,9 +1,126 @@
1
1
  /**
2
- * get 命令 - 获取节点信息
2
+ * get 命令 - 获取节点信息
3
+ * 返回与编辑器 Inspector 面板一致的所有属性
3
4
  */
4
5
 
5
6
  const { loadScene, buildMaps, findNodeIndex } = require('../lib/fire-utils');
6
7
 
8
+ /**
9
+ * 将 _color 对象转为 #RRGGBB 字符串
10
+ */
11
+ function colorToHex(color) {
12
+ if (!color) return '#ffffff';
13
+ const r = (color.r || 0).toString(16).padStart(2, '0');
14
+ const g = (color.g || 0).toString(16).padStart(2, '0');
15
+ const b = (color.b || 0).toString(16).padStart(2, '0');
16
+ return `#${r}${g}${b}`;
17
+ }
18
+
19
+ /**
20
+ * 提取组件的关键属性(对应 Inspector 面板显示)
21
+ */
22
+ function extractComponentProps(comp) {
23
+ if (!comp) return null;
24
+ const type = comp.__type__;
25
+
26
+ // 只在 disabled 时才输出 enabled 字段
27
+ const base = comp._enabled ? { type } : { type, enabled: false };
28
+
29
+ // 清理对象中的 __type__ 字段
30
+ const clean = (obj) => {
31
+ if (!obj || typeof obj !== 'object') return obj;
32
+ if (Array.isArray(obj)) return obj.map(clean);
33
+ const result = {};
34
+ for (const [k, v] of Object.entries(obj)) {
35
+ if (k === '__type__') continue;
36
+ result[k] = typeof v === 'object' ? clean(v) : v;
37
+ }
38
+ return result;
39
+ };
40
+
41
+ switch (type) {
42
+ case 'cc.Sprite':
43
+ return {
44
+ ...base,
45
+ spriteFrame: comp._spriteFrame?.__uuid__ || null,
46
+ sizeMode: ['CUSTOM', 'TRIMMED', 'RAW'][comp._sizeMode] || comp._sizeMode,
47
+ spriteType: ['SIMPLE', 'SLICED', 'TILED', 'FILLED', 'MESH'][comp._type] || comp._type,
48
+ trim: comp._isTrimmedMode
49
+ };
50
+ case 'cc.Label':
51
+ return {
52
+ ...base,
53
+ string: comp._string,
54
+ fontSize: comp._fontSize,
55
+ lineHeight: comp._lineHeight,
56
+ horizontalAlign: ['LEFT', 'CENTER', 'RIGHT'][comp._N$horizontalAlign] || comp._N$horizontalAlign,
57
+ verticalAlign: ['TOP', 'CENTER', 'BOTTOM'][comp._N$verticalAlign] || comp._N$verticalAlign,
58
+ overflow: ['NONE', 'CLAMP', 'SHRINK', 'RESIZE_HEIGHT'][comp._N$overflow] || comp._N$overflow,
59
+ fontFamily: comp._N$fontFamily,
60
+ enableWrapText: comp._enableWrapText
61
+ };
62
+ case 'cc.Button':
63
+ return {
64
+ ...base,
65
+ interactable: comp._N$interactable,
66
+ transition: ['NONE', 'COLOR', 'SPRITE', 'SCALE'][comp._N$transition] || comp._N$transition,
67
+ zoomScale: comp.zoomScale,
68
+ duration: comp.duration
69
+ };
70
+ case 'cc.Widget':
71
+ return {
72
+ ...base,
73
+ alignMode: ['ONCE', 'ON_WINDOW_RESIZE', 'ALWAYS'][comp.alignMode] || comp.alignMode,
74
+ left: comp._left,
75
+ right: comp._right,
76
+ top: comp._top,
77
+ bottom: comp._bottom
78
+ };
79
+ case 'cc.Layout':
80
+ return {
81
+ ...base,
82
+ layoutType: ['NONE', 'HORIZONTAL', 'VERTICAL', 'GRID'][comp._N$layoutType] || comp._N$layoutType,
83
+ spacingX: comp._N$spacingX,
84
+ spacingY: comp._N$spacingY,
85
+ paddingLeft: comp._N$paddingLeft,
86
+ paddingRight: comp._N$paddingRight,
87
+ paddingTop: comp._N$paddingTop,
88
+ paddingBottom: comp._N$paddingBottom
89
+ };
90
+ case 'cc.Canvas':
91
+ return {
92
+ ...base,
93
+ designResolution: clean(comp._designResolution),
94
+ fitWidth: comp._fitWidth,
95
+ fitHeight: comp._fitHeight
96
+ };
97
+ case 'cc.Camera':
98
+ return {
99
+ ...base,
100
+ depth: comp._depth,
101
+ zoomRatio: comp._zoomRatio,
102
+ ortho: comp._ortho,
103
+ cullingMask: comp._cullingMask
104
+ };
105
+ case 'cc.ParticleSystem':
106
+ return {
107
+ ...base,
108
+ playOnLoad: comp.playOnLoad,
109
+ totalParticles: comp.totalParticles,
110
+ duration: comp.duration
111
+ };
112
+ default:
113
+ // 未知组件:返回去掉内部字段的原始属性
114
+ const result = { ...base };
115
+ for (const key of Object.keys(comp)) {
116
+ if (!key.startsWith('_') && key !== '__type__') {
117
+ result[key] = comp[key];
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ }
123
+
7
124
  function run(args) {
8
125
  if (args.length < 2) {
9
126
  console.log(JSON.stringify({ error: '用法: cocos2.4 get <场景文件路径> <节点索引|名称>' }));
@@ -30,21 +147,41 @@ function run(args) {
30
147
  console.log(JSON.stringify({ error: `节点不存在: ${nodeRef}` }));
31
148
  return;
32
149
  }
33
-
34
- // 返回节点信息
150
+
151
+ // 从 _trs 数组解析变换属性
152
+ // 格式: [x, y, z, qx, qy, qz, qw, scaleX, scaleY, scaleZ]
153
+ const trs = node._trs?.array || [0,0,0, 0,0,0,1, 1,1,1];
154
+
155
+ // 组件详细信息
156
+ const components = (node._components || []).map(ref => {
157
+ const comp = data[ref.__id__];
158
+ return extractComponentProps(comp);
159
+ });
160
+
161
+ // 子节点名称
162
+ const children = (node._children || []).map(ref => data[ref.__id__]?._name || '(unknown)');
163
+
35
164
  const info = indexMap[idx] || {};
36
- console.log(JSON.stringify({
37
- success: true,
38
- index: idx,
39
- ...info,
40
- node: {
41
- active: node._active,
42
- position: node._trs?.array?.slice(0, 2) || [0, 0],
43
- size: node._contentSize || { width: 0, height: 0 },
44
- children: node._children?.map(c => c.__id__) || [],
45
- components: node._components?.map(c => c.__id__) || []
46
- }
47
- }, null, 2));
165
+
166
+ // 精简 JSON 输出
167
+ const result = {
168
+ name: info.name,
169
+ active: node._active,
170
+ position: { x: trs[0], y: trs[1] },
171
+ rotation: node._eulerAngles?.z ?? 0,
172
+ scale: { x: trs[7], y: trs[8] },
173
+ anchor: { x: node._anchorPoint?.x ?? 0.5, y: node._anchorPoint?.y ?? 0.5 },
174
+ size: { w: node._contentSize?.width ?? 0, h: node._contentSize?.height ?? 0 },
175
+ color: colorToHex(node._color),
176
+ opacity: node._opacity ?? 255,
177
+ group: node._groupIndex ?? 0
178
+ };
179
+
180
+ if (children.length > 0) result.children = children;
181
+ if (components.length > 0) result.components = components;
182
+
183
+ console.log(JSON.stringify(result));
184
+
48
185
  } catch (err) {
49
186
  console.log(JSON.stringify({ error: err.message }));
50
187
  }
@@ -2,96 +2,31 @@
2
2
  * remove 命令 - 删除节点或组件
3
3
  */
4
4
 
5
- const { loadScene, saveScene, buildMaps, collectNodeAndChildren, rebuildReferences, refreshEditor, installPlugin } = require('../lib/fire-utils');
6
- const fs = require('fs');
7
- const path = require('path');
8
-
9
- // 加载脚本映射
10
- function loadScriptMap(scenePath) {
11
- const projectPath = path.dirname(path.dirname(scenePath));
12
- const mapPath = path.join(projectPath, 'data', 'script_map.json');
13
- try {
14
- if (fs.existsSync(mapPath)) {
15
- return JSON.parse(fs.readFileSync(mapPath, 'utf-8'));
16
- }
17
- } catch (e) {}
18
- return {};
19
- }
20
-
21
- // 构建树形结构
22
- function buildTree(data, scriptMap, nodeIndex, prefix = '', isLast = true, isRoot = true) {
23
- const node = data[nodeIndex];
24
- if (!node) return '';
25
-
26
- const nodeName = isRoot ? 'Root' : (node._name || '(unnamed)');
27
- const active = node._active !== false ? '●' : '○';
28
- const uuidRegex = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
29
-
30
- let result = prefix + (isRoot ? '' : active + ' ') + nodeName + ' #' + nodeIndex;
31
-
32
- // 添加组件信息
33
- if (node._components && node._components.length > 0) {
34
- const comps = node._components.map(c => {
35
- const comp = data[c.__id__];
36
- if (!comp) return `? #${c.__id__}`;
37
- const typeName = comp.__type__;
38
- let displayName;
39
- if (uuidRegex.test(typeName)) {
40
- const scriptInfo = scriptMap[typeName];
41
- if (scriptInfo && scriptInfo.name) {
42
- displayName = scriptInfo.name;
43
- } else {
44
- displayName = '⚠️MissingScript';
45
- }
46
- } else if (typeName === 'MissingScript') {
47
- displayName = '⚠️MissingScript';
48
- } else {
49
- displayName = typeName.replace('cc.', '');
50
- }
51
- return `${displayName} #${c.__id__}`;
52
- }).join(', ');
53
- result += ` (${comps})`;
54
- }
55
-
56
- result += '\n';
57
-
58
- // 处理子节点
59
- if (node._children && node._children.length > 0) {
60
- node._children.forEach((childRef, idx) => {
61
- const childIsLast = idx === node._children.length - 1;
62
- const childPrefix = prefix + (isRoot ? '' : (isLast ? ' ' : '│ '));
63
- result += buildTree(data, scriptMap, childRef.__id__, childPrefix, childIsLast, false);
64
- });
65
- }
66
-
67
- return result;
68
- }
5
+ const { loadScene, saveScene, collectNodeAndChildren, rebuildReferences, refreshEditor, loadScriptMap, buildTree } = require('../lib/fire-utils');
69
6
 
70
7
  function run(args) {
71
8
  if (args.length < 2) {
72
- console.log('用法: cocos2.4 remove <场景文件路径> <索引>');
9
+ console.log(JSON.stringify({ error: '用法: cocos2.4 remove <场景文件路径> <索引>' }));
73
10
  return;
74
11
  }
75
12
 
76
13
  const scenePath = args[0];
77
14
 
78
- // 安装 CLI Helper 插件(如果不存在)
79
- installPlugin(scenePath);
80
-
81
- const index = parseInt(args[1]);
82
-
83
- if (isNaN(index)) {
84
- console.log('错误: 索引必须是数字');
15
+ // 索引必须是数字
16
+ if (!/^\d+$/.test(args[1])) {
17
+ console.log(JSON.stringify({ error: '索引必须是数字,请先用 tree 命令查看节点索引' }));
85
18
  return;
86
19
  }
87
20
 
21
+ const index = parseInt(args[1]);
22
+
88
23
  try {
89
24
  let data = loadScene(scenePath);
90
25
  const scriptMap = loadScriptMap(scenePath);
91
26
 
92
27
  // 检查索引是否存在
93
28
  if (!data[index]) {
94
- console.log(`错误: 索引 ${index} 不存在`);
29
+ console.log(JSON.stringify({ error: `索引 ${index} 不存在` }));
95
30
  return;
96
31
  }
97
32
 
@@ -103,7 +38,7 @@ function run(args) {
103
38
  const isComponent = item.node !== undefined;
104
39
 
105
40
  if (isNode && index <= 1) {
106
- console.log('错误: 不能删除根节点');
41
+ console.log(JSON.stringify({ error: '不能删除根节点' }));
107
42
  return;
108
43
  }
109
44
 
@@ -156,8 +91,8 @@ function run(args) {
156
91
  console.log(buildTree(data, scriptMap, 1));
157
92
 
158
93
  } catch (err) {
159
- console.log(`错误: ${err.message}`);
94
+ console.log(JSON.stringify({ error: err.message }));
160
95
  }
161
96
  }
162
97
 
163
- module.exports = { run };
98
+ module.exports = { run };
@@ -1,9 +1,122 @@
1
1
  /**
2
- * set 命令 - 修改节点属性
2
+ * set 命令 - 修改节点属性
3
+ * 修改成功后返回节点最终状态(与 get 命令格式一致)
3
4
  */
4
5
 
5
6
  const { loadScene, saveScene, buildMaps, findNodeIndex, refreshEditor } = require('../lib/fire-utils');
6
7
 
8
+ /**
9
+ * 将 _color 对象转为 #RRGGBB 字符串
10
+ */
11
+ function colorToHex(color) {
12
+ if (!color) return '#ffffff';
13
+ const r = (color.r || 0).toString(16).padStart(2, '0');
14
+ const g = (color.g || 0).toString(16).padStart(2, '0');
15
+ const b = (color.b || 0).toString(16).padStart(2, '0');
16
+ return `#${r}${g}${b}`;
17
+ }
18
+
19
+ /**
20
+ * 提取组件的关键属性(对应 Inspector 面板显示)
21
+ */
22
+ function extractComponentProps(comp) {
23
+ if (!comp) return null;
24
+ const type = comp.__type__;
25
+ const base = comp._enabled ? { type } : { type, enabled: false };
26
+
27
+ const clean = (obj) => {
28
+ if (!obj || typeof obj !== 'object') return obj;
29
+ if (Array.isArray(obj)) return obj.map(clean);
30
+ const result = {};
31
+ for (const [k, v] of Object.entries(obj)) {
32
+ if (k === '__type__') continue;
33
+ result[k] = typeof v === 'object' ? clean(v) : v;
34
+ }
35
+ return result;
36
+ };
37
+
38
+ switch (type) {
39
+ case 'cc.Sprite':
40
+ return {
41
+ ...base,
42
+ spriteFrame: comp._spriteFrame?.__uuid__ || null,
43
+ sizeMode: ['CUSTOM', 'TRIMMED', 'RAW'][comp._sizeMode] || comp._sizeMode,
44
+ spriteType: ['SIMPLE', 'SLICED', 'TILED', 'FILLED', 'MESH'][comp._type] || comp._type,
45
+ trim: comp._isTrimmedMode
46
+ };
47
+ case 'cc.Label':
48
+ return {
49
+ ...base,
50
+ string: comp._string,
51
+ fontSize: comp._fontSize,
52
+ lineHeight: comp._lineHeight,
53
+ horizontalAlign: ['LEFT', 'CENTER', 'RIGHT'][comp._N$horizontalAlign] || comp._N$horizontalAlign,
54
+ verticalAlign: ['TOP', 'CENTER', 'BOTTOM'][comp._N$verticalAlign] || comp._N$verticalAlign,
55
+ overflow: ['NONE', 'CLAMP', 'SHRINK', 'RESIZE_HEIGHT'][comp._N$overflow] || comp._N$overflow,
56
+ fontFamily: comp._N$fontFamily,
57
+ enableWrapText: comp._enableWrapText
58
+ };
59
+ case 'cc.Button':
60
+ return {
61
+ ...base,
62
+ interactable: comp._N$interactable,
63
+ transition: ['NONE', 'COLOR', 'SPRITE', 'SCALE'][comp._N$transition] || comp._N$transition,
64
+ zoomScale: comp.zoomScale,
65
+ duration: comp.duration
66
+ };
67
+ case 'cc.Widget':
68
+ return {
69
+ ...base,
70
+ alignMode: ['ONCE', 'ON_WINDOW_RESIZE', 'ALWAYS'][comp.alignMode] || comp.alignMode,
71
+ left: comp._left,
72
+ right: comp._right,
73
+ top: comp._top,
74
+ bottom: comp._bottom
75
+ };
76
+ case 'cc.Layout':
77
+ return {
78
+ ...base,
79
+ layoutType: ['NONE', 'HORIZONTAL', 'VERTICAL', 'GRID'][comp._N$layoutType] || comp._N$layoutType,
80
+ spacingX: comp._N$spacingX,
81
+ spacingY: comp._N$spacingY,
82
+ paddingLeft: comp._N$paddingLeft,
83
+ paddingRight: comp._N$paddingRight,
84
+ paddingTop: comp._N$paddingTop,
85
+ paddingBottom: comp._N$paddingBottom
86
+ };
87
+ case 'cc.Canvas':
88
+ return {
89
+ ...base,
90
+ designResolution: clean(comp._designResolution),
91
+ fitWidth: comp._fitWidth,
92
+ fitHeight: comp._fitHeight
93
+ };
94
+ case 'cc.Camera':
95
+ return {
96
+ ...base,
97
+ depth: comp._depth,
98
+ zoomRatio: comp._zoomRatio,
99
+ ortho: comp._ortho,
100
+ cullingMask: comp._cullingMask
101
+ };
102
+ case 'cc.ParticleSystem':
103
+ return {
104
+ ...base,
105
+ playOnLoad: comp.playOnLoad,
106
+ totalParticles: comp.totalParticles,
107
+ duration: comp.duration
108
+ };
109
+ default:
110
+ const result = { ...base };
111
+ for (const key of Object.keys(comp)) {
112
+ if (!key.startsWith('_') && key !== '__type__') {
113
+ result[key] = comp[key];
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ }
119
+
7
120
  // 解析颜色
8
121
  function parseColor(colorStr) {
9
122
  if (!colorStr) return null;
@@ -22,6 +135,33 @@ function parseColor(colorStr) {
22
135
  return null;
23
136
  }
24
137
 
138
+ /**
139
+ * 获取节点的完整状态(与 get 命令一致)
140
+ */
141
+ function getNodeState(data, node, nodeIndex) {
142
+ const trs = node._trs?.array || [0,0,0, 0,0,0,1, 1,1,1];
143
+ const components = (node._components || []).map(ref => extractComponentProps(data[ref.__id__]));
144
+ const children = (node._children || []).map(ref => data[ref.__id__]?._name || '(unknown)');
145
+
146
+ const result = {
147
+ name: node._name,
148
+ active: node._active,
149
+ position: { x: trs[0], y: trs[1] },
150
+ rotation: node._eulerAngles?.z ?? 0,
151
+ scale: { x: trs[7], y: trs[8] },
152
+ anchor: { x: node._anchorPoint?.x ?? 0.5, y: node._anchorPoint?.y ?? 0.5 },
153
+ size: { w: node._contentSize?.width ?? 0, h: node._contentSize?.height ?? 0 },
154
+ color: colorToHex(node._color),
155
+ opacity: node._opacity ?? 255,
156
+ group: node._groupIndex ?? 0
157
+ };
158
+
159
+ if (children.length > 0) result.children = children;
160
+ if (components.length > 0) result.components = components;
161
+
162
+ return result;
163
+ }
164
+
25
165
  function run(args) {
26
166
  if (args.length < 2) {
27
167
  console.log(JSON.stringify({ error: '用法: cocos2.4 set <场景文件路径> <节点索引|名称> [选项]' }));
@@ -53,20 +193,15 @@ function run(args) {
53
193
  }
54
194
 
55
195
  const node = data[nodeIndex];
56
- const changes = {};
57
196
 
58
197
  // 修改名称
59
198
  if (options.name !== undefined) {
60
- const oldName = node._name;
61
199
  node._name = options.name;
62
- changes.name = { from: oldName, to: options.name };
63
200
  }
64
201
 
65
202
  // 修改激活状态
66
203
  if (options.active !== undefined) {
67
- const oldActive = node._active;
68
204
  node._active = options.active !== 'false';
69
- changes.active = { from: oldActive, to: node._active };
70
205
  }
71
206
 
72
207
  // 修改位置
@@ -74,11 +209,8 @@ function run(args) {
74
209
  if (!node._trs) {
75
210
  node._trs = { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] };
76
211
  }
77
- const oldX = node._trs.array[0];
78
- const oldY = node._trs.array[1];
79
212
  if (options.x !== undefined) node._trs.array[0] = parseFloat(options.x);
80
213
  if (options.y !== undefined) node._trs.array[1] = parseFloat(options.y);
81
- changes.position = { from: [oldX, oldY], to: [node._trs.array[0], node._trs.array[1]] };
82
214
  }
83
215
 
84
216
  // 修改大小
@@ -86,11 +218,8 @@ function run(args) {
86
218
  if (!node._contentSize) {
87
219
  node._contentSize = { "__type__": "cc.Size", width: 0, height: 0 };
88
220
  }
89
- const oldW = node._contentSize.width;
90
- const oldH = node._contentSize.height;
91
221
  if (options.width !== undefined) node._contentSize.width = parseFloat(options.width);
92
222
  if (options.height !== undefined) node._contentSize.height = parseFloat(options.height);
93
- changes.size = { from: { width: oldW, height: oldH }, to: { width: node._contentSize.width, height: node._contentSize.height } };
94
223
  }
95
224
 
96
225
  // 修改锚点
@@ -98,18 +227,13 @@ function run(args) {
98
227
  if (!node._anchorPoint) {
99
228
  node._anchorPoint = { "__type__": "cc.Vec2", x: 0.5, y: 0.5 };
100
229
  }
101
- const oldX = node._anchorPoint.x;
102
- const oldY = node._anchorPoint.y;
103
230
  if (options.anchorX !== undefined) node._anchorPoint.x = parseFloat(options.anchorX);
104
231
  if (options.anchorY !== undefined) node._anchorPoint.y = parseFloat(options.anchorY);
105
- changes.anchor = { from: [oldX, oldY], to: [node._anchorPoint.x, node._anchorPoint.y] };
106
232
  }
107
233
 
108
234
  // 修改透明度
109
235
  if (options.opacity !== undefined) {
110
- const oldOpacity = node._opacity;
111
236
  node._opacity = Math.max(0, Math.min(255, parseInt(options.opacity)));
112
- changes.opacity = { from: oldOpacity, to: node._opacity };
113
237
  }
114
238
 
115
239
  // 修改颜色
@@ -117,7 +241,6 @@ function run(args) {
117
241
  const color = parseColor(options.color);
118
242
  if (color) {
119
243
  node._color = color;
120
- changes.color = { to: options.color };
121
244
  }
122
245
  }
123
246
 
@@ -126,9 +249,7 @@ function run(args) {
126
249
  if (!node._eulerAngles) {
127
250
  node._eulerAngles = { "__type__": "cc.Vec3", x: 0, y: 0, z: 0 };
128
251
  }
129
- const oldRotation = node._eulerAngles.z;
130
252
  node._eulerAngles.z = parseFloat(options.rotation);
131
- changes.rotation = { from: oldRotation, to: node._eulerAngles.z };
132
253
  }
133
254
 
134
255
  // 修改缩放
@@ -136,25 +257,18 @@ function run(args) {
136
257
  if (!node._trs) {
137
258
  node._trs = { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] };
138
259
  }
139
- const oldScaleX = node._trs.array[7];
140
- const oldScaleY = node._trs.array[8];
141
260
  if (options.scaleX !== undefined) node._trs.array[7] = parseFloat(options.scaleX);
142
261
  if (options.scaleY !== undefined) node._trs.array[8] = parseFloat(options.scaleY);
143
- changes.scale = { from: [oldScaleX, oldScaleY], to: [node._trs.array[7], node._trs.array[8]] };
144
262
  }
145
263
 
146
264
  // 保存场景
147
265
  saveScene(scenePath, data);
148
266
 
149
- // 触发编辑器刷新(传入场景路径以重新打开场景)
267
+ // 触发编辑器刷新
150
268
  refreshEditor(scenePath);
151
269
 
152
- console.log(JSON.stringify({
153
- success: true,
154
- index: nodeIndex,
155
- name: node._name,
156
- changes
157
- }, null, 2));
270
+ // 返回节点最终状态(与 get 命令格式一致)
271
+ console.log(JSON.stringify(getNodeState(data, node, nodeIndex)));
158
272
  } catch (err) {
159
273
  console.log(JSON.stringify({ error: err.message }));
160
274
  }
@@ -1,22 +1,8 @@
1
1
  /**
2
- * tree 命令 - 查看节点树
2
+ * tree 命令 - 查看节点树
3
3
  */
4
4
 
5
- const { loadScene, buildMaps } = require('../lib/fire-utils');
6
- const fs = require('fs');
7
- const path = require('path');
8
-
9
- // 加载脚本映射
10
- function loadScriptMap(scenePath) {
11
- const projectPath = path.dirname(path.dirname(scenePath));
12
- const mapPath = path.join(projectPath, 'data', 'script_map.json');
13
- try {
14
- if (fs.existsSync(mapPath)) {
15
- return JSON.parse(fs.readFileSync(mapPath, 'utf-8'));
16
- }
17
- } catch (e) {}
18
- return {};
19
- }
5
+ const { loadScene, loadScriptMap, buildTree } = require('../lib/fire-utils');
20
6
 
21
7
  function run(args) {
22
8
  const scenePath = args[0];
@@ -28,64 +14,10 @@ function run(args) {
28
14
 
29
15
  try {
30
16
  const data = loadScene(scenePath);
31
- const { indexMap } = buildMaps(data);
32
17
  const scriptMap = loadScriptMap(scenePath);
33
- const uuidRegex = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
34
-
35
- // 构建树形结构输出
36
- function buildTree(nodeIndex, prefix = '', isLast = true, isRoot = true) {
37
- const node = data[nodeIndex];
38
- if (!node) return '';
39
-
40
- const nodeName = isRoot ? 'Root' : (node._name || '(unnamed)');
41
- const active = node._active !== false ? '●' : '○';
42
- const connector = isRoot ? '' : (isLast ? '└── ' : '├── ');
43
-
44
- let result = prefix + (isRoot ? '' : active + ' ') + nodeName + ' #' + nodeIndex;
45
-
46
- // 添加组件信息
47
- if (node._components && node._components.length > 0) {
48
- const comps = node._components.map(c => {
49
- const comp = data[c.__id__];
50
- if (!comp) return `? #${c.__id__}`;
51
- const typeName = comp.__type__;
52
- let displayName;
53
- // 如果是 UUID,尝试从 scriptMap 查找类名
54
- if (uuidRegex.test(typeName)) {
55
- const scriptInfo = scriptMap[typeName];
56
- if (scriptInfo && scriptInfo.name) {
57
- displayName = scriptInfo.name;
58
- } else {
59
- displayName = '⚠️MissingScript';
60
- }
61
- } else if (typeName === 'MissingScript') {
62
- displayName = '⚠️MissingScript';
63
- } else {
64
- displayName = typeName.replace('cc.', '');
65
- }
66
- return `${displayName} #${c.__id__}`;
67
- }).join(', ');
68
- result += ` (${comps})`;
69
- }
70
-
71
- result += '\n';
72
-
73
- // 处理子节点
74
- if (node._children && node._children.length > 0) {
75
- node._children.forEach((childRef, idx) => {
76
- const childIsLast = idx === node._children.length - 1;
77
- const childPrefix = prefix + (isRoot ? '' : (isLast ? ' ' : '│ '));
78
- result += buildTree(childRef.__id__, childPrefix, childIsLast, false);
79
- });
80
- }
81
-
82
- return result;
83
- }
84
-
85
- const treeStr = buildTree(1);
86
- console.log(treeStr);
18
+ console.log(buildTree(data, scriptMap, 1));
87
19
  } catch (err) {
88
- console.log(`Error: ${err.message}`);
20
+ console.log(JSON.stringify({ error: err.message }));
89
21
  }
90
22
  }
91
23
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Fire 文件工具模块
2
+ * Fire 文件工具模块
3
3
  * 提供直接读取和操作 .fire 场景文件的功能
4
4
  */
5
5
 
@@ -398,6 +398,67 @@ function installPlugin(scenePath) {
398
398
  }
399
399
  }
400
400
 
401
+ /**
402
+ * 加载脚本映射(用于显示自定义脚本组件名称)
403
+ */
404
+ function loadScriptMap(scenePath) {
405
+ const projectPath = path.dirname(path.dirname(scenePath));
406
+ const mapPath = path.join(projectPath, 'data', 'script_map.json');
407
+ try {
408
+ if (fs.existsSync(mapPath)) {
409
+ return JSON.parse(fs.readFileSync(mapPath, 'utf-8'));
410
+ }
411
+ } catch (e) {}
412
+ return {};
413
+ }
414
+
415
+ /**
416
+ * 构建节点树输出
417
+ */
418
+ function buildTree(data, scriptMap, nodeIndex, prefix = '', isLast = true, isRoot = true) {
419
+ const node = data[nodeIndex];
420
+ if (!node) return '';
421
+
422
+ const nodeName = isRoot ? 'Root' : (node._name || '(unnamed)');
423
+ const active = node._active !== false ? '●' : '○';
424
+ const uuidRegex = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
425
+
426
+ let result = prefix + (isRoot ? '' : active + ' ') + nodeName + ' #' + nodeIndex;
427
+
428
+ // 添加组件信息
429
+ if (node._components && node._components.length > 0) {
430
+ const comps = node._components.map(c => {
431
+ const comp = data[c.__id__];
432
+ if (!comp) return `? #${c.__id__}`;
433
+ const typeName = comp.__type__;
434
+ let displayName;
435
+ if (uuidRegex.test(typeName)) {
436
+ const scriptInfo = scriptMap[typeName];
437
+ displayName = (scriptInfo && scriptInfo.name) ? scriptInfo.name : '⚠️MissingScript';
438
+ } else if (typeName === 'MissingScript') {
439
+ displayName = '⚠️MissingScript';
440
+ } else {
441
+ displayName = typeName.replace('cc.', '');
442
+ }
443
+ return `${displayName} #${c.__id__}`;
444
+ }).join(', ');
445
+ result += ` (${comps})`;
446
+ }
447
+
448
+ result += '\n';
449
+
450
+ // 处理子节点
451
+ if (node._children && node._children.length > 0) {
452
+ node._children.forEach((childRef, idx) => {
453
+ const childIsLast = idx === node._children.length - 1;
454
+ const childPrefix = prefix + (isRoot ? '' : (isLast ? ' ' : '│ '));
455
+ result += buildTree(data, scriptMap, childRef.__id__, childPrefix, childIsLast, false);
456
+ });
457
+ }
458
+
459
+ return result;
460
+ }
461
+
401
462
  module.exports = {
402
463
  loadScene,
403
464
  saveScene,
@@ -408,5 +469,7 @@ module.exports = {
408
469
  reorderArrayToMatchChildren,
409
470
  refreshEditor,
410
471
  installPlugin,
411
- checkPluginStatus
472
+ checkPluginStatus,
473
+ loadScriptMap,
474
+ buildTree
412
475
  };