MOBA 战斗同步实战:服务器权威 + 状态同步是怎么落地的

发布时间:2026/11/4 2:38:01
MOBA 战斗同步实战:服务器权威 + 状态同步是怎么落地的 1. 为什么 MOBA 的战斗同步这么难做 MOBA 的人迟早会撞上同一个问题十个玩家、上百个技能、每帧都在变的血量、蓝量、冷却、位移、Buff怎么让所有人看到的世界基本一致难点集中在三件事上公平性谁先出手、谁先死必须由同一个裁判说了算不能各客户端自己算。实时性技能释放到看到反馈延迟超过 100ms 玩家就会觉得卡。带宽一局 10 人20~40 分钟每秒几十次同步流量必须压得住。我们最终选的是服务器权威Server Authoritative 状态同步State Synchronization这套组合。下面把设计思路、关键实现和踩过的坑完整讲一遍。2. 先明确服务器权威到底权威在哪服务器权威的核心只有一句话客户端只负责表达意图结果一律由服务器计算并下发。在我们的架构里职责划分是这样的角色负责内容不负责内容客户端采集输入、本地预测、表现层插值伤害计算、命中判定、死亡判定服务器技能校验、伤害结算、状态机推进、广播快照渲染、动画、音效客户端点技能发出去的不是我打中了他扣 300 血而是一条意图{type:cast_skill,seq:1024,heroId:1001,skillId:30021,targetId:2003,castPos:{x:12.5,z:-3.2},clientTime:1730548800123}服务器收到后做一串校验技能是否学会、是否在冷却、蓝量够不够、施法距离是否合法、目标是否存活、是否被沉默。全部通过才真正结算然后把结果写进下一次状态快照。这样做的好处很直接外挂改不了伤害改包也改不了结果。客户端就算把本地血量改成满血服务器下一帧快照回来照样打回原形。3. 状态同步同步的是世界状态不是操作状态同步和帧同步最大的区别在于服务器下发的是结果状态而不是所有人的输入指令。我们每 50ms20Hz向所有客户端广播一次世界快照内容大致分三层静态/低频层英雄基础属性、装备、等级变化时增量下发。动态层位置、朝向、血量、蓝量、当前动作状态每帧全量或增量。事件层技能命中、击杀、Buff 添加/移除一次性事件保证不丢。一个简化的快照结构{tick:88213,serverTime:1730548800150,entities:[{id:1001,pos:[12.4,0,-3.1],rot:1.57,hp:1820,mp:340,state:casting,buffs:[{id:7001,remain:3200}]}],events:[{type:damage,from:1001,to:2003,value:312,crit:true}]}关键点位置用定点数或整型压缩血量用差值编码事件单独走可靠通道。这样一帧快照能压到几百字节10 人局 20Hz 完全扛得住。4. 客户端预测与回滚让手感不粘纯状态同步有个致命问题玩家按下技能要等一个 RTT 才看到动作手感像在泥里游泳。我们的做法是客户端预测 服务器校正客户端按下技能立刻本地播放前摇动画同时把输入发给服务器。服务器结算后快照里带上lastProcessedSeq。客户端收到快照把seq lastProcessedSeq的本地预测丢弃用服务器状态覆盖。若服务器结果与预测不一致比如判定未命中做一次平滑回滚而不是硬跳。-- 客户端收到快照后的校正逻辑伪代码functiononSnapshot(snapshot)localackSeqsnapshot.lastProcessedSeq-- 丢弃已被服务器确认的预测while#pendingInputs0andpendingInputs[1].seqackSeqdotable.remove(pendingInputs,1)end-- 用服务器状态作为基准localbasesnapshot.entities[myHeroId]-- 重放尚未确认的输入for_,inputinipairs(pendingInputs)dobasesimulateLocal(base,input)end-- 平滑过渡到重放结果避免瞬移smoothTo(base)end这里有个经验预测只做表现不做结算。客户端可以预测技能放出去了但绝不预测打中了、扣血了。否则一旦服务器判定不同回滚会非常难看。5. 位置同步插值、外推与延迟补偿位置是最容易看出同步质量的地方。我们用了三套手段叠加插值Interpolation客户端渲染落后服务器约 100ms在两个快照之间做线性/样条插值画面平滑。外推Extrapolation快照丢失时按速度外推最多 150ms超过就停下等新数据避免飞出去。延迟补偿Lag Compensation服务器保留最近 1 秒的历史位置处理指向性技能时按客户端上报的延迟回溯目标位置。延迟补偿的伪代码// 服务器处理指向性技能时的回溯判定publicbooleancheckHit(longclientTime,intcasterId,inttargetId,Vector3castDir){longrewindTimeclientTime-estimatedRtt(casterId)/2;Vector3historicalPoshistoryBuffer.getPosition(targetId,rewindTime);returnisInSkillRange(historicalPos,castDir);}注意回溯窗口不能开太大我们限制在 200ms 内否则会出现明明躲开了还是被打中的观感问题。6. 带宽优化从 20Hz 全量到增量压缩早期我们天真地每帧全量广播10 人局峰值跑到 200KB/s手机端直接劝退。后来做了几层优化增量同步只下发与上一帧不同的字段位置变化小于阈值的不发。量化压缩坐标从 float 压成 16 位定点朝向压成 8 位角度。优先级调度视野内实体高频同步视野外低频同步。事件合并同一帧内的多次伤害合并成一条事件。优化后单客户端下行稳定在15~30KB/s弱网下也能跑。7. 踩过的坑坑一客户端预测导致双份伤害表现。一开始客户端预测了伤害数字飘字服务器又下发一次玩家看到两个数字。解决方式是飘字只由服务器事件驱动客户端预测只做动画。坑二快照乱序。UDP 下快照可能乱序到达早期直接覆盖导致位置回跳。后来给每个快照加tick只接受比本地更新的快照。坑三断线重连状态不一致。重连时客户端本地状态已经过期必须由服务器下发一次全量快照isFullSnapshot: true客户端清空本地预测队列后重建。坑四技能前摇期间被控制。服务器判定施法被打断后客户端预测的动画还在播。我们加了castId校验快照里带interruptedCastId客户端收到就立刻打断本地动画。坑五没做缓冲各客户端画面依然不同步。早期我们以为只要服务器权威、快照一致大家看到的就该一样。实际跑起来才发现每个客户端的网络延迟不同快照到达时间参差不齐A 玩家看到自己已经走开B 玩家屏幕上 A 还站在原地团战里经常出现我明明躲了的争议。后来我们引入了渲染缓冲Buffer客户端不直接渲染最新收到的快照而是把快照先放进一个队列延迟一段时间再取出来播放。最初缓冲设得比较保守实际显示的画面是250ms 之前的状态——好处是各客户端都稳定落后服务器同样的时间画面终于对齐了代价是操作反馈明显发钝手感变差。之后我们逐步把缓冲时间往下压最终降到100ms左右客户端操作几乎没有肉眼可感知的差距同时各端画面依然保持同步。这里的关键是缓冲时间要大于网络抖动的波动范围否则队列会被抽空、出现卡顿或跳帧而缓冲越小手感越好。所以它是一个需要结合自己项目网络质量反复调参的平衡点不是拍脑袋定一个数就完事。8. 小结服务器权威 状态同步这套方案本质是在公平、手感、带宽三者之间找平衡服务器权威保证结果唯一、防作弊状态同步让网络模型简单、易回放、易重连客户端预测 插值 延迟补偿把体感拉回可接受范围。如果你的 MOBA 还在纠结帧同步还是状态同步我的建议是强对抗、需要严格防作弊、技能逻辑复杂的优先状态同步对确定性要求极高、需要完整录像逐帧复现的再考虑帧同步。两者没有绝对优劣只有是否匹配你的项目。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询