cocos2d-cli 1.1.1 → 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.
- package/bin/cocos-cli.js +34 -30
- package/package.json +1 -1
- package/src/commands/create-scene.js +370 -404
- package/src/commands/prefab-create.js +437 -25
|
@@ -1,46 +1,458 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* prefab-create 命令 -
|
|
2
|
+
* prefab-create 命令 - 从 JSON 结构创建预制体文件
|
|
3
|
+
*
|
|
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 + 各组件特有属性
|
|
3
19
|
*/
|
|
4
20
|
|
|
5
|
-
const { saveScene, createPrefab } = require('../lib/fire-utils');
|
|
6
21
|
const fs = require('fs');
|
|
7
22
|
const path = require('path');
|
|
23
|
+
const { Components, generateId } = require('../lib/components');
|
|
24
|
+
const { createPrefab, generateFileId } = require('../lib/fire-utils');
|
|
8
25
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
// 支持的组件类型
|
|
27
|
+
const COMPONENT_TYPES = {
|
|
28
|
+
'sprite': 'sprite',
|
|
29
|
+
'label': 'label',
|
|
30
|
+
'button': 'button',
|
|
31
|
+
'layout': 'layout',
|
|
32
|
+
'widget': 'widget',
|
|
33
|
+
'camera': 'camera',
|
|
34
|
+
'canvas': 'canvas',
|
|
35
|
+
'particle': 'particleSystem',
|
|
36
|
+
'particlesystem': 'particleSystem'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 解析颜色字符串 #RRGGBB 或 #RRGGBBAA
|
|
41
|
+
*/
|
|
42
|
+
function parseColor(colorStr) {
|
|
43
|
+
if (!colorStr || typeof colorStr !== 'string') return null;
|
|
44
|
+
|
|
45
|
+
let hex = colorStr.replace('#', '');
|
|
46
|
+
if (hex.length === 6) {
|
|
47
|
+
return {
|
|
48
|
+
r: parseInt(hex.substring(0, 2), 16),
|
|
49
|
+
g: parseInt(hex.substring(2, 4), 16),
|
|
50
|
+
b: parseInt(hex.substring(4, 6), 16),
|
|
51
|
+
a: 255
|
|
52
|
+
};
|
|
53
|
+
} else if (hex.length === 8) {
|
|
54
|
+
return {
|
|
55
|
+
r: parseInt(hex.substring(0, 2), 16),
|
|
56
|
+
g: parseInt(hex.substring(2, 4), 16),
|
|
57
|
+
b: parseInt(hex.substring(4, 6), 16),
|
|
58
|
+
a: parseInt(hex.substring(6, 8), 16)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 生成 UUID
|
|
66
|
+
*/
|
|
67
|
+
function generateUUID() {
|
|
68
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
69
|
+
const r = Math.random() * 16 | 0;
|
|
70
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
71
|
+
return v.toString(16);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 解析组件定义
|
|
77
|
+
* 支持两种格式:
|
|
78
|
+
* 1. 字符串: "sprite"
|
|
79
|
+
* 2. 对象: { "type": "sprite", "sizeMode": 1 }
|
|
80
|
+
*/
|
|
81
|
+
function parseComponent(compDef) {
|
|
82
|
+
if (typeof compDef === 'string') {
|
|
83
|
+
const type = COMPONENT_TYPES[compDef.toLowerCase()];
|
|
84
|
+
return type ? { type, props: {} } : null;
|
|
13
85
|
}
|
|
14
86
|
|
|
15
|
-
|
|
16
|
-
|
|
87
|
+
if (typeof compDef === 'object' && compDef.type) {
|
|
88
|
+
const type = COMPONENT_TYPES[compDef.type.toLowerCase()];
|
|
89
|
+
if (!type) return null;
|
|
90
|
+
|
|
91
|
+
const props = { ...compDef };
|
|
92
|
+
delete props.type;
|
|
93
|
+
return { type, props };
|
|
94
|
+
}
|
|
17
95
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 应用组件属性
|
|
101
|
+
*/
|
|
102
|
+
function applyComponentProps(comp, props) {
|
|
103
|
+
if (!props || !comp) return;
|
|
104
|
+
|
|
105
|
+
const type = comp.__type__;
|
|
106
|
+
|
|
107
|
+
// Widget 特殊处理:根据设置的方向计算 alignFlags
|
|
108
|
+
// 位掩码定义 (来自 Cocos Creator 源码)
|
|
109
|
+
// TOP=1, MID(verticalCenter)=2, BOT=4, LEFT=8, CENTER(horizontalCenter)=16, RIGHT=32
|
|
110
|
+
if (type === 'cc.Widget') {
|
|
111
|
+
const ALIGN = {
|
|
112
|
+
top: 1,
|
|
113
|
+
verticalCenter: 2,
|
|
114
|
+
bottom: 4,
|
|
115
|
+
left: 8,
|
|
116
|
+
horizontalCenter: 16,
|
|
117
|
+
right: 32
|
|
118
|
+
};
|
|
119
|
+
let alignFlags = 0;
|
|
120
|
+
|
|
121
|
+
for (const dir of ['top', 'bottom', 'left', 'right', 'horizontalCenter', 'verticalCenter']) {
|
|
122
|
+
if (props[dir] !== undefined && props[dir] !== null) {
|
|
123
|
+
alignFlags |= ALIGN[dir];
|
|
124
|
+
}
|
|
23
125
|
}
|
|
24
126
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!fs.existsSync(dir)) {
|
|
28
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
127
|
+
if (alignFlags > 0) {
|
|
128
|
+
comp._alignFlags = alignFlags;
|
|
29
129
|
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 通用属性映射
|
|
133
|
+
const propMap = {
|
|
134
|
+
// Sprite
|
|
135
|
+
'sizeMode': '_sizeMode',
|
|
136
|
+
'fillType': '_fillType',
|
|
137
|
+
'fillCenter': '_fillCenter',
|
|
138
|
+
'fillStart': '_fillStart',
|
|
139
|
+
'fillRange': '_fillRange',
|
|
140
|
+
'trim': '_isTrimmedMode',
|
|
30
141
|
|
|
31
|
-
//
|
|
32
|
-
|
|
142
|
+
// Label
|
|
143
|
+
'string': '_string',
|
|
144
|
+
'fontSize': '_fontSize',
|
|
145
|
+
'lineHeight': '_lineHeight',
|
|
146
|
+
'horizontalAlign': '_N$horizontalAlign',
|
|
147
|
+
'verticalAlign': '_N$verticalAlign',
|
|
148
|
+
'overflow': '_N$overflow',
|
|
149
|
+
'fontFamily': '_N$fontFamily',
|
|
150
|
+
'wrap': '_enableWrapText',
|
|
33
151
|
|
|
34
|
-
//
|
|
35
|
-
|
|
152
|
+
// Widget
|
|
153
|
+
'alignFlags': '_alignFlags',
|
|
154
|
+
'left': '_left',
|
|
155
|
+
'right': '_right',
|
|
156
|
+
'top': '_top',
|
|
157
|
+
'bottom': '_bottom',
|
|
158
|
+
'horizontalCenter': '_horizontalCenter',
|
|
159
|
+
'verticalCenter': '_verticalCenter',
|
|
160
|
+
'isAbsLeft': '_isAbsLeft',
|
|
161
|
+
'isAbsRight': '_isAbsRight',
|
|
162
|
+
'isAbsTop': '_isAbsTop',
|
|
163
|
+
'isAbsBottom': '_isAbsBottom',
|
|
36
164
|
|
|
165
|
+
// Button
|
|
166
|
+
'interactable': '_N$interactable',
|
|
167
|
+
'transition': '_N$transition',
|
|
168
|
+
'zoomScale': 'zoomScale',
|
|
169
|
+
'duration': 'duration',
|
|
170
|
+
|
|
171
|
+
// Layout
|
|
172
|
+
'layoutType': '_N$layoutType',
|
|
173
|
+
'cellSize': '_N$cellSize',
|
|
174
|
+
'startAxis': '_N$startAxis',
|
|
175
|
+
'paddingLeft': '_N$paddingLeft',
|
|
176
|
+
'paddingRight': '_N$paddingRight',
|
|
177
|
+
'paddingTop': '_N$paddingTop',
|
|
178
|
+
'paddingBottom': '_N$paddingBottom',
|
|
179
|
+
'spacingX': '_N$spacingX',
|
|
180
|
+
'spacingY': '_N$spacingY',
|
|
181
|
+
'resize': '_resize',
|
|
182
|
+
|
|
183
|
+
// Canvas
|
|
184
|
+
'designResolution': '_designResolution',
|
|
185
|
+
'fitWidth': '_fitWidth',
|
|
186
|
+
'fitHeight': '_fitHeight',
|
|
187
|
+
|
|
188
|
+
// Camera
|
|
189
|
+
'orthoSize': '_orthoSize',
|
|
190
|
+
'backgroundColor': '_backgroundColor',
|
|
191
|
+
'cullingMask': '_cullingMask',
|
|
192
|
+
'zoomRatio': '_zoomRatio'
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// 特殊处理:label 的 string 属性需要同时设置 _N$string
|
|
196
|
+
if (type === 'cc.Label' && props.string !== undefined) {
|
|
197
|
+
comp._N$string = props.string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 应用属性
|
|
201
|
+
for (const [key, value] of Object.entries(props)) {
|
|
202
|
+
const compKey = propMap[key];
|
|
203
|
+
if (compKey) {
|
|
204
|
+
// 处理特殊类型
|
|
205
|
+
if (key === 'fillCenter' && Array.isArray(value)) {
|
|
206
|
+
comp[compKey] = { "__type__": "cc.Vec2", "x": value[0], "y": value[1] };
|
|
207
|
+
} else if (key === 'cellSize' && Array.isArray(value)) {
|
|
208
|
+
comp[compKey] = { "__type__": "cc.Size", "width": value[0], "height": value[1] };
|
|
209
|
+
} else if (key === 'designResolution' && Array.isArray(value)) {
|
|
210
|
+
comp[compKey] = { "__type__": "cc.Size", "width": value[0], "height": value[1] };
|
|
211
|
+
} else if ((key === 'backgroundColor' || key === 'color') && typeof value === 'string') {
|
|
212
|
+
const color = parseColor(value);
|
|
213
|
+
if (color) {
|
|
214
|
+
comp[compKey] = { "__type__": "cc.Color", ...color };
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
comp[compKey] = value;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 从 JSON 定义创建预制体数据
|
|
225
|
+
*/
|
|
226
|
+
function createPrefabData(nodeDef) {
|
|
227
|
+
const data = [];
|
|
228
|
+
|
|
229
|
+
// 索引 0: cc.Prefab
|
|
230
|
+
data.push({
|
|
231
|
+
"__type__": "cc.Prefab",
|
|
232
|
+
"_name": "",
|
|
233
|
+
"_objFlags": 0,
|
|
234
|
+
"_native": "",
|
|
235
|
+
"data": { "__id__": 1 },
|
|
236
|
+
"optimizationPolicy": 0,
|
|
237
|
+
"asyncLoadAssets": false,
|
|
238
|
+
"readonly": false
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const prefabInfoList = [];
|
|
242
|
+
|
|
243
|
+
function addNode(def, parentIndex) {
|
|
244
|
+
const nodeIndex = data.length;
|
|
245
|
+
const uuid = generateUUID();
|
|
246
|
+
const fileId = generateFileId();
|
|
247
|
+
|
|
248
|
+
// 解析组件
|
|
249
|
+
const compList = (def.components || [])
|
|
250
|
+
.map(parseComponent)
|
|
251
|
+
.filter(Boolean);
|
|
252
|
+
|
|
253
|
+
// 解析颜色
|
|
254
|
+
let color = { "__type__": "cc.Color", "r": 255, "g": 255, "b": 255, "a": 255 };
|
|
255
|
+
if (def.color) {
|
|
256
|
+
const parsed = parseColor(def.color);
|
|
257
|
+
if (parsed) {
|
|
258
|
+
color = { "__type__": "cc.Color", ...parsed };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 解析尺寸
|
|
263
|
+
const width = def.width || 0;
|
|
264
|
+
const height = def.height || 0;
|
|
265
|
+
|
|
266
|
+
// 解析锚点
|
|
267
|
+
const anchorX = def.anchorX !== undefined ? def.anchorX : 0.5;
|
|
268
|
+
const anchorY = def.anchorY !== undefined ? def.anchorY : 0.5;
|
|
269
|
+
|
|
270
|
+
// 解析旋转和缩放
|
|
271
|
+
const rotation = def.rotation || 0;
|
|
272
|
+
const scaleX = def.scaleX !== undefined ? def.scaleX : 1;
|
|
273
|
+
const scaleY = def.scaleY !== undefined ? def.scaleY : 1;
|
|
274
|
+
|
|
275
|
+
// 创建节点
|
|
276
|
+
const node = {
|
|
277
|
+
"__type__": "cc.Node",
|
|
278
|
+
"_name": def.name || 'Node',
|
|
279
|
+
"_objFlags": 0,
|
|
280
|
+
"_parent": parentIndex === null ? null : { "__id__": parentIndex },
|
|
281
|
+
"_children": [],
|
|
282
|
+
"_active": def.active !== false,
|
|
283
|
+
"_components": [],
|
|
284
|
+
"_prefab": null,
|
|
285
|
+
"_opacity": def.opacity !== undefined ? def.opacity : 255,
|
|
286
|
+
"_color": color,
|
|
287
|
+
"_contentSize": {
|
|
288
|
+
"__type__": "cc.Size",
|
|
289
|
+
width,
|
|
290
|
+
height
|
|
291
|
+
},
|
|
292
|
+
"_anchorPoint": { "__type__": "cc.Vec2", "x": anchorX, "y": anchorY },
|
|
293
|
+
"_trs": {
|
|
294
|
+
"__type__": "TypedArray",
|
|
295
|
+
"ctor": "Float64Array",
|
|
296
|
+
"array": [
|
|
297
|
+
def.x || 0,
|
|
298
|
+
def.y || 0,
|
|
299
|
+
0, // z
|
|
300
|
+
0, // quat x
|
|
301
|
+
0, // quat y
|
|
302
|
+
rotation * Math.PI / 180, // quat z (rotation in radians)
|
|
303
|
+
1, // quat w
|
|
304
|
+
scaleX,
|
|
305
|
+
scaleY,
|
|
306
|
+
1 // scale z
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
"_eulerAngles": { "__type__": "cc.Vec3", "x": 0, "y": 0, "z": rotation },
|
|
310
|
+
"_skewX": 0,
|
|
311
|
+
"_skewY": 0,
|
|
312
|
+
"_is3DNode": false,
|
|
313
|
+
"_groupIndex": 0,
|
|
314
|
+
"groupIndex": 0,
|
|
315
|
+
"_id": uuid
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
data.push(node);
|
|
319
|
+
|
|
320
|
+
// 记录 PrefabInfo
|
|
321
|
+
prefabInfoList.push({
|
|
322
|
+
nodeIndex,
|
|
323
|
+
prefabInfo: {
|
|
324
|
+
"__type__": "cc.PrefabInfo",
|
|
325
|
+
"root": { "__id__": 1 },
|
|
326
|
+
"asset": { "__id__": 0 },
|
|
327
|
+
"fileId": fileId,
|
|
328
|
+
"sync": false
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// 添加组件
|
|
333
|
+
for (const { type, props } of compList) {
|
|
334
|
+
if (Components[type]) {
|
|
335
|
+
const comp = Components[type](nodeIndex);
|
|
336
|
+
// 应用组件属性
|
|
337
|
+
applyComponentProps(comp, props);
|
|
338
|
+
const compIndex = data.length;
|
|
339
|
+
data.push(comp);
|
|
340
|
+
node._components.push({ "__id__": compIndex });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 更新父节点
|
|
345
|
+
if (parentIndex !== null) {
|
|
346
|
+
data[parentIndex]._children.push({ "__id__": nodeIndex });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 递归处理子节点
|
|
350
|
+
if (def.children && def.children.length > 0) {
|
|
351
|
+
for (const child of def.children) {
|
|
352
|
+
addNode(child, nodeIndex);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return nodeIndex;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 添加根节点
|
|
360
|
+
addNode(nodeDef, null);
|
|
361
|
+
|
|
362
|
+
// 添加所有 PrefabInfo
|
|
363
|
+
for (const { nodeIndex, prefabInfo } of prefabInfoList) {
|
|
364
|
+
data.push(prefabInfo);
|
|
365
|
+
data[nodeIndex]._prefab = { "__id__": data.length - 1 };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return data;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function run(args) {
|
|
372
|
+
if (args.length < 1) {
|
|
37
373
|
console.log(JSON.stringify({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
message: `预制体创建成功: ${rootName}`
|
|
374
|
+
error: '用法: cocos2d-cli create-prefab <输出路径.prefab>',
|
|
375
|
+
hint: '从 stdin 读取 JSON 结构生成预制体',
|
|
376
|
+
example: 'type panel.json | cocos2d-cli create-prefab assets/panel.prefab'
|
|
42
377
|
}));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const outputPath = args[0];
|
|
382
|
+
|
|
383
|
+
// 从 stdin 读取 JSON
|
|
384
|
+
let input = '';
|
|
385
|
+
|
|
386
|
+
if (!process.stdin.isTTY) {
|
|
387
|
+
input = fs.readFileSync(0, 'utf8');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 没有 stdin 输入时创建空预制体
|
|
391
|
+
if (!input.trim()) {
|
|
392
|
+
const prefabName = path.basename(outputPath, '.prefab');
|
|
43
393
|
|
|
394
|
+
try {
|
|
395
|
+
if (fs.existsSync(outputPath)) {
|
|
396
|
+
console.log(JSON.stringify({ error: `文件已存在: ${outputPath}` }));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const dir = path.dirname(outputPath);
|
|
401
|
+
if (!fs.existsSync(dir)) {
|
|
402
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const data = createPrefab(prefabName);
|
|
406
|
+
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
|
|
407
|
+
|
|
408
|
+
console.log(JSON.stringify({
|
|
409
|
+
success: true,
|
|
410
|
+
path: outputPath,
|
|
411
|
+
rootName: prefabName
|
|
412
|
+
}));
|
|
413
|
+
return;
|
|
414
|
+
} catch (err) {
|
|
415
|
+
console.log(JSON.stringify({ error: err.message }));
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
// 移除 BOM 并解析 JSON
|
|
422
|
+
input = input.replace(/^\uFEFF/, '').trim();
|
|
423
|
+
const nodeDef = JSON.parse(input);
|
|
424
|
+
|
|
425
|
+
// 支持数组格式(第一个作为根节点)
|
|
426
|
+
const rootNode = Array.isArray(nodeDef) ? nodeDef[0] : nodeDef;
|
|
427
|
+
|
|
428
|
+
// 生成预制体
|
|
429
|
+
const prefabData = createPrefabData(rootNode);
|
|
430
|
+
|
|
431
|
+
// 确保目录存在
|
|
432
|
+
const outputDir = path.dirname(outputPath);
|
|
433
|
+
if (!fs.existsSync(outputDir)) {
|
|
434
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 保存
|
|
438
|
+
fs.writeFileSync(outputPath, JSON.stringify(prefabData, null, 2), 'utf8');
|
|
439
|
+
|
|
440
|
+
// 统计
|
|
441
|
+
let nodeCount = 0, compCount = 0;
|
|
442
|
+
for (const item of prefabData) {
|
|
443
|
+
if (item.__type__ === 'cc.Node') nodeCount++;
|
|
444
|
+
else if (item.__type__?.startsWith('cc.') && !['cc.Prefab', 'cc.PrefabInfo'].includes(item.__type__)) {
|
|
445
|
+
compCount++;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
console.log(JSON.stringify({
|
|
450
|
+
success: true,
|
|
451
|
+
path: outputPath,
|
|
452
|
+
nodes: nodeCount,
|
|
453
|
+
components: compCount
|
|
454
|
+
}));
|
|
455
|
+
|
|
44
456
|
} catch (err) {
|
|
45
457
|
console.log(JSON.stringify({ error: err.message }));
|
|
46
458
|
}
|