import Phaser from 'phaser'; import { signal, useSignal, useSignalEffect } from '@preact/signals'; import { createContext, h } from 'preact'; 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'; export interface SceneController { /** 启动场景(带淡入淡出过渡) */ launch(sceneKey: string): Promise; /** 当前活跃场景 key */ currentScene: ReadonlySignal; /** 是否正在过渡 */ isTransitioning: ReadonlySignal; } export interface PhaserGameContext { game: Phaser.Game; sceneController: SceneController; } export const phaserContext = createContext | null>(null); export const defaultPhaserConfig: Phaser.Types.Core.GameConfig = { type: Phaser.AUTO, width: 560, height: 560, parent: 'phaser-container', backgroundColor: '#f9fafb', scene: [], }; export interface PhaserGameProps { config?: Partial; /** 初始启动的场景 key */ initialScene?: string; children?: any; } export function PhaserGame(props: PhaserGameProps) { const gameSignal = useSignal({ game: undefined!, sceneController: undefined! }); const initialSceneLaunched = useRef(false); useSignalEffect(() => { const config: Phaser.Types.Core.GameConfig = { ...defaultPhaserConfig, ...props.config, }; const phaserGame = new Phaser.Game(config); // 添加 FadeScene 并启动它来初始化 overlay const fadeScene = new FadeSceneClass(); phaserGame.scene.add(FADE_SCENE_KEY, fadeScene, true); // 改为 true 以触发 create // 创建 SceneController const currentScene = signal(null); const isTransitioning = signal(false); const sceneController: SceneController = { async launch(sceneKey: string) { if (isTransitioning.value) { console.warn('SceneController: 正在进行场景切换'); return; } // 等待场景注册完成(最多等待 100ms) let retries = 0; while (!phaserGame.scene.getScene(sceneKey) && retries < 10) { await new Promise(resolve => setTimeout(resolve, 10)); retries++; } // 验证场景是否已注册 if (!phaserGame.scene.getScene(sceneKey)) { console.error(`SceneController: 场景 "${sceneKey}" 未注册`); return; } isTransitioning.value = true; const fade = phaserGame.scene.getScene(FADE_SCENE_KEY) as FadeSceneClass; // 淡出到黑色 phaserGame.scene.bringToTop(FADE_SCENE_KEY); await fade.fadeOut(300); // 停止当前场景 if (currentScene.value) { phaserGame.scene.stop(currentScene.value); } // 确保场景已注册后再启动 // (场景应该已经在 PhaserScene 组件中注册) if (!phaserGame.scene.getScene(sceneKey)) { console.error(`SceneController: 场景 "${sceneKey}" 在切换时仍未注册`); isTransitioning.value = false; return; } // 启动新场景 phaserGame.scene.start(sceneKey); currentScene.value = sceneKey; // 淡入 await fade.fadeIn(300); isTransitioning.value = false; }, currentScene, isTransitioning, }; gameSignal.value = { game: phaserGame, sceneController }; return () => { gameSignal.value = { game: undefined!, sceneController: undefined! }; initialSceneLaunched.current = false; phaserGame.destroy(true); }; }); // 启动初始场景(仅一次) useEffect(() => { const ctx = gameSignal.value; if (!initialSceneLaunched.current && props.initialScene && ctx?.sceneController) { initialSceneLaunched.current = true; // 使用 microtask 确保所有子组件的场景注册已完成 Promise.resolve().then(() => { ctx.sceneController.launch(props.initialScene!); }); } }, [gameSignal.value, props.initialScene]); return (
{props.children}
); } export interface PhaserSceneProps = {}> { sceneKey: string; scene: ReactiveScene; data?: TData; children?: any; } 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; const ctx = phaserGameSignal.value; if (!ctx?.game) return; const game = ctx.game; // 注册场景到 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 管理生命周期 }; }); return }>{props.children}; }