cocos2d-cli 1.1.2 → 1.4.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,97 +1,161 @@
1
1
  /**
2
- * remove 命令 - 删除节点或组件
2
+ * remove 命令 - 统一的删除命令
3
+ * 支持删除节点和组件
3
4
  */
4
5
 
5
- const { loadScene, saveScene, collectNodeAndChildren, rebuildReferences, refreshEditor, loadScriptMap, buildTree } = require('../lib/fire-utils');
6
+ const { loadScene, saveScene, rebuildReferences, refreshEditor, loadScriptMap } = require('../lib/fire-utils');
7
+ const { outputError, outputSuccess } = require('../lib/utils');
8
+ const { buildTree, collectNodeAndChildren, detectItemType } = require('../lib/node-utils');
9
+
10
+ /**
11
+ * 删除组件
12
+ */
13
+ function removeComponent(data, compIndex) {
14
+ const compData = data[compIndex];
15
+ if (!compData) {
16
+ return { error: `组件索引 ${compIndex} 不存在` };
17
+ }
18
+
19
+ const compType = compData.__type__;
20
+ const nodeId = compData.node?.__id__;
21
+
22
+ if (nodeId === undefined) {
23
+ return { error: `索引 ${compIndex} 不是组件` };
24
+ }
25
+
26
+ const node = data[nodeId];
27
+ if (!node) {
28
+ return { error: `组件关联的节点 ${nodeId} 不存在` };
29
+ }
30
+
31
+ const nodeName = node._name || '(unnamed)';
32
+
33
+ // 从节点的 _components 中移除引用
34
+ if (node._components) {
35
+ node._components = node._components.filter(c => c.__id__ !== compIndex);
36
+ }
37
+
38
+ // 重建引用并删除组件
39
+ const indicesToDelete = new Set([compIndex]);
40
+ rebuildReferences(data, indicesToDelete);
41
+ data.splice(compIndex, 1);
42
+
43
+ return {
44
+ success: true,
45
+ type: 'component',
46
+ componentType: compType,
47
+ componentIndex: compIndex,
48
+ nodeName,
49
+ nodeIndex: nodeId
50
+ };
51
+ }
52
+
53
+ /**
54
+ * 删除节点
55
+ */
56
+ function removeNode(data, nodeIndex) {
57
+ const node = data[nodeIndex];
58
+ if (!node) {
59
+ return { error: `节点索引 ${nodeIndex} 不存在` };
60
+ }
61
+
62
+ if (nodeIndex <= 1) {
63
+ return { error: '不能删除根节点' };
64
+ }
65
+
66
+ const nodeName = node._name || '(unnamed)';
67
+
68
+ // 收集所有需要删除的索引
69
+ const indicesToDelete = collectNodeAndChildren(data, nodeIndex);
70
+
71
+ // 从父节点的 _children 中移除引用
72
+ if (node._parent) {
73
+ const parentIndex = node._parent.__id__;
74
+ const parent = data[parentIndex];
75
+ if (parent && parent._children) {
76
+ parent._children = parent._children.filter(c => c.__id__ !== nodeIndex);
77
+ }
78
+ }
79
+
80
+ // 重建引用
81
+ rebuildReferences(data, indicesToDelete);
82
+
83
+ // 删除元素
84
+ const sortedIndices = Array.from(indicesToDelete).sort((a, b) => b - a);
85
+ for (const idx of sortedIndices) {
86
+ data.splice(idx, 1);
87
+ }
88
+
89
+ return {
90
+ success: true,
91
+ type: 'node',
92
+ nodeName,
93
+ nodeIndex,
94
+ deletedCount: sortedIndices.length
95
+ };
96
+ }
6
97
 
