Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

游戏逻辑开发

游戏逻辑运行在平台的隔离运行时中,使用 TypeScript 或 JavaScript 编写,TypeScript 编译目标为 ES5,最终构建为单文件 IIFE bundle 提交。

双 SDK

SDK运行位置用途
@game-platform/backend-sdk平台游戏逻辑运行时提供生命周期基类与 platform API 类型,编写权威玩法逻辑
@game-platform/frontend-sdk游戏前端封装宿主协议、WebSocket 连接、重连与事件分发

生命周期

继承 GameLifecycle 并重写需要的钩子,最后调用 registerGame() 注册:

import { GameLifecycle, registerGame } from '@game-platform/backend-sdk'

class MyGame extends GameLifecycle {
  // 会话准备就绪,初始化牌桌与座位,此时玩家尚未进入对局。
  onInit(): void {}

  // 玩家进入或重连,可用于落座、夺回被托管的座位。
  onPlayerJoin(playerId: string, displayName: string): void {}

  // 玩家最后一条连接断开,成员资格保留,建议将座位交给托管逻辑。
  onPlayerLeave(playerId: string): void {}

  // 玩家玩法动作,仅在会话进入 playing 后被调用。
  onAction(playerId: string, action: string, payload: any): void {}

  // 平台状态快照拉取,首次进入、重连、页面恢复可见均可触发。
  // 必须用 platform.sendTo 回应本人全量状态。
  onSync(playerId: string): void {}

  // 会话结束,做最终结算与收尾。
  onFinish(): void {}
}

registerGame(new MyGame())

调用时序:

会话准备就绪:      onInit()
玩家进出:          onPlayerJoin 与 onPlayerLeave 循环触发
会话进入 playing:  动作开始受理,循环:
                    onAction(playerId, action, payload)
                      └─ broadcast 或 sendTo 下发事件
                    onSync(playerId),状态拉取随时可能发生
会话结束:          onFinish()

注意:

  • onInit 阶段不要假设玩家已落座;onAction 之前一定已收到对应玩家的 onPlayerJoin。
  • onSync 是玩家对账的唯一通道,必须实现。断线重连、切换标签页、补发状态都依赖它。
  • 运行时还支持一个可选的全局 onStart():会话进入 playing 时若已定义则被调用,适合满员即开局的玩法触发第一手动作。建议在模块顶层直接定义该函数,不经过 registerGame。
  • 建议所有钩子无副作用地可重入,同一玩家重连会再次触发 onPlayerJoin。

platform API

platform 为运行时注入的全局对象,完整清单如下。

通信

API说明
broadcast(event, data?)向会话内所有成员含观战者广播事件
sendTo(playerId, event, data?)向指定玩家定向发送,非成员静默忽略
getPlayers()返回玩家名册,内容为初始化时的快照,后续加入者经 onPlayerJoin 通知
isPlayerOnline(playerId)判断玩家当前是否有存活连接,用于区分离席与后台标签页
finishSession()在干净边界结束整个会话并释放房间,例如一手结束且无人在线
reopenSession()对局刚结束且有空位时,将会话退回等待态,允许新玩家凭码加入

状态

API说明
getGameState(key) / setGameState(key, value)会话级键值存储,会话结束即销毁,不持久化
random()密码学安全随机数,取值区间为 0 到 1 左闭右开
log(...args)服务端日志,用于排障
setTimeout / setInterval / clearTimer定时器,会话结束时统一清理

结算,详见资金接入:

API说明
beginRound() / endRound()开启与提交业务回合,分别返回回合 ID 与提交结果
deductBalance(playerId, amount, desc?)回合内登记扣款
rewardBalance(playerId, amount, desc?)回合内登记奖励
getBalance(playerId)本地缓存余额,可能滞后,禁止用于扣款前预检
getBalanceVersion(playerId)本引擎最近一次提交的余额版本号,随结果下发,用于客户端丢弃乱序推送
lastRoundError()读取最近一次回合提交失败原因
getRoundId()当前回合 ID,无回合时为空串

公平随机,详见资金接入:

API说明
beginFairRound(seedId)生成保密种子并向全员广播其 SHA-256 承诺,返回承诺值
getFairRandom(seedId, index)从公平种子派生第 index 个随机数
revealFairRound(seedId)结算后公开种子,全员可复算随机过程

运行约束

以下为运行时的硬性约束,提审与运行均按此校验:

约束值说明
bundle 大小≤ 512 KiB超出拒绝提审
字符编码必须 UTF-8
禁用能力eval、动态 import、require、process.env、Function 构造器、fetch、XMLHttpRequest、WebSocket、DOM 与 BOM 全局bundle 中出现 eval、import、require、process.env、this.constructor 直接拒审,运行环境中上述全局均不可用
随机数必须使用 platform.random 或公平随机接口Math.random 已被替换为密码学安全源,涉及资金的逻辑禁止自行引入可预测随机
单次回调耗时≤ 2 秒任一钩子或定时器回调超时,会话立即进入 error 终态并全员收到错误事件,必须控制搜索与 AI 算法复杂度,采用节点预算加兜底策略
回合闭合必须在钩子返回前调用 endRound钩子返回时仍有未闭合回合,本轮全部资金操作作废并产生会话错误
状态存储会话内存级setGameState 不落盘,需要跨局保留的数据不得依赖它

建议开局阶段的状态全部通过事件广播下发,事件携带完整公共快照,使观战者与中途进入者无需补发即可重建局面。超大快照随每条事件全量携带的成本远低于维护增量补发逻辑。