4.1 KiB
4.1 KiB
boardgame-phaser
基于Phaser3/boardgame-core的游戏框架
概述
项目使用pnpm monorepo管理,包含以下包:
framework:通用框架boop-game:boop样例sample-game:tic 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.produce或game.produceAsync。
async function start(game: IGameContext<GameState>) {
await game.produceAsync(state => {
state.currentPlayer = 'white';
});
}
需要等待玩家交互时,使用await game.prompt(promptDef, validator, player)。
import {createPromptDef} from "boardgame-core";
export const prompts = {
play: createPromptDef<[PlayerType, number, number]>('play <player> <row:number> <col:number>'),
}
async function turn(game: IGameContext<GameState>, currentPlayer: PlayerType) {
const {player, row, col} = await game.prompt(
prompts.play,
(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
// promptDef应当从game module中导出
tryAnswerPrompt<TArgs extends any[]>(promptDef: Promptdef<TArgs>,...args: TArgs): string | null {}
// 添加中断,context.produceAsync会等待所有中断结束之后再继续
addInterruption(promise: Promise<void>): void {}
// 开始或者重新开始游戏
start(): Promise<TResult>{}
// 销毁
dispose(): void {}
// 事件侦听
on(event: 'start' | 'dispose', listener: () => void): () => void {}
}