7
98
  function run(args) {
8
99
  if (args.length < 2) {
9
- console.log(JSON.stringify({ error: '用法: cocos2.4 remove <场景文件路径> <索引>' }));
100
+ outputError('用法: cocos2d-cli remove <场景文件路径> <索引> [--component|--node]');
10
101
  return;
11
102
  }
12
103
 
13
104
  const scenePath = args[0];
14
105
 
15
- // 索引必须是数字
16
106
  if (!/^\d+$/.test(args[1])) {
17
- console.log(JSON.stringify({ error: '索引必须是数字,请先用 tree 命令查看节点索引' }));
107
+ outputError('索引必须是数字,请先用 tree 命令查看节点索引');
18
108
  return;
19
109
  }
20
110
 
21
111
  const index = parseInt(args[1]);
112
+ const forceComponent = args.includes('--component');
113
+ const forceNode = args.includes('--node');
114
+
115
+ if (forceComponent && forceNode) {
116
+ outputError('不能同时指定 --component 和 --node');
117
+ return;
118
+ }
22
119
 
23
120
  try {
24
121
  let data = loadScene(scenePath);
25
- const scriptMap = loadScriptMap(scenePath);
26
122
 
27
- // 检查索引是否存在
28
123
  if (!data[index]) {
29
- console.log(JSON.stringify({ error: `索引 ${index} 不存在` }));
124
+ outputError(`索引 ${index} 不存在`);
30
125
  return;
31
126
  }
32
127
 
33
- const item = data[index];
34
- const itemType = item.__type__;
35
-
36
- // 判断是节点还是组件
37
- const isNode = itemType === 'cc.Node' || itemType === 'cc.Scene' || item._name !== undefined;
38
- const isComponent = item.node !== undefined;
39
-
40
- if (isNode && index <= 1) {
41
- console.log(JSON.stringify({ error: '不能删除根节点' }));
42
- return;
128
+ // 确定删除类型
129
+ let deleteType;
130
+ if (forceComponent) {
131
+ deleteType = 'component';
132
+ } else if (forceNode) {
133
+ deleteType = 'node';
134
+ } else {
135
+ deleteType = detectItemType(data, index);
43
136
  }
44
137
 
45
- if (isComponent) {
46
- // 删除组件
47
- const nodeId = item.node.__id__;
48
- const node = data[nodeId];
49
-
50
- if (node && node._components) {
51
- node._components = node._components.filter(c => c.__id__ !== index);
52
- }
53
-
54
- // 真正从数组中删除组件并重建引用
55
- const indicesToDelete = new Set([index]);
56
- rebuildReferences(data, indicesToDelete);
57
- data.splice(index, 1);
58
-
138
+ // 执行删除
139
+ let result;
140
+ if (deleteType === 'component') {
141
+ result = removeComponent(data, index);
59
142
  } else {
60
- // 删除节点
61
- // 收集所有需要删除的索引
62
- const indicesToDelete = collectNodeAndChildren(data, index);
63
-
64
- // 从父节点的 _children 中移除引用
65
- if (item._parent) {
66
- const parentIndex = item._parent.__id__;
67
- const parent = data[parentIndex];
68
- if (parent && parent._children) {
69
- parent._children = parent._children.filter(c => c.__id__ !== index);
70
- }
71
- }
72
-
73
- // 重建引用
74
- rebuildReferences(data, indicesToDelete);
75
-
76
- // 删除元素
77
- const sortedIndices = Array.from(indicesToDelete).sort((a, b) => b - a);
78
- for (const idx of sortedIndices) {
79
- data.splice(idx, 1);
80
- }
143
+ result = removeNode(data, index);
144
+ }
145
+
146
+ if (result.error) {
147
+ outputError(result.error);
148
+ return;
81
149
  }
82
150
 
83
151
  // 保存场景
84
152
  saveScene(scenePath, data);
85
-
86
- // 触发编辑器刷新
87
153
  refreshEditor(scenePath);
88
154
 
89
- // 重新加载并显示最新树
90
- data = loadScene(scenePath);
91
- console.log(buildTree(data, scriptMap, 1));
155
+ outputSuccess(result);
92
156
 
93
157
  } catch (err) {
94
- console.log(JSON.stringify({ error: err.message }));
158
+ outputError(err.message);
95
159
  }
96
160
  }
97
161
 
@@ -1,271 +1,42 @@
1
1
  /**
2
- * set 命令 - 修改节点属性
3
- * 修改成功后返回节点最终状态(与 get 命令格式一致)
2
+ * set 命令 - 修改节点属性
4
3
  */
5
4
 
6
5
  const { loadScene, saveScene, buildMaps, findNodeIndex, refreshEditor } = require('../lib/fire-utils');
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
-
120
- // 解析颜色
121
- function parseColor(colorStr) {
122
- if (!colorStr) return null;
123
- let color = colorStr;
124
- if (typeof color === 'string') {
125
- if (color.startsWith('#')) color = color.slice(1);
126
- if (color.length === 6) {
127
- const r = parseInt(color.slice(0, 2), 16);
128
- const g = parseInt(color.slice(2, 4), 16);
129
- const b = parseInt(color.slice(4, 6), 16);
130
- if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
131
- return { "__type__": "cc.Color", r, g, b, a: 255 };
132
- }
133
- }
134
- }
135
- return null;
136
- }
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
- }
6
+ const { parseOptions, outputError, outputJson } = require('../lib/utils');
7
+ const { setNodeProperties, getNodeState } = require('../lib/node-utils');
164
8
 
