You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

145 lines
4.3 KiB

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<T> = new (...args: unknown[]) => T;
export async function initApp(): Promise<void> {
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<IBaseScene & Record<string, unknown>>;
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<string, unknown> & { 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 };