2026-04-04 13:20:34 +08:00
|
|
|
|
# 使用 GameHost
|
|
|
|
|
|
|
|
|
|
|
|
`GameHost` 是游戏运行的核心容器,负责管理游戏状态、命令执行和玩家交互的生命周期。
|
|
|
|
|
|
|
|
|
|
|
|
## 创建 GameHost
|
|
|
|
|
|
|
|
|
|
|
|
通过 `createGameHost` 传入一个 GameModule 来创建:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
import { createGameHost } from 'boardgame-core';
|
|
|
|
|
|
import * as tictactoe from 'boardgame-core/samples/tic-tac-toe';
|
|
|
|
|
|
|
|
|
|
|
|
const host = createGameHost(tictactoe);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 响应式状态
|
|
|
|
|
|
|
|
|
|
|
|
GameHost 暴露的所有属性都是响应式 Signal,可以直接用于 UI 渲染或 `effect()`:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
import { effect } from '@preact/signals-core';
|
|
|
|
|
|
|
|
|
|
|
|
// 游戏状态
|
|
|
|
|
|
effect(() => {
|
|
|
|
|
|
console.log(host.state.value.currentPlayer);
|
|
|
|
|
|
console.log(host.state.value.winner);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 生命周期状态: 'created' | 'running' | 'disposed'
|
|
|
|
|
|
effect(() => {
|
|
|
|
|
|
console.log('Status:', host.status.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 当前等待的玩家输入 schema
|
|
|
|
|
|
effect(() => {
|
|
|
|
|
|
const schema = host.activePromptSchema.value;
|
|
|
|
|
|
if (schema) {
|
|
|
|
|
|
console.log('Waiting for:', schema.name, schema.params);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 当前等待的玩家
|
|
|
|
|
|
effect(() => {
|
|
|
|
|
|
console.log('Current player prompt:', host.activePromptPlayer.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 启动游戏
|
|
|
|
|
|
|
|
|
|
|
|
调用 `setup()` 并传入初始化命令名来启动游戏:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
await host.setup('setup');
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这会重置游戏状态、取消当前活动提示、运行指定的 setup 命令,并将状态设为 `'running'`。
|
|
|
|
|
|
|
|
|
|
|
|
## 处理玩家输入
|
|
|
|
|
|
|
|
|
|
|
|
当命令通过 `this.prompt()` 等待玩家输入时,使用 `onInput()` 提交输入:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// 提交玩家操作,返回错误信息或 null
|
|
|
|
|
|
const error = host.onInput('play X 1 2');
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
console.log('输入无效:', error);
|
|
|
|
|
|
// 玩家可以重新输入
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 输入已被接受,命令继续执行
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 监听事件
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// 监听游戏设置完成
|
|
|
|
|
|
host.on('setup', () => {
|
|
|
|
|
|
console.log('Game initialized');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听游戏销毁
|
|
|
|
|
|
host.on('dispose', () => {
|
|
|
|
|
|
console.log('Game disposed');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// on() 返回取消订阅函数
|
|
|
|
|
|
const unsubscribe = host.on('setup', handler);
|
|
|
|
|
|
unsubscribe(); // 取消监听
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 重新开始游戏
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// 取消当前命令,重置状态,重新运行 setup 命令
|
|
|
|
|
|
await host.setup('setup');
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 销毁游戏
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
host.dispose();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
销毁后会取消所有活动命令、清理事件监听器,并将状态设为 `'disposed'`。销毁后无法再次使用。
|
|
|
|
|
|
|
|
|
|
|
|
## 完整示例
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
import { effect } from '@preact/signals-core';
|
|
|
|
|
|
import { createGameHost } from 'boardgame-core';
|
|
|
|
|
|
import * as tictactoe from 'boardgame-core/samples/tic-tac-toe';
|
|
|
|
|
|
|
|
|
|
|
|
const host = createGameHost(tictactoe);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听状态变化
|
|
|
|
|
|
effect(() => {
|
|
|
|
|
|
const state = host.state.value;
|
|
|
|
|
|
console.log(`${state.currentPlayer}'s turn (turn ${state.turn + 1})`);
|
|
|
|
|
|
if (state.winner) {
|
|
|
|
|
|
console.log('Winner:', state.winner);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 启动游戏
|
|
|
|
|
|
await host.setup('setup');
|
|
|
|
|
|
|
|
|
|
|
|
// 游戏循环:等待提示 → 提交输入
|
|
|
|
|
|
while (host.status.value === 'running' && host.activePromptSchema.value) {
|
|
|
|
|
|
const schema = host.activePromptSchema.value!;
|
|
|
|
|
|
console.log('Waiting for input:', schema.name);
|
|
|
|
|
|
|
|
|
|
|
|
// 这里可以从 UI/网络等获取输入
|
|
|
|
|
|
const input = await getPlayerInput();
|
|
|
|
|
|
const error = host.onInput(input);
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
console.log('Invalid:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 游戏结束后可以重新开始
|
|
|
|
|
|
// await host.setup('setup');
|
|
|
|
|
|
|
|
|
|
|
|
// 或彻底销毁
|
|
|
|
|
|
// host.dispose();
|
|
|
|
|
|
```
|
2026-04-04 15:27:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 动画同步
|
|
|
|
|
|
|
|
|
|
|
|
如需在状态更新之间播放动画,参考 [动画与状态更新同步](./animation-sync.md)。
|
2026-04-04 15:37:22 +08:00
|
|
|
|
|
|
|
|
|
|
`GameHost` 提供了两个方法:
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 说明 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `addInterruption(promise: Promise<void>)` | 注册动画中断,下一个 `produceAsync` 会等待它 |
|
|
|
|
|
|
| `clearInterruptions()` | 清除所有未完成的中断 |
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// UI 层:检测到状态变化后播放动画并注册中断
|
|
|
|
|
|
host.addInterruption(playAnimation('place', data));
|
|
|
|
|
|
```
|