Style: enforce 2-space indent and double quotes
This commit is contained in:
@@ -1,89 +1,83 @@
|
||||
import Phaser from 'phaser';
|
||||
import { ReactiveScene, type ReactiveScenePhaserData } from './ReactiveScene';
|
||||
import { ReactiveScene } from "./ReactiveScene";
|
||||
|
||||
export interface FadeSceneData {
|
||||
[key: string]: unknown;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理淡入淡出到黑色的过渡场景
|
||||
*/
|
||||
export class FadeScene extends ReactiveScene<FadeSceneData> {
|
||||
private overlay!: Phaser.GameObjects.Rectangle;
|
||||
private isFading = false;
|
||||
private overlay!: Phaser.GameObjects.Rectangle;
|
||||
private isFading = false;
|
||||
|
||||
constructor() {
|
||||
super(FADE_SCENE_KEY);
|
||||
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> {
|
||||
// 如果 overlay 还未初始化,直接返回 resolved promise
|
||||
if (!this.overlay) {
|
||||
console.warn("FadeScene: overlay 未初始化,跳过过渡动画");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
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();
|
||||
if (this.isFading) {
|
||||
console.warn("FadeScene: 正在进行过渡动画");
|
||||
}
|
||||
|
||||
/**
|
||||
* 淡入(从黑色到透明)
|
||||
* @param duration 动画时长(毫秒)
|
||||
*/
|
||||
fadeIn(duration = 300): Promise<void> {
|
||||
return this.fadeTo(0, duration);
|
||||
}
|
||||
this.isFading = true;
|
||||
this.overlay.setAlpha(targetAlpha === 1 ? 0 : 1);
|
||||
|
||||
/**
|
||||
* 淡出(从透明到黑色)
|
||||
* @param duration 动画时长(毫秒)
|
||||
*/
|
||||
fadeOut(duration = 300): Promise<void> {
|
||||
return this.fadeTo(1, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 淡入淡出到指定透明度
|
||||
*/
|
||||
private fadeTo(targetAlpha: number, duration: number): Promise<void> {
|
||||
// 如果 overlay 还未初始化,直接返回 resolved promise
|
||||
if (!this.overlay) {
|
||||
console.warn('FadeScene: overlay 未初始化,跳过过渡动画');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
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();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
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__';
|
||||
export const FADE_SCENE_KEY = "__fade__";
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
import type { GameHost } from 'boardgame-core';
|
||||
import { ReactiveScene } from './ReactiveScene';
|
||||
import { ReactiveScene } from "./ReactiveScene";
|
||||
|
||||
import type { GameHost } from "boardgame-core";
|
||||
|
||||
export interface GameHostSceneOptions<TState extends Record<string, unknown>> {
|
||||
gameHost: GameHost<TState>;
|
||||
[key: string]: unknown;
|
||||
gameHost: GameHost<TState>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export abstract class GameHostScene<TState extends Record<string, unknown>>
|
||||
extends ReactiveScene<GameHostSceneOptions<TState>>
|
||||
{
|
||||
public get gameHost(): 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;
|
||||
export abstract class GameHostScene<
|
||||
TState extends Record<string, unknown>,
|
||||
> extends ReactiveScene<GameHostSceneOptions<TState>> {
|
||||
public get gameHost(): 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 {
|
||||
return this.gameHost?.state.value;
|
||||
}
|
||||
public get state(): TState {
|
||||
return this.gameHost?.state.value;
|
||||
}
|
||||
|
||||
addInterruption(promise: Promise<void>){
|
||||
this.gameHost?.addInterruption(promise);
|
||||
}
|
||||
addInterruption(promise: Promise<void>) {
|
||||
this.gameHost?.addInterruption(promise);
|
||||
}
|
||||
|
||||
addTweenInterruption(tween: Phaser.Tweens.Tween){
|
||||
this.addInterruption(new Promise(
|
||||
resolve => tween.once('complete', resolve)
|
||||
));
|
||||
}
|
||||
addTweenInterruption(tween: Phaser.Tweens.Tween) {
|
||||
this.addInterruption(
|
||||
new Promise((resolve) => tween.once("complete", resolve)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,90 @@
|
||||
import Phaser from 'phaser';
|
||||
import { effect, type ReadonlySignal } from '@preact/signals-core';
|
||||
import { DisposableBag, type IDisposable } from '../utils';
|
||||
import { effect, type ReadonlySignal } from "@preact/signals-core";
|
||||
import { Scene } from "phaser";
|
||||
|
||||
import { DisposableBag, type IDisposable } from "../utils";
|
||||
|
||||
import type Phaser from "phaser";
|
||||
|
||||
type CleanupFn = void | (() => void);
|
||||
|
||||
// 前向声明,避免循环导入
|
||||
export interface SceneController {
|
||||
launch(sceneKey: string): Promise<void>;
|
||||
restart(): Promise<void>;
|
||||
currentScene: ReadonlySignal<string | null>;
|
||||
isTransitioning: ReadonlySignal<boolean>;
|
||||
launch(sceneKey: string): Promise<void>;
|
||||
restart(): Promise<void>;
|
||||
currentScene: ReadonlySignal<string | null>;
|
||||
isTransitioning: ReadonlySignal<boolean>;
|
||||
}
|
||||
|
||||
export interface ReactiveScenePhaserData {
|
||||
phaserGame: ReadonlySignal<{ game: Phaser.Game }>;
|
||||
sceneController: SceneController;
|
||||
phaserGame: ReadonlySignal<{ game: Phaser.Game }>;
|
||||
sceneController: SceneController;
|
||||
}
|
||||
|
||||
export interface ReactiveSceneOptions<TData extends Record<string, unknown> = {}> {
|
||||
key?: string;
|
||||
export interface ReactiveSceneOptions {
|
||||
key?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的响应式 Scene 基类
|
||||
* @typeparam TData - 通过 init(data) 接收的数据类型(必须包含 phaserGame 和 sceneController)
|
||||
*/
|
||||
export abstract class ReactiveScene<TData extends Record<string, unknown> = {}>
|
||||
extends Phaser.Scene
|
||||
implements IDisposable
|
||||
export abstract class ReactiveScene<TData = object>
|
||||
extends Scene
|
||||
implements IDisposable
|
||||
{
|
||||
protected disposables = new DisposableBag();
|
||||
private _initData?: TData & ReactiveScenePhaserData;
|
||||
protected disposables = new DisposableBag();
|
||||
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;
|
||||
/**
|
||||
* 获取通过 init() 注入的数据
|
||||
* 在 create() 阶段保证可用
|
||||
*/
|
||||
public get initData(): TData & ReactiveScenePhaserData {
|
||||
if (!this._initData) {
|
||||
throw new Error(
|
||||
`ReactiveScene (${this.scene.key}): initData 尚未初始化。` +
|
||||
"确保场景通过 PhaserScene 组件注册,并在 create() 阶段访问。",
|
||||
);
|
||||
}
|
||||
return this._initData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Phaser game 实例的响应式信号
|
||||
*/
|
||||
public get phaserGame(): ReadonlySignal<{ game: Phaser.Game }> {
|
||||
return this.initData.phaserGame;
|
||||
}
|
||||
/**
|
||||
* 获取 Phaser game 实例的响应式信号
|
||||
*/
|
||||
public get phaserGame(): ReadonlySignal<{ game: Phaser.Game }> {
|
||||
return this.initData.phaserGame;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景控制器
|
||||
*/
|
||||
public get sceneController(): SceneController {
|
||||
return this.initData.sceneController;
|
||||
}
|
||||
/**
|
||||
* 获取场景控制器
|
||||
*/
|
||||
public get sceneController(): SceneController {
|
||||
return this.initData.sceneController;
|
||||
}
|
||||
|
||||
constructor(key?: string) {
|
||||
super(key);
|
||||
}
|
||||
constructor(key?: string) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
init(data: TData & ReactiveScenePhaserData): void {
|
||||
this._initData = data;
|
||||
}
|
||||
init(data: TData & ReactiveScenePhaserData): void {
|
||||
this._initData = data;
|
||||
}
|
||||
|
||||
create(): void {
|
||||
this.events.on('shutdown', this.dispose, this);
|
||||
}
|
||||
create(): void {
|
||||
this.events.on("shutdown", this.dispose, this);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
|
||||
public addDisposable(disposable: IDisposable): void {
|
||||
this.disposables.add(disposable);
|
||||
}
|
||||
public addDisposable(disposable: IDisposable): void {
|
||||
this.disposables.add(disposable);
|
||||
}
|
||||
|
||||
/** 注册响应式监听(场景关闭时自动清理) */
|
||||
public addEffect(fn: () => CleanupFn): void {
|
||||
this.disposables.add(effect(fn));
|
||||
}
|
||||
/** 注册响应式监听(场景关闭时自动清理) */
|
||||
public addEffect(fn: () => CleanupFn): void {
|
||||
this.disposables.add(effect(fn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
export { ReactiveScene } from './ReactiveScene';
|
||||
export type { ReactiveSceneOptions, ReactiveScenePhaserData, SceneController } from './ReactiveScene';
|
||||
export { ReactiveScene } from "./ReactiveScene";
|
||||
export type {
|
||||
ReactiveSceneOptions,
|
||||
ReactiveScenePhaserData,
|
||||
SceneController,
|
||||
} from "./ReactiveScene";
|
||||
|
||||
export { FadeScene, FADE_SCENE_KEY } from './FadeScene';
|
||||
export type { FadeSceneData } from './FadeScene';
|
||||
export { FadeScene, FADE_SCENE_KEY } from "./FadeScene";
|
||||
export type { FadeSceneData } from "./FadeScene";
|
||||
|
||||
export { GameHostScene } from './GameHostScene';
|
||||
export type { GameHostSceneOptions } from './GameHostScene';
|
||||
export { GameHostScene } from "./GameHostScene";
|
||||
export type { GameHostSceneOptions } from "./GameHostScene";
|
||||
|
||||
Reference in New Issue
Block a user