feat: scene manager and transitions

This commit is contained in:
hyper
2026-04-12 16:52:53 +08:00
parent f8a19653ba
commit 50531446c2
9 changed files with 318 additions and 39 deletions
@@ -0,0 +1,83 @@
import Phaser from 'phaser';
import { ReactiveScene, type ReactiveScenePhaserData } from './ReactiveScene';
export interface FadeSceneData {
[key: string]: unknown;
}
/**
* 处理淡入淡出到黑色的过渡场景
*/
export class FadeScene extends ReactiveScene<FadeSceneData> {
private overlay!: Phaser.GameObjects.Rectangle;
private isFading = false;
constructor() {
super(FADE_SCENE_KEY);
}
create(): void {
super.create();
// 创建黑色遮罩层,覆盖整个游戏区域
const game = this.game;
this.overlay = this.add.rectangle(
0,
0,
game.scale.width,
game.scale.height,
0x000000,
1
).setOrigin(0)
.setAlpha(1)
.setDepth(999999)
.setInteractive({ useHandCursor: false });
// 防止遮罩阻挡输入
this.overlay.disableInteractive();
}
/**
* 淡入(从黑色到透明)
* @param duration 动画时长(毫秒)
*/
fadeIn(duration = 300): Promise<void> {
return this.fadeTo(0, duration);
}
/**
* 淡出(从透明到黑色)
* @param duration 动画时长(毫秒)
*/
fadeOut(duration = 300): Promise<void> {
return this.fadeTo(1, duration);
}
/**
* 淡入淡出到指定透明度
*/
private fadeTo(targetAlpha: number, duration: number): Promise<void> {
if (this.isFading) {
console.warn('FadeScene: 正在进行过渡动画');
}
this.isFading = true;
this.overlay.setAlpha(targetAlpha === 1 ? 0 : 1);
return new Promise<void>((resolve) => {
this.tweens.add({
targets: this.overlay,
alpha: targetAlpha,
duration,
ease: 'Linear',
onComplete: () => {
this.isFading = false;
resolve();
},
});
});
}
}
// 导出常量供 PhaserGame 使用
export const FADE_SCENE_KEY = '__fade__';
+17 -6
View File
@@ -1,15 +1,19 @@
import Phaser from 'phaser';
import { effect } from '@preact/signals-core';
import { effect, type ReadonlySignal } from '@preact/signals-core';
import { DisposableBag, type IDisposable } from '../utils';
type CleanupFn = void | (() => void);
export interface PhaserGameContext {
game: Phaser.Game;
// 前向声明,避免循环导入
export interface SceneController {
launch(sceneKey: string, data?: Record<string, unknown>): Promise<void>;
currentScene: ReadonlySignal<string | null>;
isTransitioning: ReadonlySignal<boolean>;
}
export interface ReactiveScenePhaserData {
phaserGame: PhaserGameContext;
phaserGame: ReadonlySignal<{ game: Phaser.Game }>;
sceneController: SceneController;
}
export interface ReactiveSceneOptions<TData extends Record<string, unknown> = {}> {
@@ -18,7 +22,7 @@ export interface ReactiveSceneOptions<TData extends Record<string, unknown> = {}
/**
* 通用的响应式 Scene 基类
* @typeparam TData - 通过 init(data) 接收的数据类型(必须包含 phaserGame
* @typeparam TData - 通过 init(data) 接收的数据类型(必须包含 phaserGame 和 sceneController
*/
export abstract class ReactiveScene<TData extends Record<string, unknown> = {}>
extends Phaser.Scene
@@ -38,10 +42,17 @@ export abstract class ReactiveScene<TData extends Record<string, unknown> = {}>
/**
* 获取 Phaser game 实例的响应式信号
*/
public get phaserGame(): PhaserGameContext {
public get phaserGame(): ReadonlySignal<{ game: Phaser.Game }> {
return this._initData.phaserGame;
}
/**
* 获取场景控制器
*/
public get sceneController(): SceneController {
return this._initData.sceneController;
}
constructor(key?: string) {
super(key);
}
+4 -1
View File
@@ -1,5 +1,8 @@
export { ReactiveScene } from './ReactiveScene';
export type { ReactiveSceneOptions, ReactiveScenePhaserData } from './ReactiveScene';
export type { ReactiveSceneOptions, ReactiveScenePhaserData, SceneController } from './ReactiveScene';
export { FadeScene, FADE_SCENE_KEY } from './FadeScene';
export type { FadeSceneData } from './FadeScene';
export { GameHostScene } from './GameHostScene';
export type { GameHostSceneOptions } from './GameHostScene';