zuhd / blog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FPS游戏同步机制

zuhd opened this issue · comments

commented

在开始正文之前,有个出发点非常重要,同步策略的宗旨:为了玩家的更好游戏体验--Everything for players’ better game experience。一定要牢记,后面还会提到。

一,概念

  1. authority 同步中心,通常我们说的服务器,有同步广播,纠错等功能

  2. master 玩家控制的entity

  3. replica 非玩家控制的其他entity在本地的拷贝

我们所说的同步就是指authority利用不同的策略,让replica保持和master状态同步。

二,(非)确定性模拟

根据不同的游戏类型,可以分为两大模拟策略:确定性模拟和非确定性模拟。

确定性模拟:对于不同的游戏玩家,不管在任何物理设备或外因下(机器硬件/地域/带宽),相同的输入,必然得到的输出,满足一致性。

非确定性模拟:要求较上面宽松很多,允许结果有差异性。

利用确定性模拟的游戏有我们非常熟悉的两款RTS游戏,starcraft & warcraft。总体来讲,这种游戏比较古老,受限于当时开发所处年代的硬件和带宽,这种游戏一般在局域网内体验会很好,battlenet就很糟心了。

确定性模拟的优势如下:

1,每次host广播的网络包体小,仅仅包含玩家的输入
2,游戏世界会严格同步,不会出现分歧

缺点好像更多:

1,对于不同的硬件(浮点数处理)开发和调试难度都很大,尤其是遇到bug很难重现,让程序员异常痛苦
2,一旦host上发现某一帧结果不一致,必须立即结束游戏,别无选择
3,玩家要对地图上所有的entity进行模拟计算,知道为什么澄海3C卡了吧
4,因为3,所以游戏对地图大小以及玩家数量都有限制,一般为开房规则
5,作弊变得容易,开局就口吐芬芳:挂B! ALT + Q再见
6,断线重连难以实现

三,确定性模拟同步策略

1,隐藏延迟的小把戏—延迟输入

我们在游戏里通常会遇到这样的画面,比如法师释放技能的时候搓球,战士砍人的时候把刀高高举起,有没有思考过这样做的目的是什么呢?仅仅是为了增强游戏的逼真性吗?这只是其一,其二便是我们利用前摇这段时间偷偷的把消息发给了服务器,让服务器再广播出去,打了一个提前量,用来对冲RTT的延迟。这是在游戏开发中常用的技巧,非常重要。

2,lockstep

player每一帧都会把消息发给host,host同时会广播给所有人,并发ack给player,让他继续发送下一帧,在此期间所有人都只能卡住等待最后一个人的确认消息。这样的游戏体验在war3或是dota1中我们经常能遇到,一旦有一个玩家有高延迟,所有人都会卡住,聊天频道立马就会各种问号问候全家。那在非LAN的模式下是如何保证游戏流畅运行的呢?除了要求Ping在100以下之外,还有就是上面的输入延迟起到了很大的作用。如果所有的玩家能在输入延迟之内处理掉网络消息,那么游戏就可以顺利的继续,否则有玩家有网络抖动的话,就会每隔几帧卡顿一次,一直到该玩家网络恢复正常。

d1.png

3,lockless

用lockstep固然能精确同步所有玩家的状态,但在网络抖动情况下,游戏体验太差,于是拥有7辆法拉利的游戏大神卡马克创造了lockless同步策略,这种乐观锁的**在后来的多线程或是异步事件处理中也能常常遇到。通常的做法是在host上缓存每个player的frame buffer,并允许客户端在未收到host的input1的ack之前,预判input2的行为结果,利用这段时间来对冲掉RTT,当然如果预判错误,host会无情的rollback到buffer中的历史状态。这样如果在游戏期间,player1出现了网络抖动,只要freeze住player1,其他的玩家正常游戏,再智能一点的做法就是设计一段AI代码为player1托管,让其他玩家感受不到player1的卡,尽管多数情况下这种托管都很傻。当player1网络恢复之后,就会快速播放保存在host端buffer中的Input,追赶上其他的玩家,但多数情况下player1会被干掉。为了保证游戏体验,一般会设置断线次数上限,当player1超出上限,就会被无情踢出游戏。
john carmark.jpg

四,非确定性模拟

非确定性模拟会尊重网络延迟的存在性,并通过策略的设计去妥协和弥补,以保证replica的状态不断的向master状态聚合,最终达到较好的游戏体验。它会针对玩家的延迟程度,低延迟,正常延迟,高延迟做出不同的策略。

  1. 同上,延迟输入是必要的。

  2. replica的状态永远迟于master,且replica会周期性收到的master的snapshot,周期会根据延迟自动调整,原理类似于TCP的滑动窗口。

  3. replica收到的snapshot是不连续的,需要用内插值(interpolation)让他们看起来更平滑。

  4. authority会对shooter做延迟补偿,也就是说为了对冲掉shooter的RTT,authority会让shooter以target的某个历史snapshot做为真正的目标,去进行逻辑判断。这就是为什么我们经常躲在墙角也能被击中的原因,这样做是为了照顾第一视角的游戏体验。

d2.png

  1. 延迟补偿并不能被过度的使用,这样势必会伤害到target。一般的处理办法就是在游戏中设置latency threshold,只有不高于该值才会做补偿,否则不做补偿,一句话:卡à死!对于那些更高的ping,还是早点删掉游戏为妙。

  2. 内插值是使用authority的广播数据,而且是历史数据,replica具有一定的滞后性。有一类游戏,可以使用外插值(dead reckoning)来保证replica和master之间的同步。如赛车类游戏,他们的状态相对简单,这样replica根据上一状态不断推导新的状态,并且向authority上报,一般情况下它是不关心authority的ack,除非偏离超过authority的threshold,这个时候强制同步一次即可,大大节约了带宽和时间。

racing_games.jpg

  1. 如何解决replica和master之间的偏离?

非确定性模拟的精髓就在于如何妥协偏离,它遵循的原则说的通俗一点就是:该软的时候软,该硬的时候就要硬。在网络抖动不是很大的时候,它会用滑动窗口和延迟补偿来让玩家尽量无感体验,一旦网络抖动较大,偏离超出了threshold,服务器可以立即使用拉回,穿墙,TP等不优雅的手段,强行保持一致。

五,首尾呼应

一切优化的目的:JUST FOR FUN!