微信小游戏 1v1 对战帧同步实践:确定性模拟与断线重连

最近在给自己的微信小游戏(网格摆块 + 兵线对冲的 1v1 对战玩法)加联机对战。踩完一整轮坑之后把方案和经验记下来,给同样要做小游戏实时对战的朋友一个参考。服务端是 C#(DotNetty),客户端 Cocos Creator,协议 WebSocket + protobuf。

为什么选帧同步

对战双方的所有操作只有"摆块、用道具、选技能"这几类离散指令,单位数值全部可以由逻辑模拟推出。帧同步(lockstep 思路)下服务端只转发指令帧,不算游戏逻辑:

代价是客户端必须是确定性模拟:同种子 + 同指令序列 → 两端状态逐位一致。这件事说起来一句话,做起来全是坑。

确定性模拟三条军规

镜像视角最容易踩的坑

帧同步双端各模拟两份棋盘:本地视角下 ally=自己、enemy=对手,两端视角互为镜像。这意味着所有按 ally/enemy 区分的参数和逻辑顺序,都是视角相对量

判据很简单:把双端模拟各跑 60 秒无头对比,任何一项状态(位置/数值/基地血)不一致就是分歧。我们最后就是用这个方法把分歧压到 0 的。

断线重连:比想象中深

断线保留座位 + 45 秒重连窗口 + resumeToken 凭证,这些是标配。真正坑在链路细节:

结算:不信任任何一端

对局结果由双端各自模拟得出并上报,服务端做一致性校验:一致则广播权威结算;不一致记日志(反作弊线索),按剩余血量兜底判胜。客户端任何"我赢了"的本地判断都只用于上报,不直接生效。

小结

帧同步的难点从来不在"转发指令",而在确定性:随机流归属、视角相对参数、事件顺序、时间源,任何一处视角相关或环境相关的差异都会 quietly 毁掉两端一致性——而且症状往往表现为"对手像个笨 AI"这种完全误导方向的表象。建议早写一个无头双端对比脚本,分歧一旦出现就能精确到帧定位。

后续有空再写服务端房间生命周期管理(结算释放、空房回收、会话归属守卫)的笔记。就酱。