245 lines
9.6 KiB
TypeScript
245 lines
9.6 KiB
TypeScript
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();
|
||
}
|
||
}
|