165
9
  function run(args) {
166
10
  if (args.length < 2) {
167
- console.log(JSON.stringify({ error: '用法: cocos2.4 set <场景.fire | 预制体.prefab> <节点索引|名称> [选项]' }));
11
+ outputError('用法: cocos2d-cli set <场景.fire | 预制体.prefab> <节点索引|名称> [选项]');
168
12
  return;
169
13
  }
170
14
 
171
15
  const scenePath = args[0];
172
16
  const nodeRef = args[1];
173
-
174
- // 解析选项
175
- const options = {};
176
- args.slice(2).forEach(arg => {
177
- if (arg.startsWith('--')) {
178
- const [key, value] = arg.substring(2).split('=');
179
- options[key] = value;
180
- }
181
- });
17
+ const options = parseOptions(args, 2);
182
18
 
183
19
  try {
184
20
  const data = loadScene(scenePath);
185
21
  const { indexMap } = buildMaps(data);
186
22
 
187
- // 查找节点
188
23
  const nodeIndex = findNodeIndex(data, indexMap, nodeRef);
189
24
 
190
25
  if (nodeIndex === null || !data[nodeIndex]) {
191
- console.log(JSON.stringify({ error: `找不到节点: ${nodeRef}` }));
26
+ outputError(`找不到节点: ${nodeRef}`);
192
27
  return;
193
28
  }
194
29
 
195
30
  const node = data[nodeIndex];
196
31
 
197
- // 修改名称
198
- if (options.name !== undefined) {
199
- node._name = options.name;
200
- }
201
-
202
- // 修改激活状态
203
- if (options.active !== undefined) {
204
- node._active = options.active !== 'false';
205
- }
206
-
207
- // 修改位置
208
- if (options.x !== undefined || options.y !== undefined) {
209
- if (!node._trs) {
210
- node._trs = { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] };
211
- }
212
- if (options.x !== undefined) node._trs.array[0] = parseFloat(options.x);
213
- if (options.y !== undefined) node._trs.array[1] = parseFloat(options.y);
214
- }
215
-
216
- // 修改大小
217
- if (options.width !== undefined || options.height !== undefined) {
218
- if (!node._contentSize) {
219
- node._contentSize = { "__type__": "cc.Size", width: 0, height: 0 };
220
- }
221
- if (options.width !== undefined) node._contentSize.width = parseFloat(options.width);
222
- if (options.height !== undefined) node._contentSize.height = parseFloat(options.height);
223
- }
224
-
225
- // 修改锚点
226
- if (options.anchorX !== undefined || options.anchorY !== undefined) {
227
- if (!node._anchorPoint) {
228
- node._anchorPoint = { "__type__": "cc.Vec2", x: 0.5, y: 0.5 };
229
- }
230
- if (options.anchorX !== undefined) node._anchorPoint.x = parseFloat(options.anchorX);
231
- if (options.anchorY !== undefined) node._anchorPoint.y = parseFloat(options.anchorY);
232
- }
32
+ // 设置节点属性
33
+ setNodeProperties(node, options);
233
34
 
234
- // 修改透明度
235
- if (options.opacity !== undefined) {
236
- node._opacity = Math.max(0, Math.min(255, parseInt(options.opacity)));
237
- }
238
-
239
- // 修改颜色
240
- if (options.color !== undefined) {
241
- const color = parseColor(options.color);
242
- if (color) {
243
- node._color = color;
244
- }
245
- }
246
-
247
- // 修改旋转角度
248
- if (options.rotation !== undefined) {
249
- if (!node._eulerAngles) {
250
- node._eulerAngles = { "__type__": "cc.Vec3", x: 0, y: 0, z: 0 };
251
- }
252
- node._eulerAngles.z = parseFloat(options.rotation);
253
- }
254
-
255
- // 修改缩放
256
- if (options.scaleX !== undefined || options.scaleY !== undefined) {
257
- if (!node._trs) {
258
- node._trs = { "__type__": "TypedArray", "ctor": "Float64Array", "array": [0, 0, 0, 0, 0, 0, 1, 1, 1, 1] };
259
- }
260
- if (options.scaleX !== undefined) node._trs.array[7] = parseFloat(options.scaleX);
261
- if (options.scaleY !== undefined) node._trs.array[8] = parseFloat(options.scaleY);
262
- }
263
-
264
- // 修改 Label 文字内容及字体属性
35
+ // 修改 Label 文字属性
265
36
  if (options.string !== undefined || options.fontSize !== undefined || options.lineHeight !== undefined) {
266
37
  const labelComp = (node._components || []).map(ref => data[ref.__id__]).find(c => c && c.__type__ === 'cc.Label');
267
38
  if (!labelComp) {
268
- console.log(JSON.stringify({ error: `节点 ${node._name} 没有 cc.Label 组件,无法设置文字属性` }));
39
+ outputError(`节点 ${node._name} 没有 cc.Label 组件,无法设置文字属性`);
269
40
  return;
270
41
  }
271
42
  if (options.string !== undefined) {
@@ -280,16 +51,12 @@ function run(args) {
280
51
  }
281
52
  }
282
53
 
283
- // 保存场景
284
54
  saveScene(scenePath, data);
285
-
286
- // 触发编辑器刷新
287
55
  refreshEditor(scenePath);
288
56
 
289
- // 返回节点最终状态(与 get 命令格式一致)
290
- console.log(JSON.stringify(getNodeState(data, node, nodeIndex)));
57
+ outputJson(getNodeState(data, node, nodeIndex));
291
58
  } catch (err) {
292
- console.log(JSON.stringify({ error: err.message }));
59
+ outputError(err.message);
293
60
  }
294
61
  }
