boardgame-phaser/QWEN.md

3.8 KiB
Raw Blame History

boardgame-phaser

基于Phaser3/boardgame-core的游戏框架

概述

项目使用pnpm monorepo管理包含以下包

  • framework:通用框架
  • boop-gameboop样例
  • sample-gametic tac toe样例

游戏应当使用vite构建基于preact/signals进行状态管理,使用phaser3实现游戏功能。

boardgame-core

项目使用boardgame-core进行游戏定义。 boardgame-core的内容可以在framework/node_modules/boardgame-core找到。 这个文件夹被.gitignore忽略查看时需要绕开这一限制。

编写GameModule

游戏逻辑以GameModule的形式定义

import {createGameCommandRegistry, IGameContext} from "boardgame-core";

// 创建mutative游戏初始状态
export function createInitialState(): GameState {
    //...
}

// 运行游戏
export async function start(game: IGameContext<GameState>) {
    // ...
}

// 可选
export const registry = createGameCommandRegistry();

使用以下步骤创建GameModule

1. 定义状态

通常使用一个regions: Record<string, Region>和一个parts: Record<string, Part<TMeta>>来记录桌游物件的摆放。

import {Region} from "boardgame-core";

type GameState = {
    regions: {
        white: Region,
        black: Region,
        board: Region,
    },
    pieces: Record<string, Part<PartsTable['0']>>,
    currentPlayer: PlayerType,
    winner: WinnerType,
};

游戏的部件可以从csv加载。详情见boop-game/node_modules/inline-schema/

/// parts.csv
type, player, count
string, string, number
cat, white, 8
cat, black, 8

/// parts.csv.d.ts
type PartsTable = {
    type: string;
    player: string;
    count: number;
  }[];

declare const data: PartsTable;
export default data;

2. 定义流程

使用async function start(game: IGameContext<GameState>)作为入口。

使用game.value读取游戏当前状态

async function gameEnd(game: IGameContext<GameState>) {
    return game.value.winner;
}

需要修改状态时,使用game.producegame.produceAsync

async function start(game: IGameContext<GameState>) {
    await game.produceAsync(state => {
        state.currentPlayer = 'white';
    });
}

需要等待玩家交互时,使用await game.prompt(schema, validator, player)

async function turn(game: IGameContext<GameState>, currentPlayer: PlayerType) {
    const {player, row, col} = await game.prompt(
        'play <player:string> <row:number> <col:number>',
        (player, row, col) => {
            if(player !== currentPlayer)
                throw `Wrong player!`
            return {player, row, col};
        }
    )
}

3. 编写测试

使用vitest编写测试,测试应当使用GameHost来模拟游戏环境。

编写Phaser App

使用framework/src/ui/PhaserBridge来创建Phaser App。

使用framework/src/scenes/GameHostScene来创建游戏场景。

使用GameHost来控制游戏状态。

export class GameHost<TState extends Record<string, unknown>, TResult=unknown> {
    // 获取游戏状态的只读快照
    get state(): TState{}
    // 运行状态
    readonly status: ReadonlySignal<GameHostStatus>;

    // 运行中途需要玩家输入时使用
    readonly activePromptSchema: ReadonlySignal<CommandSchema | null>;
    readonly activePromptPlayer: ReadonlySignal<string | null>;

    // 玩家响应activePrompt的输入若报错则返回string否则返回null
    onInput(input: string): string | null {}
    
    // 添加中断context.produceAsync会等待所有中断结束之后再继续
    addInterruption(promise: Promise<void>): void {}
    
    // 开始或者重新开始游戏
    start(): Promise<TResult>{}
    
    // 销毁
    dispose(): void {}

    // 事件侦听
    on(event: 'start' | 'dispose', listener: () => void): () => void {}
}