refactor: improved scene manager
This commit is contained in:
parent
fe57583a8f
commit
9ab7ae3e60
|
|
@ -10,7 +10,14 @@ export abstract class GameHostScene<TState extends Record<string, unknown>>
|
||||||
extends ReactiveScene<GameHostSceneOptions<TState>>
|
extends ReactiveScene<GameHostSceneOptions<TState>>
|
||||||
{
|
{
|
||||||
public get gameHost(): GameHost<TState> {
|
public get gameHost(): GameHost<TState> {
|
||||||
return this.initData.gameHost as GameHost<TState>;
|
const gameHost = this.initData.gameHost as GameHost<TState>;
|
||||||
|
if (!gameHost) {
|
||||||
|
throw new Error(
|
||||||
|
`GameHostScene (${this.scene.key}): gameHost 未提供。` +
|
||||||
|
`确保在 PhaserScene 组件的 data 属性中传入 gameHost。`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return gameHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get state(): TState {
|
public get state(): TState {
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,19 @@ export abstract class ReactiveScene<TData extends Record<string, unknown> = {}>
|
||||||
implements IDisposable
|
implements IDisposable
|
||||||
{
|
{
|
||||||
protected disposables = new DisposableBag();
|
protected disposables = new DisposableBag();
|
||||||
private _initData!: TData & ReactiveScenePhaserData;
|
private _initData?: TData & ReactiveScenePhaserData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取通过 init() 注入的数据
|
* 获取通过 init() 注入的数据
|
||||||
* 在 create() 阶段保证可用
|
* 在 create() 阶段保证可用
|
||||||
*/
|
*/
|
||||||
public get initData(): TData & ReactiveScenePhaserData {
|
public get initData(): TData & ReactiveScenePhaserData {
|
||||||
|
if (!this._initData) {
|
||||||
|
throw new Error(
|
||||||
|
`ReactiveScene (${this.scene.key}): initData 尚未初始化。` +
|
||||||
|
`确保场景通过 PhaserScene 组件注册,并在 create() 阶段访问。`
|
||||||
|
);
|
||||||
|
}
|
||||||
return this._initData;
|
return this._initData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,14 +49,14 @@ export abstract class ReactiveScene<TData extends Record<string, unknown> = {}>
|
||||||
* 获取 Phaser game 实例的响应式信号
|
* 获取 Phaser game 实例的响应式信号
|
||||||
*/
|
*/
|
||||||
public get phaserGame(): ReadonlySignal<{ game: Phaser.Game }> {
|
public get phaserGame(): ReadonlySignal<{ game: Phaser.Game }> {
|
||||||
return this._initData.phaserGame;
|
return this.initData.phaserGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取场景控制器
|
* 获取场景控制器
|
||||||
*/
|
*/
|
||||||
public get sceneController(): SceneController {
|
public get sceneController(): SceneController {
|
||||||
return this._initData.sceneController;
|
return this.initData.sceneController;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(key?: string) {
|
constructor(key?: string) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import Phaser from 'phaser';
|
import Phaser from 'phaser';
|
||||||
import { signal, useSignal, useSignalEffect } from '@preact/signals';
|
import { signal, useSignal, useSignalEffect } from '@preact/signals';
|
||||||
import { createContext, h } from 'preact';
|
import { createContext, h } from 'preact';
|
||||||
import { useContext } from 'preact/hooks';
|
import { useContext, useEffect, useRef } from 'preact/hooks';
|
||||||
import {ReadonlySignal} from "@preact/signals-core";
|
import { ReadonlySignal } from "@preact/signals-core";
|
||||||
import type { ReactiveScene } from '../scenes';
|
import type { ReactiveScene } from '../scenes';
|
||||||
import { FadeScene as FadeSceneClass, FADE_SCENE_KEY } from '../scenes/FadeScene';
|
import { FadeScene as FadeSceneClass, FADE_SCENE_KEY } from '../scenes/FadeScene';
|
||||||
|
|
||||||
|
|
@ -38,9 +38,17 @@ export interface PhaserGameProps {
|
||||||
children?: any;
|
children?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 存储待注册的场景配置 */
|
||||||
|
interface SceneRegistration<TData extends Record<string, unknown> = {}> {
|
||||||
|
sceneKey: string;
|
||||||
|
scene: ReactiveScene<TData>;
|
||||||
|
initData?: TData;
|
||||||
|
}
|
||||||
|
|
||||||
export function PhaserGame(props: PhaserGameProps) {
|
export function PhaserGame(props: PhaserGameProps) {
|
||||||
const gameSignal = useSignal<PhaserGameContext>({ game: undefined!, sceneController: undefined! });
|
const gameSignal = useSignal<PhaserGameContext>({ game: undefined!, sceneController: undefined! });
|
||||||
const initialSceneLaunched = useSignal(false);
|
const scenesRef = useRef<Map<string, SceneRegistration>>(new Map());
|
||||||
|
const initialSceneLaunched = useRef(false);
|
||||||
|
|
||||||
useSignalEffect(() => {
|
useSignalEffect(() => {
|
||||||
const config: Phaser.Types.Core.GameConfig = {
|
const config: Phaser.Types.Core.GameConfig = {
|
||||||
|
|
@ -64,6 +72,12 @@ export function PhaserGame(props: PhaserGameProps) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证场景是否已注册
|
||||||
|
if (!phaserGame.scene.getScene(sceneKey) && !scenesRef.current.has(sceneKey)) {
|
||||||
|
console.error(`SceneController: 场景 "${sceneKey}" 未注册`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isTransitioning.value = true;
|
isTransitioning.value = true;
|
||||||
const fade = phaserGame.scene.getScene(FADE_SCENE_KEY) as FadeSceneClass;
|
const fade = phaserGame.scene.getScene(FADE_SCENE_KEY) as FadeSceneClass;
|
||||||
|
|
||||||
|
|
@ -75,6 +89,23 @@ export function PhaserGame(props: PhaserGameProps) {
|
||||||
phaserGame.scene.stop(currentScene.value);
|
phaserGame.scene.stop(currentScene.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保场景已注册后再启动
|
||||||
|
if (!phaserGame.scene.getScene(sceneKey)) {
|
||||||
|
const registration = scenesRef.current.get(sceneKey);
|
||||||
|
if (registration) {
|
||||||
|
phaserGame.scene.add(
|
||||||
|
sceneKey,
|
||||||
|
registration.scene,
|
||||||
|
false,
|
||||||
|
{
|
||||||
|
...registration.initData,
|
||||||
|
phaserGame: gameSignal,
|
||||||
|
sceneController,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 启动新场景
|
// 启动新场景
|
||||||
phaserGame.scene.start(sceneKey);
|
phaserGame.scene.start(sceneKey);
|
||||||
currentScene.value = sceneKey;
|
currentScene.value = sceneKey;
|
||||||
|
|
@ -91,19 +122,23 @@ export function PhaserGame(props: PhaserGameProps) {
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
gameSignal.value = { game: undefined!, sceneController: undefined! };
|
gameSignal.value = { game: undefined!, sceneController: undefined! };
|
||||||
initialSceneLaunched.value = false;
|
scenesRef.current.clear();
|
||||||
|
initialSceneLaunched.current = false;
|
||||||
phaserGame.destroy(true);
|
phaserGame.destroy(true);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 启动初始场景
|
// 启动初始场景(仅一次)
|
||||||
useSignalEffect(() => {
|
useEffect(() => {
|
||||||
const ctx = gameSignal.value;
|
const ctx = gameSignal.value;
|
||||||
if (!initialSceneLaunched.value && props.initialScene && ctx?.sceneController) {
|
if (!initialSceneLaunched.current && props.initialScene && ctx?.sceneController) {
|
||||||
initialSceneLaunched.value = true;
|
initialSceneLaunched.current = true;
|
||||||
ctx.sceneController.launch(props.initialScene);
|
// 使用 microtask 确保所有子组件的场景注册已完成
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
ctx.sceneController.launch(props.initialScene!);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
}, [gameSignal.value, props.initialScene]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="phaser-container" className="w-full h-full">
|
<div id="phaser-container" className="w-full h-full">
|
||||||
|
|
@ -122,9 +157,11 @@ export interface PhaserSceneProps<TData extends Record<string, unknown> = {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const phaserSceneContext = createContext<ReadonlySignal<ReactiveScene> | null>(null);
|
export const phaserSceneContext = createContext<ReadonlySignal<ReactiveScene> | null>(null);
|
||||||
|
|
||||||
export function PhaserScene<TData extends Record<string, unknown> = {}>(props: PhaserSceneProps<TData>) {
|
export function PhaserScene<TData extends Record<string, unknown> = {}>(props: PhaserSceneProps<TData>) {
|
||||||
const phaserGameSignal = useContext(phaserContext);
|
const phaserGameSignal = useContext(phaserContext);
|
||||||
const sceneSignal = useSignal<ReactiveScene<TData>>();
|
const sceneSignal = useSignal<ReactiveScene<TData>>();
|
||||||
|
const registered = useRef(false);
|
||||||
|
|
||||||
useSignalEffect(() => {
|
useSignalEffect(() => {
|
||||||
if (!phaserGameSignal) return;
|
if (!phaserGameSignal) return;
|
||||||
|
|
@ -132,20 +169,23 @@ export function PhaserScene<TData extends Record<string, unknown> = {}>(props: P
|
||||||
if (!ctx?.game) return;
|
if (!ctx?.game) return;
|
||||||
|
|
||||||
const game = ctx.game;
|
const game = ctx.game;
|
||||||
const initData = {
|
|
||||||
...props.data,
|
|
||||||
phaserGame: phaserGameSignal,
|
|
||||||
sceneController: ctx.sceneController,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 注册场景但不启动
|
// 注册场景到 Phaser(但不启动)
|
||||||
if (!game.scene.getScene(props.sceneKey)) {
|
if (!game.scene.getScene(props.sceneKey)) {
|
||||||
|
const initData = {
|
||||||
|
...props.data,
|
||||||
|
phaserGame: phaserGameSignal,
|
||||||
|
sceneController: ctx.sceneController,
|
||||||
|
};
|
||||||
game.scene.add(props.sceneKey, props.scene, false, initData);
|
game.scene.add(props.sceneKey, props.scene, false, initData);
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneSignal.value = props.scene;
|
sceneSignal.value = props.scene;
|
||||||
|
registered.current = true;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
sceneSignal.value = undefined;
|
sceneSignal.value = undefined;
|
||||||
|
registered.current = false;
|
||||||
// 不在这里移除场景,让 SceneController 管理生命周期
|
// 不在这里移除场景,让 SceneController 管理生命周期
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue