import { BaseScene } from "./scene/BaseScene"; import type { IBaseScene } from "./scene/types"; import { SceneType } from "./enums/SceneType"; import { assetManager } from "./core/AssetManager"; import { logger } from "./core/Logger"; import { Container } from "pixi.js"; import { appRuntime } from "./kernel/AppRuntime"; import type { RuntimePlugin } from "./kernel/RuntimePlugin"; const game = appRuntime.game; const sceneManager = appRuntime.sceneManager; const eventBus = appRuntime.events; const runtimeSceneEventsPlugin: RuntimePlugin = { name: "runtime-scene-events", setup: ({ game: runtimeGame, sceneManager: runtimeSceneManager, events }) => { runtimeGame.setAfterFrameRenderHook(() => { // Keep legacy event name, but only emit from frame loop. events.emit("game:rendered"); events.emit("game:frame-rendered"); }); runtimeSceneManager.onStageChange((current, previous) => { events.emit("scene:changed", { current: current.name, previous: previous?.name, }); }); }, }; type Constructor = new (...args: unknown[]) => T; export async function initApp(): Promise { appRuntime.use(runtimeSceneEventsPlugin); await assetManager.init(); await game.init(); const sceneModules = import.meta.glob("./stages/**/page_*.ts", { eager: true }); for (const path in sceneModules) { try { const mod = sceneModules[path]; const match = path.match(/page_(.*?)\.ts$/); if (!match) continue; const fileSceneName = match[1]; const raw = (mod as { default: unknown }).default; if (typeof raw !== "function") { logger.warn(`initApp: invalid scene file ${path}, expected default class export, skipping`); continue; } const SceneCtor = raw as Constructor>; const scene = new SceneCtor(); if (!scene || typeof scene !== "object" || !("stage" in scene)) { logger.warn(`initApp: invalid scene file ${path}, skipping`); continue; } const legacy = scene as IBaseScene & Record & { name?: string; type?: SceneType }; const mutable = legacy as { name?: string; type?: SceneType; stage: Container | null }; if (!mutable.name) { mutable.name = fileSceneName; } if (mutable.type === undefined) { mutable.type = SceneType.Normal; } if (!mutable.stage) { mutable.stage = new Container(); } sceneManager.registerScene(legacy as IBaseScene); } catch (error) { logger.error(`initApp: failed to load scene file ${path}`, error); } } game.ticker.add((ticker) => { const dt = ticker.deltaTime; const current = sceneManager.currentScene; for (const scene of sceneManager.getAllScenes()) { if (scene.type === SceneType.Resident && scene.stage.visible) { scene.update?.(dt, scene.name, ticker); } } if (current) { current.update?.(dt, current.name, ticker); } game.render("frame"); for (const scene of sceneManager.getAllScenes()) { if (scene.type === SceneType.Resident && scene.stage.visible && scene.lateUpdate) { scene.lateUpdate(dt, scene.name, ticker); } } if (current?.lateUpdate) { current.lateUpdate(dt, current.name, ticker); } }); const residentScenes: BaseScene[] = []; for (const scene of sceneManager.getAllScenes()) { if (scene.type === SceneType.Resident) { residentScenes.push(scene as BaseScene); } } for (const scene of residentScenes) { try { if (!scene._assetsLoaded) { await scene.loadBundle?.(); scene._assetsLoaded = true; } if (!scene._layoutDone) { await scene.layout?.(); scene._layoutDone = true; } await scene.onLoad?.(); } catch (error) { logger.error(`initApp: failed to initialize resident scene ${scene.name}`, error); } } const entryScene = "init"; if (sceneManager.hasScene(entryScene)) { await sceneManager.initScene(entryScene); } else { logger.error(`initApp: entry scene "${entryScene}" not found`); } sceneManager.onStageChange((current) => { logger.debug("Scene changed to", current.name); }); logger.info("App initialized"); appRuntime.markReady(); } export { game, sceneManager, eventBus, assetManager, logger };