boardgame-phaser/QWEN.md

155 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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的形式定义
```typescript
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>>`来记录桌游物件的摆放。
```typescript
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`读取游戏当前状态
```typescript
async function gameEnd(game: IGameContext<GameState>) {
return game.value.winner;
}
```
需要修改状态时,使用`game.produce`或`game.produceAsync`。
```typescript
async function start(game: IGameContext<GameState>) {
await game.produceAsync(state => {
state.currentPlayer = 'white';
});
}
```
需要等待玩家交互时,使用`await game.prompt(schema, validator, player)`。
```typescript
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`来控制游戏状态。
```typescript
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 {}
}
```