基于守望先锋ECS网络同步:
程序丨暴雪Tim Ford:《守望先锋》架构设计与网络同步
本文主要聊聊预测同步
首先FPS这类游戏东西肯定不会让服务器过分相信客户端,因为总有混蛋开挂,作为一款即使对战游戏,高性能的网络表现一定是重中之重。
案例:
cf的外挂原理就是修改了客户端的数据,接着将修改完的数据上传到服务器,服务器验证不了是否合理过分相信客户端,后来腾讯反外挂检测外界是否修改内存这才将外挂进行了拦截。
前段时间大火的“🐏了个🐏”能出现外挂也是修改了本地
所以不能过于相信客户端,但玩家操作都在客户端上,想摆脱也是不可能的。
但作为一款快速响应(responsive)的网络对战动作游戏,如果每个操作都要等服务器回包的话,就不可能有高响应性了,所以就必须针对玩家的操作做预测(predict,也可以说是预表现)。
fps也就只能:
客户端先发包给出操作,
服务器再返回给出状态。
那如果出现错误咋办?
虽然服务器会纠正这一错误,但作为一款快速反应的系统,不可能通过延迟操作来进行实现。
守望先锋采取了一个叫:”确定性”(Determinism)来减少预测错误发生的概率。
也就是说所有数据都是真实有效的,不存在服务器预判你的操作,产生假数据出现的这一现象。
确定性(Determinism)
依据时间同步技术,先同步服务器和客户端的时间,然后将帧数固定(固定更新周期和量化),包括双方发消息的时间等等,官方叫:“命令帧”,每个命令帧都是固定的16毫秒,不过在电竞比赛时是7毫秒。
比如他们把玩家操作没有放在Update中,放在更加稳定的UpdateFixed,它会在每个固定的命令帧调用。(来张官方的图:侵删)
双方都会按照一定的频率发送消息,进行交互,差不多是这个概念
按照这个流程,双方差不多是互相稳定的在互相发消息,当然从客户端发到收这一过程自然越短越好,但这肯定不现实,碍于延迟、程序相应等因素,这一过程肯定不会能达到想要的那种程度。
他们这个演讲提到一个叫Rtt东西,大致是这样的
RTT=ping+逻辑处理时间
一个RTT大概就是从客户端到服务器再返回到客户端的时间(扣除服务器处理时间)
官方为了计算一个提前量,给每帧加了一毫秒的提前量,作为客户端的处理时间
提前量=(RTT/2)+1(这里的1是1帧,守望的资料给的是16毫秒)
然后客户端开始模拟并把输入的操作上报给服务器,过一段时间(基本上是半个RTT加上缓冲时间)以后,服务器才开始模拟这一帧。
正因为客户端是不停地接受玩家输入,为了尽可能地贴近现在时刻,如果还需要等待服务器回包才能响应的话,那看起来就太慢了,会让游戏变得卡顿
而另外的RTT就是服务器模拟权威带来的副作用,就此客户端要用一个环形缓冲(ring buffer)来记录历史运动轨迹,目的用来处理客户端与服务器之间的错误
如果想简单的处理错误,那就直接用服务器下发的结果覆盖客户端就行了,但是这个结果已经是“旧”(相对于当前时刻的输入来讲)的了,因为服务器的回包一般都是几百毫秒之前的了。
那么接下来就是技术点了?
守望自己用的肯定不是这么简单的方案,当然对于一般游戏来说这基本上够用了
(1)守望采取了关键帧技术方案,具体是这样实现的(官图真好,接着上官图)
我最开始并不关心服务是怎样的,我(客户端)只管操作并给服务器发我的状态,直到服务器给我返回我该有的状态,然后立刻以服务器的为准同步我的状态,而且重新计算之后所有的输入操作,直至追上当前时刻这样就又回到“帧同步”状态了
在这个过程中我们也能够获知我们这种异常状态到底进行了多久
当异常状态解除时,客户端做出反应,与此同时服务器也解除了异常状态,这样就又同步了。如上面官方示意图那样,此时客户端解除了异常状态,服务器发包虽然还是异常,但此时已经开始模拟正常状态了。由此图可见客户端要领先于服务器半个RTT,此时服务器与客户端完全同步。
(2)改装(组装)的UDP
大厂几乎都有这个习惯,就是组装upd。得益于udp特性速度快的特点备受游戏开发人员的青睐,至于安全,一个游戏管他呢也不是啥重要数据传输,只要少丢点包就好了,但天总不会随人愿。
要使用上面的的这个方案有个前提就是稳定的发包收包,可丢了就会破坏这个稳定性,服务器又试图保持了一个小小的、保存未模拟输入的缓冲区,如官方这几个图那样格子中客户端发出来的在格子中会有一点间隙,这就是缓冲区。
一旦这个缓冲区是空的,服务器只能根据你最后一次输入去“猜测”。等到真正的输入到达时,它会试着“缓和”,确保不会弄丢你的任何操作,但是也会有预测错误。
就像上图那样,服务器意识到以后,就会复制先前的输入操作来就行预测,一边祈祷希望预测正确,一边发包告诉客户端:“嘿哥们,丢包了,不太对劲哦”。接下来发生的就更奇怪的了,客户端会进行时间膨胀,比约定的帧率更快地进行模拟。
客户端这时候就会比之前更快的发包,以覆盖从客户端到服务器之间发生的丢包,让服务器能够度过丢包,服务器不再复制状态,以完成同步。
(不得不说这方案真NB,客户端主,服务器从的关系)
一旦客户端赶上来了,就不会再复制输入了,这样会有因为丢包而被忽略的风险。
客户端和服务端各自运行物理引擎,客户端不等待服务器数据而是一路向前运行,这种方案消耗较大且工程量不小
这一预测失败也会产生很多问题,这可以去上面的那个链接看去。
许多情况可能会导致出现问题,服务器或者是客户端双方的错误判定导致的许许多多的问题。
如果出现了这种问题也就只能见招拆招了。
倒是还有一些其他的方案:(指路)
浅谈物理引擎的网络同步方案 - 知乎 (zhihu.com)
对于反外挂在客户端方面腾讯倒是做的相当优秀,直接干到内存感知内存。