110 lines
3.7 KiB
TypeScript
110 lines
3.7 KiB
TypeScript
import { Layers, Node, Sprite, UITransform, Vec3 } from 'cc';
|
||
import { cellToWorldCenter, parseCellKey, tileNodeName } from '../core/GridCoords';
|
||
import { LevelConfig } from './LevelTypes';
|
||
import { setupLayerContainer } from './TileLayout';
|
||
|
||
const UI_LAYER = Layers.Enum.UI_2D;
|
||
|
||
function ensureLayer(root: Node, name: 'Ground' | 'Border'): Node {
|
||
let layer = root.getChildByName(name);
|
||
if (!layer) {
|
||
layer = new Node(name);
|
||
layer.parent = root;
|
||
layer.layer = UI_LAYER;
|
||
}
|
||
setupLayerContainer(layer);
|
||
return layer;
|
||
}
|
||
|
||
/** 砖块可能在 Tiles(排序合并后);优先 Tiles 避免在 Ground/Border 重复创建 */
|
||
function findTileNode(levelRoot: Node, layerKind: 'ground' | 'border', x: number, y: number): Node | null {
|
||
const nodeName = tileNodeName(layerKind, x, y);
|
||
for (const parentName of ['Tiles', 'Ground', 'Border'] as const) {
|
||
const parent = levelRoot.getChildByName(parentName);
|
||
const node = parent?.getChildByName(nodeName) ?? null;
|
||
if (node?.isValid) return node;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function ensureTile(levelRoot: Node, layer: Node, layerKind: 'ground' | 'border', x: number, y: number): Node {
|
||
const existing = findTileNode(levelRoot, layerKind, x, y);
|
||
if (existing) {
|
||
existing.active = true;
|
||
return existing;
|
||
}
|
||
const nodeName = tileNodeName(layerKind, x, y);
|
||
const node = new Node(nodeName);
|
||
node.parent = layer;
|
||
node.layer = UI_LAYER;
|
||
node.addComponent(UITransform);
|
||
node.addComponent(Sprite);
|
||
node.active = true;
|
||
return node;
|
||
}
|
||
|
||
/** 按 levels-database.json 补齐 / 隐藏砖块节点(与关卡编辑器数据一致) */
|
||
export function syncTileNodesFromConfig(levelRoot: Node, config: LevelConfig): number {
|
||
const ground = ensureLayer(levelRoot, 'Ground');
|
||
const border = ensureLayer(levelRoot, 'Border');
|
||
const wantGround = new Set(Object.keys(config.ground ?? {}));
|
||
const wantBorder = new Set(Object.keys(config.border ?? {}));
|
||
let n = 0;
|
||
|
||
for (const key of wantGround) {
|
||
const p = parseCellKey(key);
|
||
if (!p) continue;
|
||
ensureTile(levelRoot, ground, 'ground', p.x, p.y);
|
||
n++;
|
||
}
|
||
for (const key of wantBorder) {
|
||
const p = parseCellKey(key);
|
||
if (!p) continue;
|
||
ensureTile(levelRoot, border, 'border', p.x, p.y);
|
||
n++;
|
||
}
|
||
|
||
for (const ch of ground.children) {
|
||
const m = /^g_(-?\d+)_(-?\d+)$/.exec(ch.name);
|
||
if (!m) continue;
|
||
const key = `${m[1]},${m[2]}`;
|
||
ch.active = wantGround.has(key);
|
||
}
|
||
for (const ch of border.children) {
|
||
const m = /^b_(-?\d+)_(-?\d+)$/.exec(ch.name);
|
||
if (!m) continue;
|
||
const key = `${m[1]},${m[2]}`;
|
||
ch.active = wantBorder.has(key);
|
||
}
|
||
return n;
|
||
}
|
||
|
||
/** 关卡包围盒中心(格子 pivot 落点),用于居中显示 */
|
||
export function computeLevelCenterOffset(config: LevelConfig): Vec3 {
|
||
const keys = [
|
||
...Object.keys(config.ground ?? {}),
|
||
...Object.keys(config.border ?? {}),
|
||
];
|
||
if (!keys.length) return new Vec3(0, 0, 0);
|
||
|
||
let minX = Infinity;
|
||
let maxX = -Infinity;
|
||
let minY = Infinity;
|
||
let maxY = -Infinity;
|
||
for (const key of keys) {
|
||
const p = parseCellKey(key);
|
||
if (!p) continue;
|
||
const w = cellToWorldCenter(new Vec3(p.x, p.y, 0));
|
||
minX = Math.min(minX, w.x);
|
||
maxX = Math.max(maxX, w.x);
|
||
minY = Math.min(minY, w.y);
|
||
maxY = Math.max(maxY, w.y);
|
||
}
|
||
return new Vec3(-(minX + maxX) * 0.5, -(minY + maxY) * 0.5, 0);
|
||
}
|
||
|
||
export function centerLevelRoot(levelRoot: Node, config: LevelConfig) {
|
||
const off = computeLevelCenterOffset(config);
|
||
levelRoot.setPosition(off.x, off.y, 0);
|
||
}
|