Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration. Co-authored-by: Cursor <cursoragent@cursor.com>
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
import { Sprite, UITransform, Node, view, Layers, director, find } from 'cc';
|
||
import { normalizeTexturePath } from '../visual/EntityTextureResolver';
|
||
import { VisualAssets } from '../visual/VisualAssets';
|
||
import { getThemeBackground } from './ThemeRegistry';
|
||
|
||
const BG_NODE_NAME = 'LevelThemeBackground';
|
||
/** 与主关卡 UI_2D、HUD UI_3D 分离,由 BgCamera 固定渲染 */
|
||
const BG_LAYER = Layers.Enum.DEFAULT;
|
||
|
||
/** 按关卡 theme 应用背景图(BgOverlay + BgCamera,不随主相机缩放/拖拽) */
|
||
export class ThemeBackground {
|
||
static resolveHost(entrance: Node | null): Node | null {
|
||
const scene = entrance?.scene ?? director.getScene();
|
||
if (!scene) return null;
|
||
return find('BgOverlay', scene);
|
||
}
|
||
|
||
/** 移除误挂在 GameRoot / MainLevelEntrance 等处的旧背景节点 */
|
||
static purgeStaleNodes(entrance: Node | null) {
|
||
const scene = entrance?.scene ?? director.getScene();
|
||
if (!scene) return;
|
||
const host = find('BgOverlay', scene);
|
||
const walk = (node: Node) => {
|
||
for (const ch of [...node.children]) {
|
||
if (ch.name === BG_NODE_NAME && ch.parent !== host) {
|
||
ch.destroy();
|
||
} else {
|
||
walk(ch);
|
||
}
|
||
}
|
||
};
|
||
walk(scene);
|
||
}
|
||
|
||
static async apply(entrance: Node | null, themeId: string | undefined): Promise<void> {
|
||
const parent = this.resolveHost(entrance);
|
||
if (!parent?.isValid) return;
|
||
this.purgeStaleNodes(entrance);
|
||
|
||
const path = normalizeTexturePath(getThemeBackground(themeId));
|
||
let bgNode = parent.getChildByName(BG_NODE_NAME);
|
||
if (!path) {
|
||
if (bgNode) bgNode.active = false;
|
||
return;
|
||
}
|
||
if (!bgNode) {
|
||
bgNode = new Node(BG_NODE_NAME);
|
||
bgNode.parent = parent;
|
||
bgNode.addComponent(UITransform);
|
||
bgNode.addComponent(Sprite);
|
||
}
|
||
bgNode.layer = BG_LAYER;
|
||
bgNode.active = true;
|
||
bgNode.setSiblingIndex(0);
|
||
bgNode.setPosition(0, 0, 0);
|
||
bgNode.setScale(1, 1, 1);
|
||
|
||
const sf = await VisualAssets.loadTexturePath(path);
|
||
if (!sf || !bgNode?.isValid) return;
|
||
const ui = bgNode.getComponent(UITransform)!;
|
||
const spr = bgNode.getComponent(Sprite)!;
|
||
spr.spriteFrame = sf;
|
||
spr.sizeMode = Sprite.SizeMode.CUSTOM;
|
||
this.layoutFullScreen(bgNode, sf);
|
||
}
|
||
|
||
private static layoutFullScreen(bgNode: Node, sf: NonNullable<Awaited<ReturnType<typeof VisualAssets.loadTexturePath>>>) {
|
||
const ui = bgNode.getComponent(UITransform)!;
|
||
const vs = view.getVisibleSize();
|
||
const scale = Math.max(vs.width / sf.originalSize.width, vs.height / sf.originalSize.height);
|
||
ui.setContentSize(sf.originalSize.width * scale, sf.originalSize.height * scale);
|
||
ui.setAnchorPoint(0.5, 0.5);
|
||
bgNode.setPosition(0, 0, 0);
|
||
}
|
||
|
||
static clear(entrance: Node | null) {
|
||
this.purgeStaleNodes(entrance);
|
||
const parent = this.resolveHost(entrance);
|
||
const bgNode = parent?.getChildByName(BG_NODE_NAME);
|
||
if (bgNode) bgNode.active = false;
|
||
}
|
||
}
|