boardgame.io Client API 完全指南:从单机棋盘到多人联机的客户端构建
游戏开发【免费下载链接】boardgame.ioState Management and Multiplayer Networking for Turn-Based Games项目地址https://gitcode.com/gh_mirrors/bo/boardgame.io点击查看免费下载boardgame.io 是一款面向回合制游戏的状态管理与多人联机框架而Client正是其客户端应用的核心入口无论是纯 JavaScript 还是 React 项目都需要通过它创建并驱动一个游戏客户端实例。本文以 docs/documentation/api/Client.md 为主体结合 src/client/client.ts 等源码实现完整讲解Client的全部配置选项、实例属性与方法、React 封装方式以及多人联机传输层的工作原理读完即可上手搭建可运行的单机与多人棋类游戏前端。一、Client 是什么客户端应用的统一入口在 boardgame.io 中Client负责创建并托管一个完整的游戏客户端。它封装了 Redux 状态仓库store、游戏规则game、多人传输层transport与调试面板Debug Panel对外暴露统一的属性与方法让开发者无需关心底层状态同步细节即可聚焦于棋盘界面的渲染与交互。从源码看Client只是一个返回_ClientImpl实例的工厂函数见 src/client/client.ts。它有两个官方发布形式Plain JS 客户端从boardgame.io/client导入返回一个提供moves、events、getState等 API 的普通对象React 客户端从boardgame.io/react导入返回一个可直接渲染的 React 组件并把同样的 API 通过props注入给你的棋盘组件。两者的底层共用同一个_ClientImpl实现React 版本只是在其上包了一层组件桥接见 src/client/react.tsx。此外仓库还提供boardgame.io/react-native客户端packages/react-native.ts与同时导出两套 Client 的全量入口packages/main.js。二、Plain JS 客户端创建与全部配置选项2.1 导入import { Client } from boardgame.io/client;该入口由 packages/client.ts 统一导出同时导出的还有LobbyClient与LobbyClientError用于调用服务端大厅 API。2.2 创建客户端Client只接收一个options对象参数完整字段如下与 Client.md 保持一致const client Client({ // 游戏定义对象Game 配置见 Game API 文档。 game: game, // 玩家人数。 numPlayers: 2, // 设为下列传输实现之一即可启用多人联机 // // SocketIO // 通过 socket.io 与远程服务器通信的实现。 // // 导入方式 // import { SocketIO } from boardgame.io/multiplayer // // 参数 // 一个包含 2 个字段的对象 // 1. socketOpts直接透传给 socket.io 客户端的选项。 // 2. server服务器地址格式为 [http[s]://]hostname[:port] // 默认使用当前页面所在主机。 // // Local // 特殊本地模式使用内存版 game master便于在 // 不连接服务器的情况下本地测试多人交互。 // // 导入方式 // import { Local } from boardgame.io/multiplayer // // 此外你也可以编写自己的 transport 实现 // 详见 src/client/client.ts 中的相关说明。 multiplayer: false, // 要连接的比赛 ID多人模式。 matchID: matchID, // 将客户端关联到某个玩家多人模式。 playerID: playerID, // 该玩家的认证凭据多人模式。 credentials: credentials, // 设为 false 可禁用调试面板。 debug: true, // 或 false // 为内部 Redux store 追加 enhancer。 // 更多细节见 Debugging 指南docs/documentation/debugging.md。 enhancer: enhancer, });逐项说明与源码依据game必填游戏规则对象包含setup、moves、phases、turn、endIf等字段完整定义见 Game API 文档。创建客户端时会先经ProcessGameConfig(game)标准化处理src/client/client.ts。numPlayers玩家数量默认值为 2。它仅用于本地初始化游戏多人模式下该值会被传给 master 用于创建比赛。在 Transport 基类中可以看到numPlayers || 2的默认兜底逻辑。multiplayer传输层工厂函数。false表示单机模式传入SocketIO({...})或Local({...})则启用多人模式。从源码看单机模式会默认使用DummyTransportsrc/client/client.ts该传输实现为占位用途。matchID/playerID/credentials多人模式下用于定位比赛、声明玩家身份与提供鉴权。其中matchID缺省时为defaultsrc/client/client.ts。debug控制调试面板。注意在源码类型定义中它既可以是布尔值也可以是DebugOpt对象target、impl、collapseOnLoad、hideToggleButton见 src/client/client.ts测试中也有把调试面板挂载到自定义 DOM 元素上的用例src/client/client.test.ts。enhancerRedux store enhancer。源码使用compose(middleware, enhancer)将内置中间件与你的 enhancer 组合src/client/client.ts因此可以在不改动框架的前提下拦截 dispatch、添加日志或触发副作用。2.3 使用客户端实例属性客户端实例暴露以下只读属性moves包含所有已定义 move 的 dispatch 函数的对象。函数名与你创建的 游戏对象 中的 move 一一对应每个函数可接收任意参数这些参数会依次传递给 move 函数排在G与ctx之后。注意phase 级别的 move 也会被并入moves且仅在对应 phase 激活时可用测试见 src/client/client.test.ts。events包含endTurn、endPhase、setPhase、setActivePlayers、endStage、setStage、endGame、pass等游戏事件 dispatch 函数的对象。可用的事件集合取决于游戏配置中的events字段src/client/client.test.ts。log游戏日志LogEntry[]。它由客户端内置的LogMiddleware维护本地 move/event 会产生deltalog并追加来自 master 的update/patch会按_stateID去重后追加sync会整体替换reset会清空src/client/client.ts。matchID与该客户端关联的比赛 ID。playerID与该客户端关联的玩家 ID。credentials该玩家的多人鉴权凭据。matchData通过 Lobby API 加入当前比赛的玩家数组。示例[ { id: 0, name: Alice }, { id: 1, name: Bob, isConnected: true } ]chatMessages该客户端收到的聊天消息数组。每条消息包含以下字段id唯一 ID 字符串sender发送者的playerIDpayload调用sendChatMessage时传入的值。示例[ { id: foo, sender: 0, payload: Ready to play? }, { id: bar, sender: 1, payload: Lets go! }, ]从源码看sendChatMessage会用nanoid(7)生成消息 ID 并通过 transport 发送src/client/client.ts收到消息后经receiveChatMessage追加到chatMessages并通知订阅者src/client/client.ts。2.4 使用客户端实例方法start()启动客户端。连接多人传输层并创建调试面板。源码中它调用transport.connect()并将自身注册进全局ClientManagersrc/client/client.ts。stop()停止客户端。断开传输层并卸载调试面板同时从ClientManager注销src/client/client.ts。getState()获取当前游戏状态。若客户端尚未与远程 master 完成同步则返回null否则返回如下对象{ // 游戏状态对象 G。 G: { /* ... */ }, // 游戏 ctxturn、currentPlayer 等。 ctx: { /* ... */ }, // 插件状态。 plugins: { /* ... */ }, // 游戏日志。 log: [ /* ... */ ], // 为 true 表示客户端当前可以行动或与游戏交互。 isActive: true, // 或 false // 为 true 表示与服务器的连接处于活动状态。 isConnected: true, // 或 false }源码揭示了isActive的计算逻辑多人模式下当前玩家不在激活状态、或单机模式指定了playerID但该玩家未激活、或ctx.gameover已设置时isActive均为false另外在单机模式下会在这里执行playerView剥离开局玩家不可见的秘密信息src/client/client.ts。subscribe(callback)为每次状态变化注册回调。回调收到的参数与getState()返回的值一致函数返回一个取消订阅函数。const unsubscribe client.subscribe(state { // 使用更新后的 state }); // 取消订阅 unsubscribe();源码中每个订阅者会分配一个递增 ID 存入subscribers表notifySubscribers()会遍历调用同时它还会订阅传输层的连接状态变化src/client/client.ts。完整的多订阅/退订行为在 src/client/client.test.ts 中有详尽测试。reset()重置游戏。内部 dispatchRESET动作将状态恢复为初始状态。undo()撤销上一步操作。内部 dispatchUNDO动作。redo()重做之前被撤销的操作。内部 dispatchREDO动作。undo/redo的完整行为含多人模式下playerID的传递见 src/client/client.ts基础功能测试见 src/client/client.test.ts。sendChatMessage(message)向其他玩家发送聊天消息。message可以是字符串也可以传对象以附带更多元数据。updateMatchID(id)更新客户端的比赛 ID并同步通知传输层源码中会调用transport.updateMatchID并重建 dispatcherssrc/client/client.ts。updatePlayerID(id)更新客户端的玩家 ID同样会同步到传输层src/client/client.ts。updateCredentials(credentials)更新客户端的鉴权凭据并同步到传输层src/client/client.ts。2.5 单机模式的一个隐含行为源码中的assumedPlayerIDsrc/client/client.ts体现了一个单机模式细节当客户端未指定playerID时会默认把ctx.currentPlayer当作playerID附加到每个 move/event 动作上。这意味着单机模式下你无需关心当前行动方是谁框架会自动代劳而在多人模式下则必须显式声明playerID。三、多人联机SocketIO 与 Local 两种传输层multiplayer选项决定客户端如何与 game master 通信。boardgame.io 官方提供两种传输实现均从boardgame.io/multiplayer导入见 packages/multiplayer.ts。3.1 SocketIO连接远程服务器import { SocketIO } from boardgame.io/multiplayer; const client Client({ game, multiplayer: SocketIO({ // 可选直接透传给 socket.io 客户端的选项 socketOpts: { transports: [websocket] }, // 可选服务器地址格式为 [http[s]://]hostname[:port] server: localhost:8000, }), matchID: match-id, playerID: 0, });从 src/client/transport/socketio.ts 可以看到其底层机制connect()会根据server字段决定 socket 连接地址显式传入时会自动补全http://前缀与结尾/并以server gameName作为命名空间未传server时使用当前页面主机下的/ gameName命名空间src/client/transport/socketio.ts。连接成功后立即执行requestSync()拉取最新状态并监听五类服务器推送patch增量补丁、update完整状态、sync初始同步、matchData玩家元数据、chat聊天消息src/client/transport/socketio.ts。sendAction把客户端动作连同_stateID、matchID、playerID通过socket.emit(update, ...)发给 mastersrc/client/transport/socketio.ts。3.2 Local内存版多人模式import { Local } from boardgame.io/multiplayer; const client Client({ game, multiplayer: Local({ // 可选为指定玩家挂载 AI bot bots: { 1: RandomBot }, // 可选持久化到 localStorage默认使用内存存储 persist: true, // 可选自定义 localStorage 存储键名 storageKey: my-game, }), matchID: local-match, playerID: 0, });Local模式把LocalMaster一个内存版 master继承自服务端Master类直接内嵌进客户端多个客户端共享同一个 master 实例即可模拟完整的多人交互非常适合本地测试。源码要点src/client/transport/local.ts共享 masterLocal()内部维护了一个以gameKey为键的全局映射相同游戏与相同选项bots、storageKey、persist的多个客户端会复用同一个LocalMaster实例src/client/transport/local.ts测试中专门验证了这一点src/client/transport/local.test.ts。Bot 支持LocalMaster会在状态变化时检查当前是否轮到某个 bot若是则以 100ms 延迟触发 bot 的play()并把结果动作提交回 mastersrc/client/transport/local.ts。GetBotPlayer会同时处理activePlayers阶段与普通currentPlayer两种情况src/client/transport/local.ts。持久化persist: true时使用LocalStorage数据库存储键形如${storageKey}_state默认bgio_state否则使用InMemory数据库src/client/transport/local.ts。读写 localStorage 的完整流程有专门测试src/client/transport/local.test.ts。Bot 与联机行为验证src/client/transport/local.test.ts 演示了两个客户端共享同一局一方moves.A()后另一方立即可见状态变化后加入的客户端通过sync拿到最新状态聊天消息也能实时互通。3.3 自定义传输层multiplayer本质上是一个接收TransportOpts、返回Transport实例的工厂函数。src/client/transport/transport.ts 定义了Transport抽象基类及必须实现的 7 个抽象方法connect、disconnect、sendAction、sendChatMessage、requestSync、updateMatchID、updatePlayerID、updateCredentials。src/client/client.test.ts 给出了一个自定义 transport 的最小实现示例用于注入自定义元数据回调。四、React 客户端组件化封装4.1 导入与返回import { Client } from boardgame.io/react;该入口由 packages/react.ts 导出同时导出的还有BoardProps类型与Lobby组件。Client接收一个options对象返回一个 React 组件该组件在挂载时创建底层客户端并驱动渲染。4.2 组件 props返回的组件支持以下 props多人与调试相关matchIDstring连接指定比赛多人模式。playerIDstring将客户端关联到指定玩家多人模式。credentialsstring该玩家的鉴权凭据多人模式。debugboolean设为false以禁用调试 UI。源码中这三个可更新 props 的默认值分别为default、null、nullsrc/client/react.tsx且在componentDidUpdate中检测到任一 props 变化时会调用对应的updateMatchID/updatePlayerID/updateCredentials同步到底层客户端与传输层src/client/react.tsx。4.3 完整用法示例import React from react; import ReactDOM from react-dom; import { Client } from boardgame.io/react; const App Client({ // 游戏对象。 game: game, // 玩家人数。 numPlayers: 2, // 你的棋盘 React 组件。 // 该组件接收的 props 详见下方 Board Props。 // 使用 TypeScript 时请将组件 props 类型声明为继承 BoardProps。 board: Board, // 可选客户端在与 game master 完成初始同步之前 // 处于 loading 状态时显示的 React 组件。 // 仅多人模式相关。若未提供客户端默认显示 connecting...。 loading: LoadingComponent, // 设为下列传输实现之一以启用多人联机 // // SocketIO // 通过 socket.io 与远程服务器通信的实现。 // // 导入方式 // import { SocketIO } from boardgame.io/multiplayer // // 参数 // 一个包含 2 个字段的对象 // 1. socketOpts直接透传给 socket.io 客户端的选项。 // 2. server服务器地址格式为 [http[s]://]hostname[:port] // 默认使用当前页面所在主机。 // // Local // 特殊本地模式使用内存版 game master便于在 // 不连接服务器的情况下本地测试多人交互。 // // 导入方式 // import { Local } from boardgame.io/multiplayer // // 此外你也可以编写自己的 transport 实现。 multiplayer: false, // 设为 false 可禁用调试 UI。 debug: true, // 可选的 Redux store enhancer。 // 可用于增强 Redux store 以进行调试或拦截 // 事件以便在响应 move 时触发其他副作用。 enhancer: applyMiddleware(your_middleware), }); ReactDOM.render(App /, document.getElementById(app));其中loading缺省时的默认组件在源码中就是一个渲染connecting...文本的 divsrc/client/react.tsx与文档描述一致。4.4 React 版本渲染流程从 src/client/react.tsx 可以看到组件生命周期与底层客户端的对应关系构造函数中创建底层RawClient_ClientImpl传入game、debug、numPlayers、multiplayer、props 中的matchID/playerID/credentials以及enhancercomponentDidMount时调用client.subscribe(() this.forceUpdate())订阅状态变化并client.start()启动componentWillUnmount时client.stop()并取消订阅render时若getState()返回null尚未与 master 同步渲染loading组件否则把状态与客户端 API 一并注入board组件并包裹在div.bgio-client中。五、Board Props棋盘组件拿到的全部内容你传入board选项的组件会收到以下 propsG游戏状态。ctx游戏元数据回合、当前玩家、阶段等。moves包含你定义的所有 move 的 dispatch 函数对象函数名与 游戏对象 中的 move 一一对应每个函数可接收任意参数参数会排在G与ctx之后传给 move 函数。events包含endTurn、endPhase等游戏事件的 dispatch 函数对象。reset重置游戏的函数。undo撤销上一步的函数。redo重做被撤销步骤的函数。sendChatMessage(message)发送聊天消息的函数message可为字符串或对象。chatMessages收到的聊天消息数组消息对象字段为id、sender、payload[ { id: foo, sender: 0, payload: Ready to play? }, { id: bar, sender: 1, payload: Lets go! }, ]log游戏日志。matchID与该客户端关联的比赛 ID。playerID与该客户端关联的玩家 ID。matchData通过 Lobby API 加入当前比赛的玩家数组[ { id: 0, name: Alice }, { id: 1, name: Bob, isConnected: true } ]isActive为true表示客户端当前可以行动或与游戏交互。isMultiplayer为true表示这是多人游戏。isConnected为true表示与服务器的连接处于活动状态。credentials使用 Lobby REST API 时该玩家的认证令牌。这些 props 的 TypeScript 类型定义在BoardPropsG中src/client/react.tsx其中isMultiplayer是根据multiplayer选项是否为真值推导出来的src/client/react.tsx。完整的 props 注入发生在render阶段src/client/react.tsx与文档所列完全对应。六、源码纵深Client 内部的工作原理6.1 四条内置中间件_ClientImpl构造时会组合四条 Redux 中间件src/client/client.tsTransientHandlingMiddleware来自 src/core/reducer.ts处理INVALID_MOVE等临时性transient结果确保非法 move 不会污染状态测试见 src/client/client.test.tsSubscriptionMiddleware每次 action 处理后触发notifySubscribers()驱动 React 组件的重渲染与订阅回调TransportMiddleware把非clientOnly且非STRIP_TRANSIENTS的动作通过transport.sendAction(baseState, action)转发给 mastermaster 才是多人模式的权威状态源src/client/client.tsLogMiddleware维护log数组处理本地deltalog追加、masterupdate/patch去重合并、sync整体替换与reset清空src/client/client.ts。6.2 动作分发器dispatchersmoves、events、plugins三个 API 均由createDispatchers统一生成src/client/client.ts它遍历游戏解析出的动作名moveNames、enabledEventNames、pluginNames为每个名字生成一个闭包闭包内部用ActionCreators.makeMove/gameEvent/plugin构造带playerID与credentials的动作并 dispatch 到 store。assumedPlayerID会在单机模式且未指定playerID时自动填入ctx.currentPlayer。6.3 来自 master 的数据处理传输层收到的所有数据都会经receiveTransportData处理src/client/client.tssync用 master 返回的initialState与log整体重置本地状态并更新matchDataupdate仅当state._stateID currentState._stateID时才应用天然丢弃过期状态patch以 JSON Patchrfc6902方式增量应用要求prevStateID与本地_stateID严格一致不一致时忽略若补丁应用后_stateID未前进应用失败会主动requestSync()重新同步src/client/client.ts这一补丁失败自动重同步的容错在 src/client/client.test.ts 中有专门测试matchData更新玩家元数据chat追加聊天消息。值得注意的是receiveTransportData会先校验matchID是否与当前客户端一致不一致的数据会被直接丢弃src/client/client.test.ts。6.4 秘密信息的剥离时机单机模式下getState()会额外执行playerView并调用PlayerView插件视图函数把对当前玩家不可见的信息剥离后再返回src/client/client.ts而多人模式下服务器已经完成剥离客户端不会重复处理对应 issue #818 的修复这一行为有专门的测试用例验证src/client/client.test.ts。七、实践要点小结单机起步Client({ game, numPlayers })即可运行moves/events/getState/subscribe构成最基础的交互循环。本地多人测试用Local()替代真实服务器可在同一页面模拟多个玩家还能用bots挂载 AI、用persist持久化进度测试用例见 src/client/transport/local.test.ts。正式联机SocketIO({ server, socketOpts })连接远程 mastermatchID、playerID、credentials三者共同定位身份与权限服务端搭建参见 Server API 文档。React 集成直接Client({ game, board, loading, multiplayer })拿到组件棋盘组件通过 props 获得全部状态与操作 APITypeScript 用户应将组件 props 声明为继承BoardProps。联机下的 UI 细节联机时必须渲染loading组件以覆盖初始同步窗口否则显示默认 connecting...getState()在同步完成前返回nullUI 需对此做空态处理。想深入理解game对象各字段moves、phases、turn、playerView等与Client的配合方式可直接阅读 Game API 文档调试面板与enhancer的进阶用法参见 Debugging 指南完整的大厅对接流程见 Lobby API 文档。赞分享游戏开发【免费下载链接】boardgame.ioState Management and Multiplayer Networking for Turn-Based Games项目地址https://gitcode.com/gh_mirrors/bo/boardgame.io点击查看免费下载相关推荐如何快速构建回合制游戏客户端boardgame.io 客户端 API 完整指南如何快速构建回合制游戏客户端boardgame.io 客户端 API 完整指南 boardgame.io 是一个专为回合制游戏设计的状态管理和多人网络库它提游戏开发boardgame.io游戏教程构建多人象棋游戏的完整指南boardgame.io游戏教程构建多人象棋游戏的完整指南 想要快速构建一个支持多人对战的在线象棋游戏吗 boardgame.io框架为你提供了完美的解游戏开发boardgame.io 事件系统Events完全指南从 ctx 状态机到客户端触发的实战解析boardgame.io 事件系统Events完全指南从 ctx 状态机到客户端触发的实战解析 导读 本文围绕 boardgame.io 的 events游戏开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考