ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ET框架 帧同步实战:20Hz心跳、5帧预测与hash回滚是怎么跑通的

ET框架 帧同步实战:20Hz心跳、5帧预测与hash回滚是怎么跑通的 ET框架 帧同步实战20Hz心跳、5帧预测与hash回滚是怎么跑通的【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET你按下攻击键屏幕里的角色愣了0.25秒才挥出那刀对面却已经砍完两下——这就是帧同步做砸时的经典画面。反过来做对了同样的20Hz逻辑心跳下玩家感知延迟能压到半帧以内。本文带你拆穿ET框架里预测回滚帧同步的真实实现一套50ms定步长的房间心跳一个不到10字节的输入结构一个环形快照缓冲外加hash对账兜底。面向有Unity3D和C#基础、但没碰过帧同步的同学所有代码都来自仓库里的Packages/cn.etetet.lockstep演示包可以直接对着源码读。不同步的瞬间0.25秒到底卡在哪里先把时间账算清楚。ET帧同步demo的逻辑步长定在50ms也就是每20毫秒…不是每50毫秒跑一帧逻辑折合20帧/秒。客户端允许抢跑最多5帧权威帧——50ms × 5 250ms。这解释了开头那个0.25秒预测跑满上限后角色只能原地等权威帧追上来表现出来就是输入被吞了四分之一秒。再叠加一次网络RTT移动网络50~150ms很常见最坏情况你的操作从按下到看见结果接近400ms。帧同步的思路很简单不传状态只传每帧输入所有客户端跑同一套确定性模拟。但确定性三个字背后要解决的工程问题才是正文要拆的东西。帧同步怎么跑起来50ms定步长与20Hz房间心跳房间实体Room同时是场景和逻辑容器身上挂着帧缓冲、定步长计数器、预测帧号、权威帧号和存档四件套这就是整个同步世界的全部家当public class Room: Entity, IScene, IAwake, IUpdate { public FrameBuffer FrameBuffer { get; set; } // 帧缓存输入、快照、hash public FixedTimeCounter FixedTimeCounter { get; set; } // 定步长时间轴 public int PredictionFrame { get; set; } -1; // 预测帧 public int AuthorityFrame { get; set; } -1; // 权威帧 public Replay Replay { get; set; } new(); // 存档 }⚠️ 注意帧是逻辑概念和你渲染的60Hz/120Hz无关。逻辑帧推进节奏完全由墙钟时间驱动跟客户端掉不掉帧没关系。定步长时钟帧号怎么换算成时间戳所有帧推进都先过这道门当前时间没到这一帧的应到时刻就不许跑。public class FixedTimeCounter { public void ChangeInterval(int interval, int frame) // 支持动态改步长 { this.startTime (frame - this.startFrame) * this.Interval; this.startFrame frame; this.Interval interval; } public long FrameTime(int frame) { return this.startTime (frame - this.startFrame) * this.Interval; // ← 帧号→时间戳 } }Interval是50ms这个常量决定的。客户端侧还能在运行时调小步长做时间膨胀补帧追进度服务端则用它兜底防跑飞。⚠️ 时间轴起点StartTime是服务器定下发的。客户端自己new一个差不多的起点几分钟后各端帧号就会错位hash开始全线对不上——排查这种问题极其痛苦。输入怎么同步一个不到10字节的帧指令每帧要同步的数据长这样[MemoryPackable] // 无GC序列化网络收发不走托管堆 public partial struct LSInput { public TrueSync.TSVector2 V; // 量化方向向量 public int Button; // 按钮位掩码 public static bool operator(LSInput a, LSInput b) { return a.V b.V a.Button b.Button; // ← 相等性是回滚比对的基石 } }TSVector2是定点数量化类型不是float——这是确定性模拟的入场券。⚠️ 用浮点向量做输入和物理x86和ARM、有FMA和没FMA的机器算出来的轨迹会差出最后一位。今天对得上明天换台设备就回滚雪崩。确定性项目里浮点只能出现在表现层。服务端收集与广播缺帧补上一帧的操作服务端的循环很克制到点了才取这一帧的输入凑不齐就用玩家上一帧的操作顶替然后广播给全房间。int frame room.AuthorityFrame 1; if (timeNow room.FixedTimeCounter.FrameTime(frame)) { return; // 还没到这一帧的时刻 } OneFrameInputs oneFrameInputs self.GetOneFrameMessage(frame); // ← 缺输入→补上一帧 room.AuthorityFrame; RoomMessageHelper.BroadCast(room, sendInput); // 广播给房间 room.Update(oneFrameInputs); // 本地推进一帧补上一帧这个策略值得记住短暂丢包时角色保持上一帧动作而不是瞬移或冻结观感自然得多。出错怎么修hash对账与快照回滚预测回滚 ≈ 你先替队友按下按键等他确认你按错了再倒回分歧帧重按。ET的实现分三步走。预测抢跑上限5帧客户端的更新循环是整篇demo里最精华的一段值得逐行看while (true) { if (timeNow room.FixedTimeCounter.FrameTime(room.PredictionFrame 1)) { return; // 时间轴没到不抢跑 } if (room.PredictionFrame - room.AuthorityFrame 5) // ← 最多只预测5帧 { return; } room.PredictionFrame; OneFrameInputs oneFrameInputs self.GetOneFrameMessages(room.PredictionFrame); room.Update(oneFrameInputs); // 本地推帧 root.GetComponentClientSenderComponent().Send(frameMessage); // 上报输入 }GetOneFrameMessages内部是复制权威帧输入 覆盖自己这一帧的输入也就是典型的本地预测别人按什么先用已知信息猜自己按什么直接用真实按键。⚠️ 5帧上限不是保守是算出来的5 × 50ms 250ms缓冲。网络抖动一超过这个数玩家就会肉眼可见地顿一下。如果你发现卡顿集中在网络毛刺时段先查RTT分布再谈其他优化。回滚快照、hash与重放每推进一帧RoomSystem会把整个世界序列化进环形缓冲顺手算一个hash存下MemoryBuffer memoryBuffer self.FrameBuffer.Snapshot(frame); // 环形复用不扩容 memoryBuffer.SetLength(0); MemoryPackHelper.Serialize(self.LSWorld, memoryBuffer); long hash memoryBuffer.GetBuffer().Hash(0, (int)memoryBuffer.Length); self.FrameBuffer.SetHash(frame, hash); // ← 对账用FrameBuffer的容量是60秒帧数槽位按frame % capacity循环覆盖——内存占用恒定跑再久也不涨。收到权威输入后拿权威帧hash和预测hash一比一致预测段全部转正不一致GetLSWorld从分歧帧的快照反序列化出世界用权威输入一路重放到当前帧期间表现层插值抹平那一下跳变。⚠️ 重放的前提是同输入必同结果。任何一帧里冒出随机数、浮点、未量化精度重放出来的世界和服务端就会永久分叉。hash对账就是为此存在的上线前别把它当调试工具关掉。另外存档侧不是每帧都落快照输入逐帧记录完整世界快照每60秒一帧才存一份SaveLSWorldFrameCount 60 × 20帧/秒回放时从最近检查点起步省掉整场重算。动手调优帧间隔、输入体积与快照省法调优参数先落在一个常量类里全部可改public static class LSConstValue { public const int UpdateInterval 50; // 逻辑步长 ms public const int FrameCountPerSecond 1000 / UpdateInterval; // 20帧/秒 public const int SaveLSWorldFrameCount 60 * FrameCountPerSecond; // 60秒一存档 } 三组问题→调法→效果照着改就行1. 帧间隔怎么定问题20Hz下预测缓冲上限只有250ms弱网下明显顿。调法把UpdateInterval降到33ms约30帧/秒或16ms约60帧/秒预测上限5帧不变缓冲分别扩到约165ms和80ms。效果同RTT下感知延迟减半代价是每房间网络包频率和CPU帧计算量翻倍。竞技对局建议30Hz起步休闲玩法20Hz足够。2. 输入怎么精简问题有人往输入里塞摇杆原始float、角色朝向、甚至技能目标坐标单包轻松上百字节。调法严格锁定量化向量位掩码这一形态一个玩家20Hz下每帧约10~14字节8人房每房间上行带宽在1.5KB/s量级。效果包体越小弱网重传越便宜KCP/QoS策略越好做。⚠️ 常见误区把服务器权威理解成状态也发过去——帧同步里状态一个字都不该走网络输入才是全部流量。3. 快照怎么省问题每帧全量序列化20Hz下内存拷贝和CPU开销不小。调法环形缓冲复用槽位内存恒定、hash复用序列化后的字节流一次算完回滚对账零额外成本、存档检查点拉长到60秒一份回放存储只存增量输入。效果60秒对局回滚所需内存基本只由环形缓冲容量决定与对局时长无关。![ET框架帧同步演示场景](https://raw.gitcode.com/GitHub_Trending/et/ET/raw/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.lockstep/Assets/GameRes/Loading/Sprites/Warrior_Background2 1.png?utm_sourcegitcode_repo_files)上面就是仓库里帧同步demo的运行画面。想系统看设计细节仓库作者出过一套B站的《帧同步设计》视频课共12集讲的就是预测回滚这套东西配合仓库里的Book/1.1运行指南.md跑起来读源码效率最高。真实项目验证商业MMO跑出来的数据项目团队规模周期核心指标千古风流大型MMO100人2年单服64核128G物理机1.5万在线策划限流6000人时CPU约30%Release版估算可上3万在线危境MMO抖音可搜1技术1策划——2人完成开发并持续运营直播 帧同步demo的Room组件架构定步长、预测帧、快照环、hash对账和这些商业项目用的是同一套底座。区别只在规模单机demo跑1对1线上跑满房软路由分线。⚠️ 别把demo的常量直接抄上生产线UpdateInterval、预测上限5帧、存档间隔60秒都是演示值上线前用机器人压测重新定。快速上手清单clone ET仓库仓库地址https://gitcode.com/GitHub_Trending/et/ET项目根目录执行pwsh ./Scripts/Initialize-Project.ps1完成初始化按Book/1.1运行指南.md跑起demo重点读Packages/cn.etetet.lockstep/Scripts/Model/Share/LSConstValue.cs改UpdateInterval感受20Hz与30Hz手感差异对着LSClientUpdaterSystem和LSServerUpdaterSystem走一遍预测→广播→回滚三条路径在hash比对处打日志用仓库内置的test机器人机制拉起多个机器人客户端模拟弱网压测同步稳定性有问题去官方QQ群提问474643097已满新手群 688514974【免费下载链接】ETUnity3D Client And C# Server Framework项目地址: https://gitcode.com/GitHub_Trending/et/ET创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表