Files
cocos/assets/scripts/AppBootstrap.ts
2026-06-18 14:07:38 +08:00

245 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
_decorator, Component, Node, Camera, Canvas, UITransform, view, Color,
director, find, Layers,
} from 'cc';
import { GameController } from './GameController';
import { ViewController } from './controller/ViewController';
import { LineGridRenderer } from './gameplay/LineGridRenderer';
import { UIMain } from './ui/UIMain';
import { loadLevelDatabase, refreshLevelIdBounds, LEVEL_ID_BASE } from './level/LevelRegistry';
import { ThemeBackground } from './theme/ThemeBackground';
import { GridSnapHelper } from './level/GridSnapHelper';
import { GameplayDebugBar } from './ui/GameplayDebugBar';
import {
CELL_PIXEL, CAMERA_ORTHO_HALF, DESIGN_WIDTH, DESIGN_HEIGHT,
} from './core/GridConstants';
import { applyEmbeddedDesignResolution } from './core/EmbeddedView';
export {
CELL_PIXEL, CAMERA_ORTHO_HALF, DESIGN_WIDTH, DESIGN_HEIGHT,
} from './core/GridConstants';
const { ccclass, executionOrder } = _decorator;
/** HUD 专用层:与关卡 UI_2D 分离,不受主相机缩放/拖拽影响 */
const HUD_LAYER = Layers.Enum.UI_3D;
/** 背景专用层BgCamera 固定正交,主相机缩放/平移时不跟随 */
const BG_LAYER = Layers.Enum.DEFAULT;
@ccclass('AppBootstrap')
@executionOrder(-100)
export class AppBootstrap extends Component {
private readonly onCanvasResize = () => {
const sync = (globalThis as { __tfrhSyncEmbeddedCanvas?: () => void }).__tfrhSyncEmbeddedCanvas;
if (typeof sync === 'function') sync();
else applyEmbeddedDesignResolution();
};
onDestroy() {
view.off('canvas-resize', this.onCanvasResize, this);
}
async onLoad() {
try {
await this.bootstrap();
} catch (e) {
console.error('[AppBootstrap] 初始化失败', e);
}
}
private async bootstrap() {
console.log('[AppBootstrap] 开始初始化…');
applyEmbeddedDesignResolution();
view.on('canvas-resize', this.onCanvasResize, this);
await loadLevelDatabase();
refreshLevelIdBounds();
const scene = director.getScene()!;
find('UICanvas', scene)?.destroy();
let mainCam = find('Main Camera', scene)?.getComponent(Camera) ?? null;
if (!mainCam) {
const camNode = new Node('Main Camera');
camNode.parent = scene;
mainCam = camNode.addComponent(Camera);
} else if (mainCam.node.parent !== scene) {
mainCam.node.parent = scene;
}
this.setupCamera(mainCam);
mainCam.node.getComponent(ViewController) ?? mainCam.node.addComponent(ViewController);
const size = view.getVisibleSize();
const gameRoot = this.ensureGameRoot(scene, size, mainCam);
this.ensureBgOverlay(scene, size, mainCam);
const uiOverlay = this.ensureUIOverlay(scene, size, mainCam);
this.bindAllCanvases(scene, mainCam);
const host = this.node;
const ctl = host.getComponent(GameController) ?? host.addComponent(GameController);
let entrance = gameRoot.getChildByName('MainLevelEntrance');
if (!entrance) {
entrance = new Node('MainLevelEntrance');
entrance.parent = gameRoot;
entrance.addComponent(UITransform).setContentSize(size.width, size.height);
}
entrance.layer = Layers.Enum.UI_2D;
LineGridRenderer.ensure(entrance);
GridSnapHelper.purgeRuntimeGrids(entrance);
ctl.mainLevelEntrance = entrance;
if (ctl.initialLevelID <= 0) ctl.initialLevelID = LEVEL_ID_BASE;
if (!ctl.inputLevel?.trim()) ctl.inputLevel = String(ctl.initialLevelID);
this.removeRuntimeOverlayUI(gameRoot);
gameRoot.getChildByName('UIMain')?.destroy();
UIMain.ensure(uiOverlay);
GameplayDebugBar.ensure(uiOverlay);
ThemeBackground.purgeStaleNodes(entrance);
// 关卡预制体由 SwitchLevel → createNewLevel 按需加载loader 进关再下 levels_all
ctl.markReady();
ctl.onBootstrapReady();
console.log('[AppBootstrap] 引擎已就绪SwitchLevel 进关时再加载资源/关卡库分片/预制体');
}
private ensureGameRoot(scene: Node, size: { width: number; height: number }, mainCam: Camera): Node {
let gameRoot = find('GameRoot', scene) ?? find('Canvas', scene);
if (!gameRoot) {
gameRoot = new Node('GameRoot');
gameRoot.parent = scene;
}
gameRoot.name = 'GameRoot';
gameRoot.layer = Layers.Enum.UI_2D;
gameRoot.active = true;
const ui = gameRoot.getComponent(UITransform) ?? gameRoot.addComponent(UITransform);
ui.setContentSize(size.width, size.height);
const canvas = gameRoot.getComponent(Canvas) ?? gameRoot.addComponent(Canvas);
canvas.cameraComponent = mainCam;
canvas.alignCanvasWithScreen = true;
return gameRoot;
}
/** 固定背景相机:先于主相机绘制,缩放/拖拽关卡时背景不动 */
private ensureBgOverlay(scene: Node, size: { width: number; height: number }, mainCam: Camera): Node {
let bgCamNode = find('BgCamera', scene);
if (!bgCamNode) {
bgCamNode = new Node('BgCamera');
bgCamNode.parent = scene;
}
bgCamNode.setPosition(0, 0, 1000);
bgCamNode.setRotationFromEuler(0, 0, 0);
const bgCam = bgCamNode.getComponent(Camera) ?? bgCamNode.addComponent(Camera);
bgCam.projection = Camera.ProjectionType.ORTHO;
bgCam.orthoHeight = CAMERA_ORTHO_HALF;
bgCam.near = 1;
bgCam.far = 2000;
bgCam.priority = mainCam.priority - 10;
bgCam.clearFlags = Camera.ClearFlag.SOLID_COLOR;
bgCam.clearColor = new Color(1, 1, 1, 255);
bgCam.visibility = BG_LAYER;
let overlay = find('BgOverlay', scene);
if (!overlay) {
overlay = new Node('BgOverlay');
overlay.parent = scene;
}
overlay.layer = BG_LAYER;
overlay.active = true;
const ui = overlay.getComponent(UITransform) ?? overlay.addComponent(UITransform);
ui.setContentSize(size.width, size.height);
const canvas = overlay.getComponent(Canvas) ?? overlay.addComponent(Canvas);
canvas.cameraComponent = bgCam;
canvas.alignCanvasWithScreen = true;
return overlay;
}
/** 固定 HUD 相机:缩放/平移主相机时按钮位置不变(对齐 Unity Screen Space Overlay */
private ensureUIOverlay(scene: Node, size: { width: number; height: number }, mainCam: Camera): Node {
let uiCamNode = find('UICamera', scene);
if (!uiCamNode) {
uiCamNode = new Node('UICamera');
uiCamNode.parent = scene;
}
uiCamNode.setPosition(0, 0, 1000);
uiCamNode.setRotationFromEuler(0, 0, 0);
const uiCam = uiCamNode.getComponent(Camera) ?? uiCamNode.addComponent(Camera);
uiCam.projection = Camera.ProjectionType.ORTHO;
uiCam.orthoHeight = CAMERA_ORTHO_HALF;
uiCam.near = 1;
uiCam.far = 2000;
uiCam.priority = mainCam.priority + 10;
uiCam.clearFlags = Camera.ClearFlag.DEPTH_STENCIL;
uiCam.visibility = HUD_LAYER;
let overlay = find('UIOverlay', scene);
if (!overlay) {
overlay = new Node('UIOverlay');
overlay.parent = scene;
}
overlay.layer = HUD_LAYER;
overlay.active = true;
const ui = overlay.getComponent(UITransform) ?? overlay.addComponent(UITransform);
ui.setContentSize(size.width, size.height);
const canvas = overlay.getComponent(Canvas) ?? overlay.addComponent(Canvas);
canvas.cameraComponent = uiCam;
canvas.alignCanvasWithScreen = true;
return overlay;
}
private bindAllCanvases(scene: Node, mainCam: Camera) {
const uiCam = find('UICamera', scene)?.getComponent(Camera) ?? null;
const bgCam = find('BgCamera', scene)?.getComponent(Camera) ?? null;
for (const canvas of scene.getComponentsInChildren(Canvas)) {
if (canvas.node.name === 'BgOverlay') {
if (bgCam) canvas.cameraComponent = bgCam;
canvas.alignCanvasWithScreen = true;
continue;
}
if (canvas.node.name === 'UIOverlay') {
if (uiCam) canvas.cameraComponent = uiCam;
canvas.alignCanvasWithScreen = true;
continue;
}
if (!canvas.cameraComponent?.isValid) {
canvas.cameraComponent = mainCam;
}
canvas.alignCanvasWithScreen = true;
}
// 清理无相机 Canvas避免 PointerEventDispatcher 读 null.cameraPriority
for (const canvas of scene.getComponentsInChildren(Canvas)) {
if (!canvas.cameraComponent) {
canvas.destroy();
}
}
}
private removeRuntimeOverlayUI(gameRoot: Node) {
gameRoot.getChildByName('Hint')?.destroy();
gameRoot.getChildByName('LevelSwitchBar')?.destroy();
}
private setupCamera(cam: Camera) {
const camNode = cam.node;
camNode.setPosition(0, 0, 1000);
camNode.setRotationFromEuler(0, 0, 0);
camNode.layer = Layers.Enum.DEFAULT;
cam.projection = Camera.ProjectionType.ORTHO;
cam.orthoHeight = CAMERA_ORTHO_HALF;
cam.near = 1;
cam.far = 2000;
cam.clearFlags = Camera.ClearFlag.DEPTH_STENCIL;
cam.visibility = Layers.Enum.UI_2D;
}
switchLevelFromBootstrap() {
const gc = this.getComponent(GameController);
if (!gc) {
console.warn('[AppBootstrap] 未找到 GameController请先点预览 ▶');
return;
}
gc.clickSwitchLevel();
}
}