资讯动态

ET框架帧同步实现原理与预测回滚实战指南

发布时间:2026/9/17 20:17:37 来源:尧图企业网站定制
ET框架帧同步实现原理与预测回滚实战指南【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET两玩家对战时一方网络卡了 200 毫秒他这边角色还站在原地你这边角色已经跑到了墙后。重连后再一看双方血量对不上录像回放里人物直接瞬移。这类问题的根源是客户端各自跑帧、状态悄悄漂移。ET框架的帧同步方案预测回滚 LockStep就是针对这个场景的所有房间共享同一套输入序列客户端先本地预测跑帧权威帧一到再校验对不上就回滚重放。下面直接看它怎么做到先做题、再对答案。ET帧同步预测回滚房间场景帧同步为什么选预测回滚三种方案怎么选先用一个类比帧同步像合唱团对拍。每个人客户端按同一个总谱输入序列演奏指挥权威帧每隔几小节确认一次进度有人跑调了就回到最近的确认点重新合奏。预测回滚则像先做题再对答案你不用等老师发标准答案自己先把题做完交卷答案公布后逐题核对错了就从错的那题开始重做。方案带宽占用状态一致性延迟体验适用场景纯锁步等所有输入才跑帧最低强受最慢玩家拖累局域网、低延迟对战状态同步同步结果高发状态强低MMO、大世界预测回滚ET帧同步采用低只发输入强靠哈希校验低本地预测快节奏多人对战两个关键名词先说白话**权威帧AuthorityFrame**是服务器确认过的最后一帧预测帧PredictionFrame是客户端自己往前抢跑的最后一帧。两者差值就是还没对答案的题ET 把这个差值上限锁死在 5 帧。数据流与核心模块从输入到回滚的完整路径整条数据流用箭头串起来玩家输入LSInput→ 本地FrameBuffer缓存 → 客户端预测循环LSClientUpdaterSystem→ 服务器收帧LSServerUpdaterSystem→ 广播OneFrameInputs→ 客户端校验权威帧 哈希 → 校验失败则Room.GetLSWorld取快照回滚 → 表现层LSUnitViewSystem更新。逐模块拆一下职责边界RoomScripts/Model/Share/Room.cs房间实体持有PredictionFrame、AuthorityFrame、FrameBuffer、FixedTimeCounter和存档Replay。它是帧同步所有状态的容器。FrameBuffer帧环形缓存按FrameCountPerSecond * 60分配容量60 秒的输入、快照、哈希MoveForward负责推进游标。输入和快照都存在这里。FixedTimeCounter把墙钟时间换算成帧号FrameTime(frame) startTime (frame - startFrame) * Interval保证服务器和客户端的第 N 帧什么时候该跑口径一致。LSClientUpdaterSystem客户端预测主循环输入是本地Input输出是提前跑出的预测帧和上报的哈希。LSServerUpdaterSystem服务器收帧器输入是各客户端上报的FrameMessage输出是广播给全房间的OneFrameInputs。RoomSystem执行一帧Update并保存快照哈希SaveLSWorld提供回滚取数GetLSWorld。客户端预测主循环这段代码决定了你能抢跑多快// 客户端预测最多比权威帧快5帧到点才跑下一帧 while (true) { // 下一帧的固定时间还没到先不跑 if (timeNow room.FixedTimeCounter.FrameTime(room.PredictionFrame 1)) { return; } // 最多只预测5帧 if (room.PredictionFrame - room.AuthorityFrame 5) { return; } room.PredictionFrame; OneFrameInputs oneFrameInputs self.GetOneFrameMessages(room.PredictionFrame); room.Update(oneFrameInputs); // 跑一帧 存快照哈希 room.SendHash(room.PredictionFrame); // 上报本帧哈希 FrameMessage frameMessage FrameMessage.Create(); frameMessage.Frame room.PredictionFrame; frameMessage.Input self.Input; root.GetComponentClientSenderComponent().Send(frameMessage); }预测帧里其他人的输入从哪来GetOneFrameMessages的逻辑是还没被服务器确认的帧用权威帧的输入前向填充然后只把自己的输入替换成本地最新值。这就是预测的全部秘密——别人的输入靠猜自己的输入不猜。服务器侧的收帧和缺帧兜底这是保证房间不卡死的关键// 服务器有人这一帧的输入没到就用他上一帧的操作顶上 foreach (long playerId in room.PlayerIds) { if (oneFrameInputs.Inputs.ContainsKey(playerId)) { continue; } if (preFrameInputs ! null preFrameInputs.Inputs.TryGetValue(playerId, out LSInput input)) { // 使用上一帧的输入 oneFrameInputs.Inputs[playerId] input; } else { oneFrameInputs.Inputs[playerId] new LSInput(); // 没有上一帧就空操作 } }回滚的底气来自快照。RoomSystem每跑一帧都会序列化整个世界状态并算哈希private static void SaveLSWorld(this Room self) { int frame self.LSWorld.Frame; MemoryBuffer memoryBuffer self.FrameBuffer.Snapshot(frame); memoryBuffer.Seek(0, SeekOrigin.Begin); memoryBuffer.SetLength(0); // 序列化当前帧的整个 LSWorld这是回滚的底座 MemoryPackHelper.Serialize(self.LSWorld, memoryBuffer); memoryBuffer.Seek(0, SeekOrigin.Begin); // 算状态哈希客户端上报后与服务端比对不一致就是不同步了 long hash memoryBuffer.GetBuffer().Hash(0, (int)memoryBuffer.Length); self.FrameBuffer.SetHash(frame, hash); }哈希校验失败时Room2C_CheckHashFailHandler会把服务端那份LSWorld和客户端用GetLSWorld从快照还原的当帧状态都打出来方便你逐字段对比。帧间隔、快照内存、带宽这几个参数怎么定 仓库里的默认值都集中在LSConstValue帧间隔UpdateInterval 50ms20 帧/秒匹配人数MatchCount 1demo 为单人对机器人可改快照存档间隔SaveLSWorldFrameCount 60 * FrameCountPerSecond每 60 秒存一次完整存档。带宽估算输入体LSInput是TSVector2两个 64 位定点数16 字节int Button4 字节约 20 字节/人/帧。估算公式带宽 ≈ 玩家数 × 20 字节 × 帧率。20 帧/秒下每人大约0.4 KB/s4 人房也才 1.6 KB/s——这就是帧同步省带宽的原因。快照内存FrameBuffer容量是 60 秒帧数20 × 60 1200 帧每帧配 10KB 的MemoryBuffer合计约12MB/房间。这是回滚窗口你能容忍回滚到最近 60 秒内的任意一帧。四个高频踩坑点⚠️ 现象打几分钟后弹出Room2C_CheckHashFail客户端和服务端状态开始对不上。根因帧逻辑里混进了float运算、Vector3或UnityEngine.Random不同平台浮点结果有微小差异累积后哈希必然爆。解法帧逻辑一律走 TrueSync 定点数学Packages/cn.etetet.truesync下的TSVector、TSMath、TSMatrix4x4随机数用TSRandomLSInput本身就用的TrueSync.TSVector2照抄这个做法。⚠️ 现象弱网时角色冻住不跑。根因预测帧差顶到 5 帧上限客户端主动停帧等权威帧而不是狂发输入。解法这是设计行为别硬改上限。Room2C_AdjustUpdateTimeHandler会做时间膨胀——把帧间隔从 50ms 动态拉到40~66ms之间用走慢一点换不卡死你只需保证逻辑是纯函数慢放不影响正确性。现象某玩家断线后他的角色原地不动对手觉得他卡了。根因服务器收帧兜底给了空LSInput。解法正常预期行为该玩家重连走G2Room_ReconnectHandler补齐中间帧无需在逻辑层做特殊处理。现象想给快照加压缩、做增量快照结果改出一堆 bug。根因过度优化——SaveLSWorld每帧全量序列化是回滚正确性的基础。解法先看第 5 节的判断标准没到阈值就别动。✅ 什么时候不需要优化快照2 人房、单机调试、或内存充裕时12MB 完全可接受全量快照 哈希反而是最不容易出 bug 的形态。从 Demo 到上线最小可运行步骤与升级判断最小可运行清单clone 仓库git clone https://gitcode.com/GitHub_Trending/et/ET用 Unity 打开工程等编译完成步骤详见 Book/1.1运行指南.md。启动服务端Packages/cn.etetet.lockstep/DotNet~下的ET.App项目再开客户端按运行指南拉起进程。进匹配后你会进入SceneType.LockStep场景FiberInit_LockStep会自动发布EntryEvent1/2/3初始化房间。控制台观察Room2C_CheckHashFail是否出现没出现说明预测与校验链路是通的。打开回放LSReplayUpdaterSystem支持 1/2/4/8 倍速重放整局输入序列用它验证确定性。真实项目参考用 ET 框架开发的商业 MMO《千古风流》100 人团队 2 年完成并上线官方资料提到单服 1.5 万人在线的承载。它证明这套 Actor 纤程架构撑得住大体量产品帧同步模块本身则更轻直接复用cn.etetet.lockstep包即可。什么时候该升级或重构看这三个信号压测时PredictionFrame - AuthorityFrame长期顶在 5 帧说明帧耗时超过 50ms先查逻辑层 CPU 热点出现Room2C_CheckHashFail立即按坑 1 排查确定性机器人压测中两台客户端帧偏差超过 2 帧且时间膨胀40~66ms 区间拉不平就该考虑缩小单帧逻辑量或提高帧率上限。延伸方向与社区入口回放与录像ReplayLSReplayUpdaterSystem已内置适合做观战、回放分享改ReplaySpeed即可变速。网络抖动补偿FixedTimeCounter.ChangeInterval已暴露了动态改帧间隔的能力可以接实时 RTT 统计做更细的补偿策略。人数扩展LSConstValue.MatchCount目前为 1扩到 4v4 时重点验证服务器GetOneFrameMessage的缺帧兜底和带宽公式见上节。代码入口在 Packages/cn.etetet.lockstep/ 和 Packages/cn.etetet.truesync/运行步骤在 Book/1.1运行指南.md。ET 讨论 QQ 群 474643097有问题欢迎来聊。先把 lockstep demo 跑起来再对照上面数据流与核心模块一节把Room→FrameBuffer→ 两个 Updater 的代码拆一遍。【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价