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.
297 lines
8.6 KiB
297 lines
8.6 KiB
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> | 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 (!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<void> {
|
|
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<void> {
|
|
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();
|