Complete Cocos Creator port with level bundles, themes, and tooling.

Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 15:30:58 +08:00
parent cba5105908
commit d393302388
6248 changed files with 17322729 additions and 11036 deletions

View File

@@ -0,0 +1,82 @@
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;
}
}