import { Container } from "pixi.js"; import Game from "@/core/Game"; import { logger } from "@/core/Logger"; import { SceneType } from "@/enums/SceneType"; import { BaseScene } from "./BaseScene"; import { TransitionTransaction } from "./TransitionTransaction"; import type { IBaseScene } from "./types"; // Extend the Container interface to allow setting and accessing _isHolderLast declare module 'pixi.js' { interface Container { _isHolderLast?: boolean; } } declare global { interface Container { _isHolderLast?: boolean; } } type StageChangeCallback = ( current: IBaseScene, previous: IBaseScene | undefined ) => Promise | void; class SceneManager { private static instance: SceneManager; private game: Game; private scenes: Map = new Map(); private _currentScene: IBaseScene | null = null; private changeCallbacks: StageChangeCallback[] = []; private constructor() { this.game = Game.getInstance(); } static getInstance(): SceneManager { if (!SceneManager.instance) { SceneManager.instance = new SceneManager(); } return SceneManager.instance; } get currentScene(): IBaseScene | null { return this._currentScene; } /** 监听场景变化 */ onStageChange(cb: StageChangeCallback): () => void { this.changeCallbacks.push(cb); return () => { const index = this.changeCallbacks.indexOf(cb); if (index >= 0) { this.changeCallbacks.splice(index, 1); } }; } /** 触发场景变化回调 */ private async emitStageChange( current: IBaseScene, previous: IBaseScene | undefined ): Promise { for (const cb of this.changeCallbacks) { await cb(current, previous); } } /** 注册场景 */ registerScene(scene: IBaseScene): void { if (!scene.name) { logger.warn("SceneManager: scene name cannot be empty"); return; } if (this.scenes.has(scene.name)) { logger.warn(`SceneManager: scene "${scene.name}" already registered`); } this.scenes.set(scene.name, scene); scene.changeScene = (name: string, options) => { this.changeScene(name, options); }; if (scene.type === SceneType.Resident) { this.game.stage.addChild(scene.stage); logger.debug(`SceneManager: registered resident scene "${scene.name}"`); } else { logger.debug(`SceneManager: registered normal scene "${scene.name}"`); } } /** 初始化入口场景(与 changeScene 一致:加载资源 → 布局 → onLoad) */ async initScene(name: string): Promise { if (!name) { logger.warn("SceneManager: scene name cannot be empty"); return; } const scene = this.getSceneOrThrow(name); this._currentScene = scene; if (scene.type === SceneType.Normal) { this.game.stage.addChild(scene.stage); } else if (scene.type === SceneType.Resident) { scene.stage.visible = true; } if (!scene._assetsLoaded) { await scene.loadBundle?.(); scene._assetsLoaded = true; } if (!scene._layoutDone) { await scene.layout?.(); scene._layoutDone = true; } await scene.onLoad?.(); await this.emitStageChange(scene, undefined); logger.debug(`SceneManager: initialized scene "${name}"`); } /** 切换场景 */ async changeScene(name: string, options?: { isHolderLast?: boolean }): Promise { if (!name) { logger.warn("SceneManager: scene name cannot be empty"); return; } const previous = this._currentScene; if (!previous) { throw new Error(`SceneManager: no current scene, call initScene first`); } const target = this.getSceneOrThrow(name); let previousVisibleBefore = true; let previousMountedBefore = false; let targetVisibleBefore = true; let targetMountedBefore = false; const transaction = new TransitionTransaction({ prepare: () => { previousVisibleBefore = previous.stage.visible; previousMountedBefore = this.game.stage.children.includes(previous.stage); targetVisibleBefore = target.stage.visible; targetMountedBefore = this.game.stage.children.includes(target.stage); }, commit: async () => { // 处理前一个场景 if (previous.type === SceneType.Normal) { if (options?.isHolderLast) { previous.stage.visible = false; previous.stage._isHolderLast = true; } else { // 销毁显示节点并卸载,但保留场景注册,便于之后再次 changeScene("welcome") 等 const oldStage = previous.stage; oldStage.destroy({ children: true }); this.game.stage.removeChild(oldStage); if (previous._assetsLoaded) { await previous.unLoadBundle?.(); previous._assetsLoaded = false; } await previous.onUnLoad?.(); const nextStage = new Container(); nextStage.label = previous.name; (previous as { stage: Container }).stage = nextStage; previous._layoutDone = false; } } else if (previous.type === SceneType.Resident) { previous.stage.visible = false; await previous.onUnLoad?.(); } this._currentScene = target; // 处理目标场景 if (target.type === SceneType.Normal) { if (target.stage._isHolderLast) { target.stage.visible = true; } else { if (!this.game.stage.children.includes(target.stage)) { this.game.stage.addChild(target.stage); } } } else if (target.type === SceneType.Resident) { target.stage.visible = true; } // 生命周期 if (!target._assetsLoaded) { await target.loadBundle?.(); target._assetsLoaded = true; } if (!target._layoutDone) { await target.layout?.(); target._layoutDone = true; } await target.onLoad?.(); // 触发回调 await this.emitStageChange(target, previous); logger.debug(`SceneManager: changed from "${previous.name}" to "${name}"`); }, rollback: async () => { this._currentScene = previous; previous.stage.visible = previousVisibleBefore; target.stage.visible = targetVisibleBefore; const previousMountedNow = this.game.stage.children.includes(previous.stage); if (previousMountedBefore && !previousMountedNow) { this.game.stage.addChild(previous.stage); } else if (!previousMountedBefore && previousMountedNow) { this.game.stage.removeChild(previous.stage); } const targetMountedNow = this.game.stage.children.includes(target.stage); if (targetMountedBefore && !targetMountedNow) { this.game.stage.addChild(target.stage); } else if (!targetMountedBefore && targetMountedNow) { this.game.stage.removeChild(target.stage); } }, }); await transaction.execute(); } /** 获取已注册场景 */ getScene(name: string): IBaseScene | undefined { if (!name) { logger.warn("SceneManager: scene name cannot be empty"); return undefined; } return this.scenes.get(name); } getSceneOrThrow(name: string): IBaseScene { if (!name) { logger.warn("SceneManager: scene name cannot be empty"); throw new Error("SceneManager: scene name cannot be empty"); } const scene = this.scenes.get(name); if (!scene) { throw new Error(`SceneManager: scene "${name}" not registered`); } return scene; } /** 确保场景存在,不存在则创建容器 */ ensureSceneExists(name: string, type: SceneType = SceneType.Normal): Container { if (!name) { logger.warn("SceneManager: scene name cannot be empty"); return new Container(); } if (this.scenes.has(name)) { return this.getSceneOrThrow(name).stage; } const container = new Container(); container.label = name; class DynamicScene extends BaseScene { stage: Container; constructor(sceneName: string, sceneType: SceneType, root: Container) { super(sceneName, sceneType); this.stage = root; } } const scene = new DynamicScene(name, type, container); this.registerScene(scene); return container; } /** 获取所有已注册场景 */ getAllScenes(): IBaseScene[] { return Array.from(this.scenes.values()); } hasScene(name: string): boolean { if (!name) { logger.warn("SceneManager: scene name cannot be empty"); return false; } return this.scenes.has(name); } } export default SceneManager; export const sceneManager = SceneManager.getInstance();