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 { loadThemeDatabase } from './theme/ThemeRegistry'; import { ensureResourcesBundle } from './core/ResourcesBundle'; import { loadTileDisplayMeta } from './visual/TileDisplayMeta'; import { ThemeBackground } from './theme/ThemeBackground'; import { GridSnapHelper } from './level/GridSnapHelper'; import { GameAudio } from './audio/GameAudio'; 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 ensureResourcesBundle(); await loadThemeDatabase(); await loadTileDisplayMeta(); await loadLevelDatabase(); refreshLevelIdBounds(); await GameAudio.preload(); 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(); } }