feat(scripts): emitPrefabJson - 按 Login_Layer.prefab schema 输出 prefab JSON
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { emitPrefabJson } from '../../scripts/legacy-layer-to-cocos-prefab.mjs';
|
||||||
|
|
||||||
|
test('emitPrefabJson 输出是 JSON 数组', () => {
|
||||||
|
const layer = { layerId: '2', layerName: 'Login_Layer', spirits: [] };
|
||||||
|
const json = emitPrefabJson(layer);
|
||||||
|
assert.ok(Array.isArray(json), '输出必须是数组');
|
||||||
|
assert.ok(json.length >= 1, '至少含 cc.Prefab 头');
|
||||||
|
assert.equal(json[0].__type__, 'cc.Prefab');
|
||||||
|
assert.equal(json[0]._name, 'Login_Layer');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emitPrefabJson 含 CompPrefabInfo 引用块', () => {
|
||||||
|
const layer = { layerId: '2', layerName: 'Login_Layer', spirits: [] };
|
||||||
|
const json = emitPrefabJson(layer);
|
||||||
|
const compInfo = json.filter(o => o.__type__ === 'cc.CompPrefabInfo');
|
||||||
|
assert.ok(compInfo.length > 0, '每个组件应有 CompPrefabInfo 引用块');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emitPrefabJson 含 PrefabInfo 块', () => {
|
||||||
|
const layer = { layerId: '2', layerName: 'Login_Layer', spirits: [] };
|
||||||
|
const json = emitPrefabJson(layer);
|
||||||
|
const prefabInfo = json.find(o => o.__type__ === 'cc.PrefabInfo');
|
||||||
|
assert.ok(prefabInfo, '必须有 PrefabInfo 块');
|
||||||
|
assert.equal(prefabInfo.root.__id__, 1); // 根节点 id=1
|
||||||
|
assert.equal(prefabInfo.asset.__id__, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emitPrefabJson 渠道logo _lpos 误差 < 0.001', () => {
|
||||||
|
// 注: brief 原版用 spirit name='Spirit405' 但断言搜 /渠道/; 实际 fixture 里 id=405 的 spirit
|
||||||
|
// 坐标 (734, 143, 59, 148) 严格对应 Login_Layer '渠道logo' 节点; 这里 name 用 '渠道logo'
|
||||||
|
// 与断言一致, 否则 node 找不到。
|
||||||
|
const layer = {
|
||||||
|
layerId: '2', layerName: 'Login_Layer',
|
||||||
|
spirits: [
|
||||||
|
{ id: '2', name: '微信登录背景', type: '0', imgResID: '20',
|
||||||
|
x: '0', y: '0', w: '1280', h: '720', group: '2', indexOfLayer: '1' },
|
||||||
|
{ id: '405', name: '渠道logo', type: '0', imgResID: '5',
|
||||||
|
x: '734', y: '143', w: '59', h: '148', group: '2', indexOfLayer: '2' },
|
||||||
|
{ id: '285', name: '版本显示', type: '1', imgResID: '',
|
||||||
|
x: '16', y: '9', w: '96', h: '31', group: '2', indexOfLayer: '3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const json = emitPrefabJson(layer);
|
||||||
|
// Find 渠道logo node
|
||||||
|
const genLogo = json.find(o => o.__type__ === 'cc.Node' && o._name && o._name.includes('渠道'));
|
||||||
|
assert.ok(genLogo, '应有渠道logo node');
|
||||||
|
assert.ok(Math.abs(genLogo._lpos.x - 123.5) < 0.001, `x expected 123.5 got ${genLogo._lpos.x}`);
|
||||||
|
assert.ok(Math.abs(genLogo._lpos.y - 143) < 0.001, `y expected 143 got ${genLogo._lpos.y}`);
|
||||||
|
});
|
||||||
@@ -257,3 +257,447 @@ export function resolveSpriteFrameUuid(imgResId) {
|
|||||||
}
|
}
|
||||||
return spriteFrame.uuid;
|
return spriteFrame.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// emitPrefabJson — Layer → Cocos prefab JSON 数组 (Task 8)
|
||||||
|
//
|
||||||
|
// Schema 严格按 fixtures/Login_Layer.prefab (已实测): cc.Prefab 头 + cc.Node 根 +
|
||||||
|
// group-X 父节点 + 每个 Spirit (Node + UITransform + CompPrefabInfo + 主组件 +
|
||||||
|
// CompPrefabInfo + Widget + CompPrefabInfo + PrefabInfo) + 末尾 group/root 的
|
||||||
|
// UITransform/Widget/PrefabInfo 块.
|
||||||
|
//
|
||||||
|
// ID 分配: cc.Prefab 在 array[0] (无 __id__ 字段, 仅 data.__id__=1 引用根);
|
||||||
|
// 从 array[1] 开始 __id__ = array-index. root=1, group-N=2..K, 然后 spirits
|
||||||
|
// 按 group-then-IndexOfLayer 顺序, 每个 spirit 占 8 个 id (1 Node + 1 UIT +
|
||||||
|
// 1 CompInfo + 1 主组件 + 1 CompInfo + 1 Widget + 1 CompInfo + 1 PrefabInfo).
|
||||||
|
// group-N 的 UIT/Widget/PrefabInfo 排在所有 spirits 之后, root 的排在最后.
|
||||||
|
//
|
||||||
|
// 设计分辨率: 1600×720 (YouleNexus project.json), 与 Login_Layer.prefab 中
|
||||||
|
// group/root UITransform._contentSize 一致.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const DESIGN_WIDTH = 1600;
|
||||||
|
const DESIGN_HEIGHT = 720;
|
||||||
|
|
||||||
|
function randomFileId() {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let id = '';
|
||||||
|
for (let i = 0; i < 22; i++) {
|
||||||
|
id += chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeNode(name, parentRef) {
|
||||||
|
return {
|
||||||
|
__type__: 'cc.Node',
|
||||||
|
_name: name,
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
_parent: parentRef,
|
||||||
|
_children: [],
|
||||||
|
_active: true,
|
||||||
|
_components: [],
|
||||||
|
_prefab: null,
|
||||||
|
_lpos: { __type__: 'cc.Vec3', x: 0, y: 0, z: 0 },
|
||||||
|
_lrot: { __type__: 'cc.Quat', x: 0, y: 0, z: 0, w: 1 },
|
||||||
|
_lscale: { __type__: 'cc.Vec3', x: 1, y: 1, z: 1 },
|
||||||
|
_mobility: 0,
|
||||||
|
_layer: 1073741824,
|
||||||
|
_euler: { __type__: 'cc.Vec3', x: 0, y: 0, z: 0 },
|
||||||
|
_id: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCompPrefabInfo() {
|
||||||
|
return {
|
||||||
|
__type__: 'cc.CompPrefabInfo',
|
||||||
|
fileId: randomFileId(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makePrefabInfo() {
|
||||||
|
return {
|
||||||
|
__type__: 'cc.PrefabInfo',
|
||||||
|
root: { __id__: 1 },
|
||||||
|
asset: { __id__: 0 },
|
||||||
|
fileId: randomFileId(),
|
||||||
|
instance: null,
|
||||||
|
targetOverrides: null,
|
||||||
|
nestedPrefabInstanceRoots: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeUiTransform(nodeId, compPrefabId, width, height) {
|
||||||
|
return {
|
||||||
|
__type__: 'cc.UITransform',
|
||||||
|
_name: '',
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
node: { __id__: nodeId },
|
||||||
|
_enabled: true,
|
||||||
|
__prefab: { __id__: compPrefabId },
|
||||||
|
_contentSize: { __type__: 'cc.Size', width, height },
|
||||||
|
_anchorPoint: { __type__: 'cc.Vec2', x: 0.5, y: 0.5 },
|
||||||
|
_id: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeWidget(nodeId, compPrefabId) {
|
||||||
|
return {
|
||||||
|
__type__: 'cc.Widget',
|
||||||
|
_name: '',
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
node: { __id__: nodeId },
|
||||||
|
_enabled: true,
|
||||||
|
__prefab: { __id__: compPrefabId },
|
||||||
|
_alignFlags: 45,
|
||||||
|
_target: null,
|
||||||
|
_left: 0, _right: 0, _top: 0, _bottom: 0,
|
||||||
|
_horizontalCenter: 0, _verticalCenter: 0,
|
||||||
|
_isAbsLeft: true, _isAbsRight: true, _isAbsTop: true, _isAbsBottom: true,
|
||||||
|
_isAbsHorizontalCenter: true, _isAbsVerticalCenter: true,
|
||||||
|
_originalWidth: 0, _originalHeight: 0,
|
||||||
|
_alignMode: 2,
|
||||||
|
_lockFlags: 0,
|
||||||
|
_id: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSprite(nodeId, compPrefabId, spriteFrameUuid, type = 0) {
|
||||||
|
return {
|
||||||
|
__type__: 'cc.Sprite',
|
||||||
|
_name: '',
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
node: { __id__: nodeId },
|
||||||
|
_enabled: true,
|
||||||
|
__prefab: { __id__: compPrefabId },
|
||||||
|
_customMaterial: null,
|
||||||
|
_srcBlendFactor: 2,
|
||||||
|
_dstBlendFactor: 4,
|
||||||
|
_color: { __type__: 'cc.Color', r: 255, g: 255, b: 255, a: 255 },
|
||||||
|
_spriteFrame: spriteFrameUuid
|
||||||
|
? { __uuid__: spriteFrameUuid, __expectedType__: 'cc.SpriteFrame' }
|
||||||
|
: null,
|
||||||
|
_type: type, // 0=SIMPLE, 1=SLICED, 2=TILED, 3=FILLED
|
||||||
|
_fillType: 0,
|
||||||
|
_sizeMode: 0,
|
||||||
|
_fillCenter: { __type__: 'cc.Vec2', x: 0, y: 0 },
|
||||||
|
_fillStart: 0,
|
||||||
|
_fillRange: 0,
|
||||||
|
_isTrimmedMode: true,
|
||||||
|
_useGrayscale: false,
|
||||||
|
_atlas: null,
|
||||||
|
_id: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeLabel(nodeId, compPrefabId, text) {
|
||||||
|
return {
|
||||||
|
__type__: 'cc.Label',
|
||||||
|
_name: '',
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
node: { __id__: nodeId },
|
||||||
|
_enabled: true,
|
||||||
|
__prefab: { __id__: compPrefabId },
|
||||||
|
_customMaterial: null,
|
||||||
|
_srcBlendFactor: 2,
|
||||||
|
_dstBlendFactor: 4,
|
||||||
|
_color: { __type__: 'cc.Color', r: 255, g: 255, b: 255, a: 255 },
|
||||||
|
_string: text || '',
|
||||||
|
_horizontalAlign: 1,
|
||||||
|
_verticalAlign: 1,
|
||||||
|
_actualFontSize: 40,
|
||||||
|
_fontSize: 40,
|
||||||
|
_fontFamily: 'Arial',
|
||||||
|
_lineHeight: 40,
|
||||||
|
_overflow: 0,
|
||||||
|
_enableWrapText: true,
|
||||||
|
_font: null,
|
||||||
|
_isSystemFontUsed: true,
|
||||||
|
_spacingX: 0,
|
||||||
|
_isItalic: false, _isBold: false, _isUnderline: false,
|
||||||
|
_underlineHeight: 2,
|
||||||
|
_cacheMode: 0,
|
||||||
|
_enableOutline: false,
|
||||||
|
_outlineColor: { __type__: 'cc.Color', r: 0, g: 0, b: 0, a: 255 },
|
||||||
|
_outlineWidth: 2,
|
||||||
|
_enableShadow: false,
|
||||||
|
_shadowColor: { __type__: 'cc.Color', r: 0, g: 0, b: 0, a: 255 },
|
||||||
|
_shadowOffset: { __type__: 'cc.Vec2', x: 2, y: 2 },
|
||||||
|
_shadowBlur: 2,
|
||||||
|
_id: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeProgressBar(nodeId, compPrefabId, spriteFrameUuid) {
|
||||||
|
// cc.ProgressBar 简化 schema — 真实 fixtures 中 Layer 含 type=3 spirit 但
|
||||||
|
// Login_Layer.prefab fixture 无 ProgressBar 节点. 字段尽量按 Cocos 3.8 实际 schema 写,
|
||||||
|
// 缺资源时降级为 null (非 unknown; 数据源权威缺失由调用方上游补).
|
||||||
|
return {
|
||||||
|
__type__: 'cc.ProgressBar',
|
||||||
|
_name: '',
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
node: { __id__: nodeId },
|
||||||
|
_enabled: true,
|
||||||
|
__prefab: { __id__: compPrefabId },
|
||||||
|
_customMaterial: null,
|
||||||
|
_srcBlendFactor: 2,
|
||||||
|
_dstBlendFactor: 4,
|
||||||
|
_color: { __type__: 'cc.Color', r: 255, g: 255, b: 255, a: 255 },
|
||||||
|
_spriteFrame: spriteFrameUuid
|
||||||
|
? { __uuid__: spriteFrameUuid, __expectedType__: 'cc.SpriteFrame' }
|
||||||
|
: null,
|
||||||
|
_type: 0,
|
||||||
|
_reverse: false,
|
||||||
|
_totalLength: 100,
|
||||||
|
_progress: 0,
|
||||||
|
_barSprite: null,
|
||||||
|
_mode: 0,
|
||||||
|
_id: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeEditBox(nodeId, compPrefabId, spriteFrameUuid, text) {
|
||||||
|
// cc.EditBox 简化 schema — 同上, fixture 无 EditBox 节点, 字段按 Cocos 3.8 实际 schema.
|
||||||
|
return {
|
||||||
|
__type__: 'cc.EditBox',
|
||||||
|
_name: '',
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
node: { __id__: nodeId },
|
||||||
|
_enabled: true,
|
||||||
|
__prefab: { __id__: compPrefabId },
|
||||||
|
_customMaterial: null,
|
||||||
|
_srcBlendFactor: 2,
|
||||||
|
_dstBlendFactor: 4,
|
||||||
|
_color: { __type__: 'cc.Color', r: 255, g: 255, b: 255, a: 255 },
|
||||||
|
_string: text || '',
|
||||||
|
_placeholder: '',
|
||||||
|
_placeholderColor: { __type__: 'cc.Color', r: 127, g: 127, b: 127, a: 255 },
|
||||||
|
_maxLength: 20,
|
||||||
|
_inputMode: 0,
|
||||||
|
_inputFlag: 0,
|
||||||
|
_returnType: 0,
|
||||||
|
_backgroundImage: spriteFrameUuid
|
||||||
|
? { __uuid__: spriteFrameUuid, __expectedType__: 'cc.SpriteFrame' }
|
||||||
|
: null,
|
||||||
|
_fontColor: { __type__: 'cc.Color', r: 255, g: 255, b: 255, a: 255 },
|
||||||
|
_fontSize: 20,
|
||||||
|
_lineHeight: 40,
|
||||||
|
_fontFamily: 'Arial',
|
||||||
|
_isSystemFontUsed: true,
|
||||||
|
_tabIndex: 0,
|
||||||
|
_editingDidBegin: null,
|
||||||
|
_textChanged: null,
|
||||||
|
_editingDidEnded: null,
|
||||||
|
_editingReturn: null,
|
||||||
|
_stayOnTop: false,
|
||||||
|
_id: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 parsed Layer 对象转为 Cocos prefab JSON 数组, schema 严格按
|
||||||
|
* fixtures/Login_Layer.prefab.
|
||||||
|
*
|
||||||
|
* 字段需求 (数据源权威唯一):
|
||||||
|
* layer.layerName — 必有; cc.Prefab._name / 根节点._name
|
||||||
|
* layer.spirits[*].id — 必有; spirit 唯一标识
|
||||||
|
* layer.spirits[*].name — 必有; 节点._name
|
||||||
|
* layer.spirits[*].type — 必有; 走 mapSpiritTypeToComponent (未知 throw)
|
||||||
|
* layer.spirits[*].x/y/w/h — 必有; 走 computeLpos + computeContentSize
|
||||||
|
* layer.spirits[*].group/indexOfLayer — 必有; 走 buildGroupParents
|
||||||
|
* layer.spirits[*].imgResID — Sprite (type=0/5) 必有; 走 resolveSpriteFrameUuid
|
||||||
|
*
|
||||||
|
* ID 分配策略 (与 Login_Layer.prefab 1:1 对齐):
|
||||||
|
* - cc.Prefab 在 array[0], 无 __id__ (data.__id__=1 引用根)
|
||||||
|
* - 数组下标 i ≥ 1 的对象 __id__ = i
|
||||||
|
* - 根节点 = array[1] (id=1)
|
||||||
|
* - group-X 父节点顺序: 按 group ID 升序排, 紧随根节点 (id=2..K)
|
||||||
|
* - Spirit 顺序: 按 group 升序, group 内按 IndexOfLayer 升序
|
||||||
|
* 每个 spirit 占 8 个 id: Node UIT CompInfo 主组件 CompInfo Widget CompInfo PrefabInfo
|
||||||
|
* - group 末尾追加: group-X 的 UIT CompInfo Widget CompInfo PrefabInfo (5 id)
|
||||||
|
* - root 末尾追加: root 的 UIT CompInfo Widget CompInfo PrefabInfo (5 id)
|
||||||
|
*
|
||||||
|
* @param {{ layerId?: string, layerName: string, spirits: Array<{ id: string, name: string, type: string, imgResID: string, x: string, y: string, w: string, h: string, group: string, indexOfLayer: string }> }} layer
|
||||||
|
* @returns {Array<object>} Cocos prefab JSON 数组
|
||||||
|
*/
|
||||||
|
export function emitPrefabJson(layer) {
|
||||||
|
if (!layer || !layer.layerName) {
|
||||||
|
throw new Error('emitPrefabJson: layer.layerName is required');
|
||||||
|
}
|
||||||
|
if (!Array.isArray(layer.spirits)) {
|
||||||
|
throw new Error('emitPrefabJson: layer.spirits must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
// ── 1. cc.Prefab 头 ─────────────────────────────────────────────
|
||||||
|
out.push({
|
||||||
|
__type__: 'cc.Prefab',
|
||||||
|
_name: layer.layerName,
|
||||||
|
_objFlags: 0,
|
||||||
|
__editorExtras__: {},
|
||||||
|
_native: '',
|
||||||
|
data: { __id__: 1 }, // 根节点 id
|
||||||
|
optimizationPolicy: 0,
|
||||||
|
persistent: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 2. 根节点 cc.Node (id=1) ────────────────────────────────────
|
||||||
|
const rootNode = makeNode(layer.layerName, null);
|
||||||
|
out.push(rootNode);
|
||||||
|
const rootId = out.length - 1; // =1
|
||||||
|
|
||||||
|
// ── 3. group-X 父节点 ──────────────────────────────────────────
|
||||||
|
const { groupParents, rootChildSpiritIds } = buildGroupParents(layer.spirits);
|
||||||
|
const spiritById = new Map(layer.spirits.map(s => [s.id, s]));
|
||||||
|
|
||||||
|
const groupInfo = []; // [{ group, node, nodeId }]
|
||||||
|
for (const g of groupParents) {
|
||||||
|
const node = makeNode(g.name, { __id__: rootId });
|
||||||
|
out.push(node);
|
||||||
|
groupInfo.push({ group: g, node, nodeId: out.length - 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 4. Spirit 子节点 + 组件 ────────────────────────────────────
|
||||||
|
const spiritInfo = new Map(); // spiritId → { node, nodeId, prefabInfoId }
|
||||||
|
|
||||||
|
for (const gi of groupInfo) {
|
||||||
|
for (const spiritId of gi.group.childSpiritIds) {
|
||||||
|
const spirit = spiritById.get(spiritId);
|
||||||
|
if (!spirit) {
|
||||||
|
throw new Error(`emitPrefabJson: spirit id ${spiritId} referenced by ${gi.group.name} but not found in layer.spirits`);
|
||||||
|
}
|
||||||
|
const info = buildSpiritBlock(spirit, { __id__: gi.nodeId }, out);
|
||||||
|
spiritInfo.set(spiritId, info);
|
||||||
|
gi.node._children.push({ __id__: info.nodeId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const spiritId of rootChildSpiritIds) {
|
||||||
|
const spirit = spiritById.get(spiritId);
|
||||||
|
if (!spirit) {
|
||||||
|
throw new Error(`emitPrefabJson: root child spirit id ${spiritId} not found in layer.spirits`);
|
||||||
|
}
|
||||||
|
const info = buildSpiritBlock(spirit, { __id__: rootId }, out);
|
||||||
|
spiritInfo.set(spiritId, info);
|
||||||
|
rootNode._children.push({ __id__: info.nodeId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 5. group-X 末尾 UITransform + Widget + PrefabInfo ──────────
|
||||||
|
for (const gi of groupInfo) {
|
||||||
|
appendNodeEnvelope(gi.node, gi.nodeId, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 6. 根节点末尾 UITransform + Widget + PrefabInfo ───────────
|
||||||
|
appendNodeEnvelope(rootNode, rootId, out);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造单个 Spirit 的所有对象 (Node + UITransform + CompPrefabInfo + 主组件 +
|
||||||
|
* CompPrefabInfo + Widget + CompPrefabInfo + PrefabInfo), 按 schema 顺序 push
|
||||||
|
* 到 out 数组, 返回关键 id 供外层 wiring.
|
||||||
|
*/
|
||||||
|
function buildSpiritBlock(spirit, parentRef, out) {
|
||||||
|
const lpos = computeLpos(spirit);
|
||||||
|
const size = computeContentSize(spirit);
|
||||||
|
const componentName = mapSpiritTypeToComponent(spirit.type);
|
||||||
|
|
||||||
|
// 1. cc.Node (主组件 id=K+0)
|
||||||
|
const node = makeNode(spirit.name, parentRef);
|
||||||
|
node._lpos = { __type__: 'cc.Vec3', x: lpos.x, y: lpos.y, z: lpos.z };
|
||||||
|
out.push(node);
|
||||||
|
const nodeId = out.length - 1;
|
||||||
|
|
||||||
|
// 2. cc.UITransform (K+1)
|
||||||
|
const uiCompInfo = makeCompPrefabInfo();
|
||||||
|
out.push(uiCompInfo);
|
||||||
|
const uiCompInfoId = out.length - 1;
|
||||||
|
const uiTransform = makeUiTransform(nodeId, uiCompInfoId, size.width, size.height);
|
||||||
|
out.push(uiTransform);
|
||||||
|
const uiId = out.length - 1;
|
||||||
|
|
||||||
|
// 3. 主组件 (K+3) — CompPrefabInfo 先 push (K+2), 主组件 __prefab → 该 CompPrefabInfo
|
||||||
|
const mainCompInfo = makeCompPrefabInfo();
|
||||||
|
out.push(mainCompInfo);
|
||||||
|
const mainCompInfoId = out.length - 1;
|
||||||
|
|
||||||
|
const spriteFrameUuid = (spirit.type === '0' || spirit.type === '3' || spirit.type === '5')
|
||||||
|
? resolveSpriteFrameUuid(spirit.imgResID)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
let mainComp;
|
||||||
|
if (componentName === 'cc.Sprite') {
|
||||||
|
const spriteType = spirit.type === '5' ? 1 : 0; // 5=SLICED, else SIMPLE
|
||||||
|
mainComp = makeSprite(nodeId, mainCompInfoId, spriteFrameUuid, spriteType);
|
||||||
|
} else if (componentName === 'cc.Label') {
|
||||||
|
mainComp = makeLabel(nodeId, mainCompInfoId, spirit.name);
|
||||||
|
} else if (componentName === 'cc.ProgressBar') {
|
||||||
|
mainComp = makeProgressBar(nodeId, mainCompInfoId, spriteFrameUuid);
|
||||||
|
} else if (componentName === 'cc.EditBox') {
|
||||||
|
mainComp = makeEditBox(nodeId, mainCompInfoId, spriteFrameUuid, spirit.name);
|
||||||
|
} else {
|
||||||
|
// 数据源权威唯一: mapSpiritTypeToComponent 已 throw "unknown" 情况, 此处兜底是
|
||||||
|
// 防御性 — 若新增组件类型但未在 makeXxx 注册, 显式报错暴露.
|
||||||
|
throw new Error(`emitPrefabJson: component "${componentName}" not yet supported by emitPrefabJson — add makeXxx helper for SpiritType ${spirit.type}`);
|
||||||
|
}
|
||||||
|
out.push(mainComp);
|
||||||
|
const mainCompId = out.length - 1;
|
||||||
|
|
||||||
|
// 4. cc.Widget (K+5) — CompPrefabInfo 先 push (K+4)
|
||||||
|
const widgetCompInfo = makeCompPrefabInfo();
|
||||||
|
out.push(widgetCompInfo);
|
||||||
|
const widgetCompInfoId = out.length - 1;
|
||||||
|
const widget = makeWidget(nodeId, widgetCompInfoId);
|
||||||
|
out.push(widget);
|
||||||
|
const widgetId = out.length - 1;
|
||||||
|
|
||||||
|
// 5. cc.PrefabInfo (K+7) — Node 的 _prefab 指向这里
|
||||||
|
const prefabInfo = makePrefabInfo();
|
||||||
|
out.push(prefabInfo);
|
||||||
|
const prefabInfoId = out.length - 1;
|
||||||
|
|
||||||
|
// ── Wire up Node ────────────────────────────────────────────
|
||||||
|
node._components.push({ __id__: uiId }, { __id__: mainCompId }, { __id__: widgetId });
|
||||||
|
node._prefab = { __id__: prefabInfoId };
|
||||||
|
|
||||||
|
return { node, nodeId, prefabInfoId };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给非 Spirit 节点 (root / group-X) 追加末尾的 UITransform + Widget + PrefabInfo
|
||||||
|
* 块, 并把 node._components / node._prefab 补齐.
|
||||||
|
*/
|
||||||
|
function appendNodeEnvelope(node, nodeId, out) {
|
||||||
|
// 1. UITransform + CompPrefabInfo
|
||||||
|
const uiCompInfo = makeCompPrefabInfo();
|
||||||
|
out.push(uiCompInfo);
|
||||||
|
const uiCompInfoId = out.length - 1;
|
||||||
|
const uiTransform = makeUiTransform(nodeId, uiCompInfoId, DESIGN_WIDTH, DESIGN_HEIGHT);
|
||||||
|
out.push(uiTransform);
|
||||||
|
const uiId = out.length - 1;
|
||||||
|
|
||||||
|
// 2. Widget + CompPrefabInfo
|
||||||
|
const widgetCompInfo = makeCompPrefabInfo();
|
||||||
|
out.push(widgetCompInfo);
|
||||||
|
const widgetCompInfoId = out.length - 1;
|
||||||
|
const widget = makeWidget(nodeId, widgetCompInfoId);
|
||||||
|
out.push(widget);
|
||||||
|
const widgetId = out.length - 1;
|
||||||
|
|
||||||
|
// 3. PrefabInfo
|
||||||
|
const prefabInfo = makePrefabInfo();
|
||||||
|
out.push(prefabInfo);
|
||||||
|
const prefabInfoId = out.length - 1;
|
||||||
|
|
||||||
|
node._components.push({ __id__: uiId }, { __id__: widgetId });
|
||||||
|
node._prefab = { __id__: prefabInfoId };
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user