资讯动态

boardgame.io 游戏配置完全指南:深入解析 Game 对象的全部字段与运行原理

发布时间:2026/9/23 1:39:36 来源:尧图企业网站定制
boardgame.io 游戏配置完全指南深入解析 Game 对象的全部字段与运行原理【免费下载链接】boardgame.ioState Management and Multiplayer Networking for Turn-Based Games项目地址: https://gitcode.com/gh_mirrors/bo/boardgame.io本文是 boardgame.io 官方 API 文档 Game.md 的深度扩展版。它面向想要掌握回合制游戏状态机配置的开发者完整讲解Game对象的必填/可选字段、moves、turn、phases、endIf等核心机制的写法与默认行为并结合仓库源码src/core/、src/plugins/揭示其底层执行原理读完即可独立编写可运行、可维护的完整游戏定义。一、Game 对象是什么在 boardgame.io 中一个游戏Game就是一个普通的 JavaScript/TypeScript 对象它描述了游戏的完整规则初始状态如何生成setup、玩家可以执行哪些操作moves、回合如何流转turn、阶段如何切换phases、游戏何时结束endIf等。框架会把这个配置对象交给客户端Client与服务端Server共用从而保证两端执行完全一致的状态机逻辑。官方在 Game.md 中给出了一份全字段骨架本指南将逐块拆解它。使用 TypeScript 的读者可先参考 TypeScript 文档 了解如何为Game对象标注泛型类型GameG, PluginAPIs, SetupData定义见 src/types.ts。const game { name: my-game, setup: ({ ctx, ...plugins }, setupData) G, moves: { ... }, // 以下全部可选 playerView: ({ G, ctx, playerID }) G, seed: random-string, turn: { ... }, phases: { ... }, minPlayers: 1, maxPlayers: 4, endIf: ({ G, ctx, random, ...plugins }) obj, onEnd: ({ G, ctx, events, random, ...plugins }) G, disableUndo: true, deltaState: true, };框架通过 ProcessGameConfigsrc/core/game.ts对该配置做归一化处理并为其附加flow、moveNames、pluginNames、processMove等内部能力之后才交给 reducer 使用。理解这一点有助于明白你写的配置只是声明真正驱动它的是框架内部的状态机 Flow。二、必填字段name、setup 与 moves1.name游戏唯一标识name是游戏的名字用于在 Lobby / Server 中注册与查找该游戏。它在内部校验中不允许包含空格否则ProcessGameConfig会直接抛出name: Game name must not include spaces见 src/core/game.ts若省略默认值为default见 src/core/game.ts。2.setup初始化游戏状态 Gsetup: ({ ctx, ...plugins }, setupData) G,setup是唯一负责生成初始游戏状态 G的函数返回的对象即后续所有 move 操作的起点。它接收两个参数context包含ctx框架管理的回合/阶段上下文以及所有已启用插件如random、events暴露的 API。setupData可选的自定义对象由 Game Creation API即 Lobby 创建对局时透传进来可用于把外部数据注入初始状态例如棋盘尺寸、随机种子、玩家配置。从 InitializeGamesrc/core/initialize.ts可以看到完整调用链框架先为G设置空对象{}初始化ctx默认玩家数为 2随后调用game.setup({ ...pluginAPIs, ctx }, setupData)并用返回值覆盖G。若省略setup默认实现为() ({})见 src/core/game.ts。与之配套的是可选校验函数validateSetupDatavalidateSetupData: (setupData, numPlayers) setupData is not valid!,它会在对局创建前校验setupData只要返回了非undefined的字符串创建就会被中止并把该字符串作为错误回报给用户。这一机制常被用于拒绝非法参数例如 Lobby 收到不合法的棋局尺寸时提前拦截。3.moves定义玩家可以执行的操作moves: { // 短格式short-form A: ({ G, ctx, playerID, events, random, ...plugins }, ...args) { }, // 长格式long-form B: { move: ({ G, ctx, playerID, events, random, ...plugins }, ...args) { }, undoable: false, // 禁止撤销该步 redact: true, // 从日志中隐藏该步的参数 client: false, // 禁止在客户端本地执行强制走服务端 noLimit: true, // 不计入玩家的步数统计 ignoreStaleStateID: true, // 允许过期客户端派发的该步继续执行 }, },短格式直接是一个函数长格式则是一个对象包含move函数与若干行为开关类型定义见 src/types.ts 的LongFormMove运行时的判定逻辑IsLongFormMove见 src/core/game.ts。每个 move 函数都接收一个统一的 context其中G当前游戏状态ctx框架管理的上下文当前玩家、回合数、阶段等playerID发起该操作的玩家 IDevents事件 APIendTurn、endPhase、setActivePlayers等详见 src/plugins/events/events.tsrandom可复现的伪随机 API...plugins其他已启用插件暴露的 API。move 函数返回的新G会被写入状态若返回INVALID_MOVE常量则表示该步非法状态不更新。长格式各开关的语义选项作用move实际的移动函数undoablefalse禁止撤销该步也可以是函数({ G, ctx }) boolean动态判断redacttrue时该步的参数不会出现在日志中常用于隐藏机密信息如暗中出牌clientfalse时该步不在客户端本地优化执行而是强制发给服务端默认多数 move 会在客户端同步预演noLimittrue时该步不计入minMoves/maxMoves的步数统计ignoreStaleStateIDtrue时即使客户端状态已过期_stateID落后于服务端该步仍会被处理。有风险需在 move 内部自行校验状态合法性在 ProcessGameConfig.processMove 中可以看到 move 的派发过程框架先从 Flow 中按ctx含阶段/舞台解析出当前可用的 move 函数阶段作用域内可覆盖全局 moves用插件包装后以{ ...plugins.GetAPIs(state), G, ctx, playerID }为 context 调用action.args作为剩余参数展开传入。这也解释了为何 move 定义里能解构出random、events等能力——它们由插件层注入。三、可选字段从 playerView 到 deltaState1.playerView按玩家裁剪状态playerView: ({ G, ctx, playerID }) G,playerView是信息隐藏的关键它在把状态发给某个玩家前被调用允许按playerID裁剪 G例如只暴露该玩家可见的牌面。若不提供默认透传原状态见 src/core/game.ts。框架内置了一个现成的实现PlayerView.STRIP_SECRETS见 src/core/player-view.ts它会删除 G 中名为secret的键并把G.players裁剪为仅包含当前playerID的条目观战者得到空对象。关于完整的信息隐藏方案含secret状态与multiview客户端可阅读 secret-state.md。2.seed伪随机种子seed: random-string,seed用于初始化内置的伪随机数生成器基于 alea 算法实现在 src/plugins/random/random.alea.ts。相同的 seed 会产生完全相同的随机序列这是多人对局中每个玩家看到的随机结果一致的根基也让 AI 测试、回放和调试变得可复现。更多细节见 random.md。3.minPlayers/maxPlayers玩家数量范围minPlayers: 1, maxPlayers: 4,声明游戏支持的玩家数上下限。注意官方文档明确指出这两个字段仅在启用 Lobby 服务端组件时才被强制执行单独使用Client/Server时主要起元信息作用。另外在 InitializeGame 中若未显式传入numPlayers框架默认按 2 名玩家初始化。4.endIf与onEnd游戏结束判定与收尾// 只要返回任意非 undefined 值游戏即结束 // 返回值会写入 ctx.gameover。 endIf: ({ G, ctx, random, ...plugins }) obj, // 游戏结束时调用此时 ctx.gameover 已经可用。 onEnd: ({ G, ctx, events, random, ...plugins }) G,endIf是游戏级结束触发器返回真值即终止游戏返回值会成为ctx.gameover的内容供 UI 展示胜负结果。它通常用于检查G中的棋盘是否出现胜者或平局。onEnd则在结束时做最后的清理/统计例如把最终分数写入 G。若省略endIf默认为恒返回undefined、onEnd默认为透传 G见 src/core/flow.ts。从 Flow 的事件循环src/core/flow.ts可以看到每次更新后框架都会自动调用ShouldEndGame检查endIf一旦命中便推入一个自动的EndGame事件——这就是endIf无需显式调用任何事件即可生效的原因。5.disableUndo全局关闭撤销disableUndo: true,true时禁用整局游戏所有步的撤销undo/redo能力。默认值为false见 src/core/game.ts。在 InitializeGame 中只有当disableUndo为假时初始状态才会被压入_undo栈作为撤销基准。撤销的完整机制见 undo.md。6.deltaState多人模式下增量同步状态deltaState: true,true时多人对局中框架通过JSON Patch只传输状态的增量diff而不是每次同步整个状态对象从而显著降低带宽占用。默认值为false见 src/core/game.ts。适用于棋盘较大或状态频繁变化、对网络流量敏感的场景。四、turn回合制核心配置turn: { order: TurnOrder.DEFAULT, // 回合顺序策略 onBegin: ({ G, ctx, events, random, ...plugins }) G, // 回合开始钩子 onEnd: ({ G, ctx, events, random, ...plugins }) G, // 回合结束钩子 endIf: ({ G, ctx, random, ...plugins }) (true | { next: 0 }), onMove: ({ G, ctx, events, random, ...plugins }) G, // 每次移动后调用 minMoves: 1, // 最少步数后才允许结束回合 maxMoves: 1, // 达到步数后自动结束回合 activePlayers: { ... }, // 回合开始时调用 setActivePlayers stages: { A: { moves: { ... }, next: B }, ... }, // 回合内舞台 },逐项说明order回合顺序默认TurnOrder.DEFAULT按玩家编号轮流。框架在 src/core/turn-order.ts 的InitTurnOrderState中调用order.first(context)决定第一个行动玩家、order.next(context)决定下一个玩家自定义order可返回playOrder数组重排行动顺序。完整的回合顺序机制见 turn-order.md。onBegin/onEnd回合开始/结束时的钩子可修改 G。省略时默认透传见 src/core/flow.ts。endIf返回true结束当前回合返回{ next: 0 }则结束回合并指定下一个行动玩家。注意它也可以返回{ next: playerID }动态切换见 src/types.ts。onMove每次 move 执行后调用常用于在回合内累积状态。minMoves/maxMoves控制回合的步数窗口。maxMoves在 ShouldEndTurn 中被硬性检查达到即强制结束回合minMoves则阻止回合在未达到步数前结束。旧版配置项moveLimit已被弃用并在 src/core/flow.ts 通过supportDeprecatedMoveLimit兼容为同时充当minMoves与maxMoves。activePlayers回合开始时以此为参数自动调用setActivePlayers用于开启多名玩家同时行动的模式详见 src/core/flow.ts 与 src/core/turn-order.ts。setActivePlayers支持数组形式[0,1]也支持{ currentPlayer, others, all, value, minMoves, maxMoves, next, revert }对象形式。stages回合内舞台。处于某舞台stage的玩家只能执行该舞台moves中定义的步endStage事件触发后玩家会转入next指定的舞台。注意舞台内的 move 名会与全局 move 去重合并见 src/core/flow.ts。完整的舞台机制见 stages.md。五、phases阶段子游戏配置phases: { A: { onBegin: ({ G, ctx, events, random, ...plugins }) G, onEnd: ({ G, ctx, events, random, ...plugins }) G, endIf: ({ G, ctx, random, ...plugins }) true, moves: { ... }, // 覆盖本阶段内的 moves turn: { ... }, // 覆盖本阶段内的 turn 配置 start: true, // 标记为游戏的第一个阶段 next: nextPhaseName, // 本阶段结束后进入的阶段 }, ... },阶段phase允许把游戏拆分为多个子游戏例如抽牌阶段 → 出牌阶段 → 结算阶段。要点start: true标记该阶段为开局阶段只能标记一个若全部阶段都没有start则游戏从空阶段不处于任何阶段开始。框架在 src/core/flow.ts 遍历阶段表记录startingPhase。moves/turn阶段内的 move 表与 turn 配置会覆盖合并进全局配置且优先级更高——阶段作用域的 move 以phase.moveName的键名注册进 moveMap见 src/core/flow.ts使同一 move 名在不同阶段可以有不同实现。endIf返回真值结束当前阶段返回{ next: phaseName }可指定下一阶段next也可以是函数({ G, ctx }) nextPhaseName动态选择下一阶段见 src/types.ts。阶段结束后 Flow 会自动把next解析为具体阶段名并启动之见 src/core/flow.ts 与UpdatePhase。onBegin/onEnd阶段开始/结束钩子。注意官方在 events 插件中明确限制setPhase/endPhase不能在本阶段的onEnd中调用动态选下一个阶段应使用next触发器错误提示见 src/plugins/events/events.ts。若turn配置被省略阶段会继承游戏级turn配置见 src/core/flow.ts若turn.order也省略则回退到TurnOrder.DEFAULTsrc/core/flow.ts。阶段系统完整讲解见 phases.md。六、底层执行原理配置如何被激活理解下面的调用链就能明白配置对象的每个字段最终落到了哪里归一化与校验ProcessGameConfigsrc/core/game.ts补齐所有默认值namedefault、moves{}、deltaStatefalse、disableUndofalse等校验插件名与游戏名不含空格然后调用Flow(game)生成状态机。生成初始状态InitializeGamesrc/core/initialize.ts初始化G{}、ctx、plugins状态调用setup得到初始 G并通过game.flow.init(initial)启动第一个阶段与第一个回合内部依次触发StartGame → StartPhase → StartTurn见 src/core/flow.ts。执行 move 与事件循环每次 move 派发后Flow.Processsrc/core/flow.ts会按序处理事件队列——自动检查ShouldEndGameendIf、ShouldEndPhase阶段endIf、ShouldEndTurn回合maxMoves/endIf命中即推入对应的自动EndGame/EndPhase/EndTurn事件形成一个声明式驱动的状态机你在配置里写的每个endIf都无需手动调用事件框架会在每个更新周期自动求值。插件注入events、random等能力通过 src/plugins/ 注入到每个 move 与钩子的 context 中事件 API 的具体类型endGame、endPhase、endStage、endTurn、pass、setActivePlayers、setPhase、setStage定义在 src/plugins/events/events.ts且会校验调用位置例如不能在endIf或turn.order中调用事件。七、完整示例井字棋仓库中的 examples/react-web/src/tic-tac-toe/game.js 是官方完整实现这里给出一个浓缩版展示各字段的组合用法import { INVALID_MOVE } from boardgame.io/core; const TicTacToe { name: tic-tac-toe, minPlayers: 2, maxPlayers: 2, setup: () ({ cells: Array(9).fill(null) }), moves: { clickCell: ({ G, ctx, playerID }, id) { if (G.cells[id] ! null) return INVALID_MOVE; // 非法落子 G.cells[id] playerID; }, }, turn: { minMoves: 1, maxMoves: 1 }, // 每回合恰好一步 endIf: ({ G, ctx }) { // 检查胜负平局返回结果对象写入 ctx.gameover if (isVictory(G.cells)) return { winner: ctx.currentPlayer }; if (G.cells.every((c) c ! null)) return { draw: true }; }, // 可选按玩家裁剪视图若加入隐藏信息 // playerView: PlayerView.STRIP_SECRETS, }; export default TicTacToe;将该对象传给Client({ game: TicTacToe })即可渲染本地对局传给Server({ games: [TicTacToe] })即获得多人联机能力。多阶段/多舞台的完整示例可分别参考 phases 官方文档 与仓库中的 examples/snippets含phases-1、phases-2、stages-1等可运行示例。八、延伸阅读围绕Game对象官方文档还提供了以下配套专题建议按需深入phases.md阶段系统详解stages.md舞台同时行动/限定 move机制turn-order.md自定义回合顺序events.md事件 API 的调用规则与限制random.md可复现随机与seedundo.md撤销/重做与disableUndosecret-state.mdplayerView与机密状态plugins.md插件机制与...pluginscontexttypescript.mdGame对象的类型标注Client.md 与 Server.md将Game接入客户端与服务端以上就是 boardgame.ioGame对象的完整画像从必填的name/setup/moves到turn/phases的回合与阶段编排再到endIf/onEnd/playerView/deltaState等进阶能力以及它们在 src/core/ 与 src/plugins/ 中的实现落点。掌握这一份配置你就能用声明式的方式描述任意回合制游戏的完整规则。【免费下载链接】boardgame.ioState Management and Multiplayer Networking for Turn-Based Games项目地址: https://gitcode.com/gh_mirrors/bo/boardgame.io创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价