两台客户端谁先动了:ET框架帧同步的预测回滚原理与上手路径
发布时间:2026/9/17 21:59:38 作者:尧图编辑部 阅读量:1,286

两台客户端谁先动了ET框架帧同步的预测回滚原理与上手路径【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET两机对战打到第200帧A端角色的剑已经挥出B端还停在原地。Unity多人游戏里帧同步最怕这种漂移同一串输入两台机器算出了两个世界。ET框架的解法是预测加回滚——每台客户端先本地预测跑帧再用哈希对拍定位差异帧回到最后一个干净帧重放。一张谱子为什么对拍就能找回差异帧把一场对战想成乐队对拍。每位乐手客户端拿到的谱子输入序列和速度固定帧间隔完全一样指挥服务器并不替谁演奏只做一件事在每个小节线帧号上收一次你这段弹了什么再广播回去。小节线就是天然的拍点对拍点——每个乐手都听得出自己在哪个小节跟整体走调了。走调一旦发生后面再怎么弹都救不回来因为错误会随着逻辑越滚越大。乐队不会硬着头皮弹完整首而是把录像带倒回到最后一个全体音准的小节从那里重新演。帧同步的预测对应乐手在指挥信号到达前先把下一小节弹了——只要速度固定抢拍不会毁掉演出回滚就是那次倒带。整场演出里真正走网络的只有每小节的谱子片段输入而不是整首录音状态这就是帧同步带宽低的原因。机制拆成三个为什么为什么所有客户端要数同一个数帧同步全部建立在确定性上相同初始状态、相同输入序列、相同逻辑执行顺序就必然得到相同结果。所以每台客户端必须数同一个帧号而且用同一个固定时间片来数。ET demo 里时间片由 LSConstValue.UpdateInterval 定义取 33ms 一档即每秒约 30 个逻辑帧。渲染可以是 60Hz、144Hz 随意逻辑帧只认固定时间片否则同一串输入会落在不同帧号上确定性直接失效。差异帧是怎么被找出来的ET 的做法是每帧对拍每帧把逻辑世界 LSWorld 序列化成一个内存缓冲算一个哈希。客户端把帧号、本帧输入、哈希打包成 FrameMessage 发往服务器服务器比较各玩家同帧的哈希——第一个哈希不一致的帧号就是差异帧。对拍失败时服务器会把自己该帧的世界字节发回客户端Room2C_CheckHashFailHandler.cs两端各打印一份数据用来定位是哪个字段开始漂移。回滚时状态从哪来状态不需要从网络拉本地就有。Room 组件挂着一个 FrameBuffer 滚动快照池每帧序列化完该帧的快照和输入序列FrameInputs都留存在缓存里。要回滚到第 N 帧从 FrameBuffer 反序列化出那一帧的快照再按输入序列从 N 帧往后重放即可输入本来就逐帧记着回滚是纯本地操作。长局里 Replay 组件还会每隔 SaveLSWorldFrameCount 帧抽一次快照落盘供录像回放和重连用。逻辑跑在哪纤程、固定时间片与预测上限在 ET 里Room 本身就是一个实体同时是场景IScene一整局对战的逻辑世界挂在它下面的独立纤程里服务器按图Map创建纤程FiberInit_Map让逻辑帧循环和 Gate、Match 等服务逻辑互不干扰客户端的逻辑层与表现层同样解耦。客户端的帧驱动组件是 LSClientUpdater核心循环可以压缩成这样看[EntitySystem] private static void Update(this LSClientUpdater self) { Room room self.GetParentRoom(); while (self.CanPredict()) // 预测帧最多领先权威帧5帧超了就先等 { room.PredictionFrame; OneFrameInputs inputs self.GetFrameInputs(room.PredictionFrame); room.Update(inputs); // 一帧逻辑执行 room.SendHash(room.PredictionFrame); self.SendInput(room.PredictionFrame, self.Input); } }真正的技巧在 GetFrameInputs帧号不超过权威帧时直接用服务器已确认的输入超过权威帧时拿缓存里最近一次见到的输入顶替别人只把自己那一格填成本帧真实输入——这就是预测。FixedTimeCounter 负责把真实时间换算成现在该跑到哪个逻辑帧网络不好时客户端做时间膨胀、主动放慢逻辑帧去等网络Room2C_AdjustUpdateTime保证预测不会拉开超过 5 帧。四类漂移现场症状、原因与处理 症状所有客户端在同一个帧号集体漂移 → 原因输入结构里塞了状态数据血量、坐标各端算出来的数值有差异 → 处理输入只做瘦身留方向位、按键、技能ID状态一律由逻辑推导。 症状帧号越大越容易不同步且只在低端机出现 → 原因渲染帧率不稳逻辑帧和渲染纠缠在一起 → 处理逻辑帧走固定时间片预测上限 5 帧超出就暂停等输入而不是加速硬追。 症状回滚一次卡顿超过 10ms → 原因快照范围太大把静态场景、动画状态都序列化进了 LSWorld → 处理快照只留参与逻辑的实体表现层数据留在渲染侧不进快照。 症状换台设备Windows 与 Android重开一局必漂移 → 原因浮点精度、字典遍历顺序跨平台不一致 → 处理逻辑层改定点数或整数运算遍历换有序容器。选型边界帧同步给谁用状态同步给谁用帧同步适合 2 到 8 人的对战胜负判定必须绝对一致、单帧输入只有几字节8 人房间 30 帧每秒服务器转发一房不到几百字节每秒低延迟同步的带宽代价很低。反过来百人同屏的大场景、需要服务器权威裁决的 MMO逻辑复杂度会被确定性要求拖死这类项目走状态同步更合适ET 仓库里 statesync demo 就是对应的参照。一条可执行的判断先问房间人数不超过 8且胜负判定要求跨端绝对一致吗两个都才是帧同步否则状态同步。商业化项目也有验证大型 MMO《千古风流》由 100 人团队用 ET 在 2 年内完成《危境》由一名技术加一名策划做出并上线。帧同步方向的落地建议按三步走先按 运行指南 跑通 lockstep demo给 LSInput 加一个字段观察哈希对拍日志能否定位漂移最后用 robot 包 拉 N 个机器人进同一房间本地验证半小时无不同步再上真实网络。想继续深入视频课可以看 帧同步设计B站 下问题交流在 ET 讨论 QQ 群 474643097。【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考