1 changed files with 195 additions and 0 deletions
@ -0,0 +1,195 @@ |
|||
import { Container } from "pixi.js"; |
|||
import Game from "@/core/Game"; |
|||
import { logger } from "@/core/Logger"; |
|||
import { assetManager } from "@/core/AssetManager"; |
|||
import { SceneType } from "@/enums/SceneType"; |
|||
import { BaseScene } from "./BaseScene"; |
|||
import type { SceneConfig, IBaseScene } from "./types"; |
|||
|
|||
type StageChangeCallback = ( |
|||
current: IBaseScene, |
|||
previous: IBaseScene | undefined |
|||
) => Promise<void> | void; |
|||
|
|||
class SceneManager { |
|||
private static instance: SceneManager; |
|||
private game: Game; |
|||
private scenes: Map<string, IBaseScene> = 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<void> { |
|||
for (const cb of this.changeCallbacks) { |
|||
await cb(current, previous); |
|||
} |
|||
} |
|||
|
|||
/** 注册场景 */ |
|||
registerScene(scene: IBaseScene): void { |
|||
if (this.scenes.has(scene.name)) { |
|||
logger.warn(`SceneManager: scene "${scene.name}" already registered`); |
|||
} |
|||
this.scenes.set(scene.name, scene); |
|||
|
|||
// 注入 changeScene 方法
|
|||
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}"`); |
|||
} |
|||
} |
|||
|
|||
/** 初始化入口场景 */ |
|||
initScene(name: string): void { |
|||
const scene = this.getSceneOrThrow(name); |
|||
this._currentScene = scene; |
|||
|
|||
if (scene.type === SceneType.Normal) { |
|||
this.game.stage.addChild(scene.stage); |
|||
} |
|||
|
|||
this.emitStageChange(scene, undefined); |
|||
logger.debug(`SceneManager: initialized scene "${name}"`); |
|||
} |
|||
|
|||
/** 切换场景 */ |
|||
async changeScene(name: string, options?: { isHolderLast?: boolean }): Promise<void> { |
|||
const previous = this._currentScene; |
|||
if (!previous) { |
|||
throw new Error(`SceneManager: no current scene, call initScene first`); |
|||
} |
|||
|
|||
const target = this.getSceneOrThrow(name); |
|||
|
|||
// 处理前一个场景
|
|||
if (previous.type === SceneType.Normal) { |
|||
if (options?.isHolderLast) { |
|||
previous.stage.visible = false; |
|||
// @ts-ignore 标记保留
|
|||
previous.stage._isHolderLast = true; |
|||
} else { |
|||
// 销毁场景
|
|||
previous.stage.destroy({ children: true }); |
|||
this.game.stage.removeChild(previous.stage); |
|||
// 卸载资源
|
|||
if (previous._assetsLoaded) { |
|||
await previous.unLoadBundle?.(); |
|||
previous._assetsLoaded = false; |
|||
} |
|||
await previous.onUnLoad?.(); |
|||
this.scenes.delete(previous.name); |
|||
} |
|||
} else if (previous.type === SceneType.Resident) { |
|||
previous.stage.visible = false; |
|||
await previous.onUnLoad?.(); |
|||
} |
|||
|
|||
this._currentScene = target; |
|||
|
|||
// 处理目标场景
|
|||
if (target.type === SceneType.Normal) { |
|||
// @ts-ignore
|
|||
if (target.stage._isHolderLast) { |
|||
target.stage.visible = true; |
|||
} else { |
|||
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 || target.stage !== this.getSceneOrThrow(name).stage) { |
|||
await target.layout?.(); |
|||
target._layoutDone = true; |
|||
} |
|||
|
|||
await target.onLoad?.(); |
|||
|
|||
// 触发回调
|
|||
await this.emitStageChange(target, previous); |
|||
|
|||
logger.debug(`SceneManager: changed from "${previous?.name}" to "${name}"`); |
|||
} |
|||
|
|||
/** 获取已注册场景 */ |
|||
getScene(name: string): IBaseScene | undefined { |
|||
return this.scenes.get(name); |
|||
} |
|||
|
|||
getSceneOrThrow(name: string): IBaseScene { |
|||
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 (this.scenes.has(name)) { |
|||
return this.getSceneOrThrow(name).stage; |
|||
} |
|||
|
|||
const container = new Container(); |
|||
container.label = name; |
|||
// 动态创建一个简单场景
|
|||
// 这个功能主要用于兼容旧的代码风格
|
|||
class DynamicScene extends BaseScene { |
|||
stage = container; |
|||
name = name; |
|||
type = type; |
|||
} |
|||
|
|||
const scene = new DynamicScene(); |
|||
this.registerScene(scene); |
|||
return container; |
|||
} |
|||
|
|||
hasScene(name: string): boolean { |
|||
return this.scenes.has(name); |
|||
} |
|||
} |
|||
|
|||
export default SceneManager; |
|||
export const sceneManager = SceneManager.getInstance(); |
|||
Loading…
Reference in new issue