import { signal, useSignal, useSignalEffect } from "@preact/signals"; import Phaser, { AUTO } from "phaser"; import { createContext } from "preact"; import { useContext, useEffect, useRef } from "preact/hooks"; import { FadeScene as FadeSceneClass, FADE_SCENE_KEY, } from "../scenes/FadeScene"; import type { ReactiveScene } from "../scenes"; import type { ReadonlySignal } from "@preact/signals-core"; export interface SceneController { /** 启动场景(带淡入淡出过渡) */ launch(sceneKey: string): Promise; /** 重新启动当前场景(带淡入淡出过渡) */ restart(): 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: AUTO, width: 560, height: 560, parent: "phaser-container", backgroundColor: "#f9fafb", scene: [], disableContextMenu: true, }; export interface PhaserGameProps { config?: Partial; /** 初始启动的场景 key */ initialScene?: string; children?: preact.ComponentChildren; } 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; }, async restart() { if (isTransitioning.value) { console.warn("SceneController: 正在进行场景切换"); return; } if (!currentScene.value) { console.warn("SceneController: 没有当前场景,无法 restart"); return; } const sceneKey = currentScene.value; const scene = phaserGame.scene.getScene(sceneKey); if (!scene) { 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); // 重启当前场景 scene.scene.restart(); // 淡入 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 | { new (): 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(但不启动) const scene = "scene" in props.scene ? props.scene : new props.scene(); const sceneKey = props.sceneKey ?? scene.sys.settings.key; if (!game.scene.getScene(sceneKey)) { const initData = { ...props.data, phaserGame: phaserGameSignal, sceneController: ctx.sceneController, }; game.scene.add(sceneKey, props.scene, false, initData); } sceneSignal.value = scene; registered.current = true; return () => { sceneSignal.value = undefined; registered.current = false; // 不在这里移除场景,让 SceneController 管理生命周期 }; }); return ( } > {props.children} ); }