295
62
 
@@ -2,13 +2,14 @@
2
2
  * tree 命令 - 查看节点树(支持场景和预制体)
3
3
  */
4
4
 
5
- const { loadScene, loadScriptMap, buildTree, isPrefab } = require('../lib/fire-utils');
5
+ const { loadScene, loadScriptMap, isPrefab } = require('../lib/fire-utils');
6
+ const { buildTree } = require('../lib/node-utils');
6
7
 
7
8
  function run(args) {
8
9
  const filePath = args[0];
9
10
 
10
11
  if (!filePath) {
11
- console.log(JSON.stringify({ error: '用法: cocos2.4 tree <场景.fire | 预制体.prefab>' }));
12
+ console.log(JSON.stringify({ error: '用法: cocos2d-cli tree <场景.fire | 预制体.prefab>' }));
12
13
  return;
13
14
  }
14
15
 
@@ -30,4 +31,4 @@ function run(args) {
30
31
  }
31
32
  }
32
33
 
33
- module.exports = { run };
34
+ module.exports = { run };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * cc.Button 组件
3
+ */
4
+
5
+ const { generateId } = require('../utils');
6
+
7
+ const BUTTON_NORMAL_SPRITE = { "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" };
8
+ const BUTTON_PRESSED_SPRITE = { "__uuid__": "e9ec654c-97a2-4787-9325-e6a10375219a" };
9
+ const BUTTON_DISABLED_SPRITE = { "__uuid__": "29158224-f8dd-4661-a796-1ffab537140e" };
10
+
11
+ // 属性映射
12
+ const PROP_MAP = {
13
+ 'interactable': '_N$interactable',
14
+ 'transition': '_N$transition',
15
+ 'zoomScale': 'zoomScale',
16
+ 'duration': 'duration',
17
+ 'target': '_N$target'
18
+ };
19
+
20
+ // 枚举值
21
+ const ENUMS = {
22
+ transition: ['NONE', 'COLOR', 'SPRITE', 'SCALE']
23
+ };
24
+
25
+ /**
26
+ * 创建 Button 组件
27
+ * @param {number} nodeId - 节点索引
28
+ * @returns {object} 组件数据
29
+ */
30
+ function create(nodeId) {
31
+ return {
32
+ "__type__": "cc.Button",
33
+ "_name": "",
34
+ "_objFlags": 0,
35
+ "node": { "__id__": nodeId },
36
+ "_enabled": true,
37
+ "_normalMaterial": null,
38
+ "_grayMaterial": null,
39
+ "duration": 0.1,
40
+ "zoomScale": 1.2,
41
+ "clickEvents": [],
42
+ "_N$interactable": true,
43
+ "_N$enableAutoGrayEffect": false,
44
+ "_N$transition": 3,
45
+ "transition": 3,
46
+ "_N$normalColor": {
47
+ "__type__": "cc.Color",
48
+ "r": 255,
49
+ "g": 255,
50
+ "b": 255,
51
+ "a": 255
52
+ },
53
+ "_N$pressedColor": {
54
+ "__type__": "cc.Color",
55
+ "r": 200,
56
+ "g": 200,
57
+ "b": 200,
58
+ "a": 255
59
+ },
60
+ "pressedColor": {
61
+ "__type__": "cc.Color",
62
+ "r": 200,
63
+ "g": 200,
64
+ "b": 200,
65
+ "a": 255
66
+ },
67
+ "_N$hoverColor": {
68
+ "__type__": "cc.Color",
69
+ "r": 255,
70
+ "g": 255,
71
+ "b": 255,
72
+ "a": 255
73
+ },
74
+ "hoverColor": {
75
+ "__type__": "cc.Color",
76
+ "r": 255,
77
+ "g": 255,
78
+ "b": 255,
79
+ "a": 255
80
+ },
81
+ "_N$disabledColor": {
82
+ "__type__": "cc.Color",
83
+ "r": 120,
84
+ "g": 120,
85
+ "b": 120,
86
+ "a": 200
87
+ },
88
+ "_N$normalSprite": BUTTON_NORMAL_SPRITE,
89
+ "_N$pressedSprite": BUTTON_PRESSED_SPRITE,
90
+ "pressedSprite": BUTTON_PRESSED_SPRITE,
91
+ "_N$hoverSprite": BUTTON_NORMAL_SPRITE,
92
+ "hoverSprite": BUTTON_NORMAL_SPRITE,
93
+ "_N$disabledSprite": BUTTON_DISABLED_SPRITE,
94
+ "_N$target": null,
95
+ "_id": generateId()
96
+ };
97
+ }
98
+
99
+ /**
100
+ * 应用属性
101
+ * @param {object} comp - 组件对象
102
+ * @param {object} props - 属性对象
103
+ */
104
+ function applyProps(comp, props) {
105
+ if (!props) return;
106
+
107
+ for (const [key, value] of Object.entries(props)) {
108
+ const compKey = PROP_MAP[key];
109
+ if (compKey) {
110
+ comp[compKey] = value;
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * 提取属性(用于显示)
117
+ * @param {object} comp - 组件对象
118
+ * @returns {object} 提取的属性
119
+ */
120
+ function extractProps(comp) {
121
+ return {
122
+ interactable: comp._N$interactable,
123
+ transition: ENUMS.transition[comp._N$transition] || comp._N$transition,
124
+ zoomScale: comp.zoomScale,
125
+ duration: comp.duration
126
+ };
127
+ }
128
+
129
+ module.exports = {
130
+ type: 'button',
131
+ ccType: 'cc.Button',
132
+ create,
133
+ applyProps,
134
+ extractProps,
135
+ PROP_MAP,
136
+ ENUMS
137
+ };