From 9ab7ae3e60223cb85f192c39c261ad5906b8798c Mon Sep 17 00:00:00 2001 From: hyper Date: Sun, 12 Apr 2026 17:52:44 +0800 Subject: [PATCH] refactor: improved scene manager --- .../framework/src/scenes/GameHostScene.ts | 9 ++- .../framework/src/scenes/ReactiveScene.ts | 12 +++- packages/framework/src/ui/PhaserBridge.tsx | 72 ++++++++++++++----- 3 files changed, 73 insertions(+), 20 deletions(-) diff --git a/packages/framework/src/scenes/GameHostScene.ts b/packages/framework/src/scenes/GameHostScene.ts index 86faa85..1c51959 100644 --- a/packages/framework/src/scenes/GameHostScene.ts +++ b/packages/framework/src/scenes/GameHostScene.ts @@ -10,7 +10,14 @@ export abstract class GameHostScene> extends ReactiveScene> { public get gameHost(): GameHost { - return this.initData.gameHost as GameHost; + const gameHost = this.initData.gameHost as GameHost; + if (!gameHost) { + throw new Error( + `GameHostScene (${this.scene.key}): gameHost 未提供。` + + `确保在 PhaserScene 组件的 data 属性中传入 gameHost。` + ); + } + return gameHost; } public get state(): TState { diff --git a/packages/framework/src/scenes/ReactiveScene.ts b/packages/framework/src/scenes/ReactiveScene.ts index 645e969..6b8dc5c 100644 --- a/packages/framework/src/scenes/ReactiveScene.ts +++ b/packages/framework/src/scenes/ReactiveScene.ts @@ -29,13 +29,19 @@ export abstract class ReactiveScene = {}> implements IDisposable { protected disposables = new DisposableBag(); - private _initData!: TData & ReactiveScenePhaserData; + private _initData?: TData & ReactiveScenePhaserData; /** * 获取通过 init() 注入的数据 * 在 create() 阶段保证可用 */ public get initData(): TData & ReactiveScenePhaserData { + if (!this._initData) { + throw new Error( + `ReactiveScene (${this.scene.key}): initData 尚未初始化。` + + `确保场景通过 PhaserScene 组件注册,并在 create() 阶段访问。` + ); + } return this._initData; } @@ -43,14 +49,14 @@ export abstract class ReactiveScene = {}> * 获取 Phaser game 实例的响应式信号 */ public get phaserGame(): ReadonlySignal<{ game: Phaser.Game }> { - return this._initData.phaserGame; + return this.initData.phaserGame; } /** * 获取场景控制器 */ public get sceneController(): SceneController { - return this._initData.sceneController; + return this.initData.sceneController; } constructor(key?: string) { diff --git a/packages/framework/src/ui/PhaserBridge.tsx b/packages/framework/src/ui/PhaserBridge.tsx index 947c2f5..3ed1158 100644 --- a/packages/framework/src/ui/PhaserBridge.tsx +++ b/packages/framework/src/ui/PhaserBridge.tsx @@ -1,8 +1,8 @@ import Phaser from 'phaser'; import { signal, useSignal, useSignalEffect } from '@preact/signals'; import { createContext, h } from 'preact'; -import { useContext } from 'preact/hooks'; -import {ReadonlySignal} from "@preact/signals-core"; +import { useContext, useEffect, useRef } from 'preact/hooks'; +import { ReadonlySignal } from "@preact/signals-core"; import type { ReactiveScene } from '../scenes'; import { FadeScene as FadeSceneClass, FADE_SCENE_KEY } from '../scenes/FadeScene'; @@ -38,9 +38,17 @@ export interface PhaserGameProps { children?: any; } +/** 存储待注册的场景配置 */ +interface SceneRegistration = {}> { + sceneKey: string; + scene: ReactiveScene; + initData?: TData; +} + export function PhaserGame(props: PhaserGameProps) { const gameSignal = useSignal({ game: undefined!, sceneController: undefined! }); - const initialSceneLaunched = useSignal(false); + const scenesRef = useRef>(new Map()); + const initialSceneLaunched = useRef(false); useSignalEffect(() => { const config: Phaser.Types.Core.GameConfig = { @@ -64,6 +72,12 @@ export function PhaserGame(props: PhaserGameProps) { return; } + // 验证场景是否已注册 + if (!phaserGame.scene.getScene(sceneKey) && !scenesRef.current.has(sceneKey)) { + console.error(`SceneController: 场景 "${sceneKey}" 未注册`); + return; + } + isTransitioning.value = true; const fade = phaserGame.scene.getScene(FADE_SCENE_KEY) as FadeSceneClass; @@ -75,6 +89,23 @@ export function PhaserGame(props: PhaserGameProps) { 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); currentScene.value = sceneKey; @@ -91,19 +122,23 @@ export function PhaserGame(props: PhaserGameProps) { return () => { gameSignal.value = { game: undefined!, sceneController: undefined! }; - initialSceneLaunched.value = false; + scenesRef.current.clear(); + initialSceneLaunched.current = false; phaserGame.destroy(true); }; }); - // 启动初始场景 - useSignalEffect(() => { + // 启动初始场景(仅一次) + useEffect(() => { const ctx = gameSignal.value; - if (!initialSceneLaunched.value && props.initialScene && ctx?.sceneController) { - initialSceneLaunched.value = true; - ctx.sceneController.launch(props.initialScene); + if (!initialSceneLaunched.current && props.initialScene && ctx?.sceneController) { + initialSceneLaunched.current = true; + // 使用 microtask 确保所有子组件的场景注册已完成 + Promise.resolve().then(() => { + ctx.sceneController.launch(props.initialScene!); + }); } - }); + }, [gameSignal.value, props.initialScene]); return (
@@ -122,9 +157,11 @@ export interface PhaserSceneProps = {}> { } export const phaserSceneContext = createContext | null>(null); + export function PhaserScene = {}>(props: PhaserSceneProps) { const phaserGameSignal = useContext(phaserContext); const sceneSignal = useSignal>(); + const registered = useRef(false); useSignalEffect(() => { if (!phaserGameSignal) return; @@ -132,20 +169,23 @@ export function PhaserScene = {}>(props: P if (!ctx?.game) return; const game = ctx.game; - const initData = { - ...props.data, - phaserGame: phaserGameSignal, - sceneController: ctx.sceneController, - }; - // 注册场景但不启动 + // 注册场景到 Phaser(但不启动) if (!game.scene.getScene(props.sceneKey)) { + const initData = { + ...props.data, + phaserGame: phaserGameSignal, + sceneController: ctx.sceneController, + }; game.scene.add(props.sceneKey, props.scene, false, initData); } + sceneSignal.value = props.scene; + registered.current = true; return () => { sceneSignal.value = undefined; + registered.current = false; // 不在这里移除场景,让 SceneController 管理生命周期 }; });