前言:
如今朋友们对“游戏同步技术”大约比较注重,各位老铁们都想要剖析一些“游戏同步技术”的相关知识。那么小编同时在网络上搜集了一些有关“游戏同步技术””的相关知识,希望姐妹们能喜欢,朋友们一起来了解一下吧!导语: 一文看懂帧同步,尽量用浅显的语言,让各个岗位的童鞋都能看懂并一起参与讨论,也欢迎各位高手不吝赐教!
why-为什么要帧同步
在最早的单机游戏中, 对局中的信息只需要在玩家本地存储即可,最多CS架构再增加一个单机后台.
当进入多人游戏后,情况变得复杂起来, 因为游戏对局中展现的每一幅画面, 都需要同步给游戏中每个玩家, 这意味着每幅画面的每个物体/角色/属性都需要在多个玩家中同步.
最直接的想法就是直接同步所有物体/角色/属性的状态, 也就是状态同步. 但随着对局玩家数越来越多,物体越来越多时,每次状态的信息量也越来越大,比如5v5的星际,每人操作上百个单位. 再考虑到人眼对12帧左右的连续画面识别为动态视频,而游戏中流畅的FPS一般会到60帧/s, 那么每秒需要同步的状态信息就被进一步放大了.该如何优化呢?
what-什么是帧同步
很显然,状态同步有很多冗余信息,比如房子,树木一般是不会变化的,而玩家移动,释放技能也都是渐变的,不会突然从树下直接到了树顶.
如果把游戏看做是一部互动类型的电影, 那么所有的信息=电影初始画面+所有玩家的输入. 所以是否可以只同步每个玩家的输入呢? 毕竟用户的输入是必须同步的信息的最小子集,答案是肯定的,这就是帧同步:「仅发送输入来保持同步」
假设
· 每个玩家有相同的初始画面和参数
· 所有玩家的客户端运行完全相同的代码
· 所有玩家的输入都实时且成功地同步给其他每个玩家,即每个时刻的输入都完全相同
那么每个客户端都能运行最终得到一样的结果,这就是帧同步如何工作的.
困难的开胃菜
理想是美好的,现实是残酷的.
· 网络是一致性的最大挑战, 特别是从PC->移动互联网, 延时和丢包都变得更加严峻,因为移动场景下,wifi和4G切换,基站之间切换,信号变差变得非常普遍.
· 一样的参数和逻辑总会保持一样的输出吗?想想ios/android平台,不同的编译器和CPU架构,比如浮点数的计算.而一个小数点的变化就像蝴蝶效应一样,结果可能千差万别
· 延迟,用户按下按键到被程序识别并打包发出至少需要16ms,理论上的最佳体验-单机游戏的延迟,即从用户按下到最终屏幕上发生变化也需要40ms左右,而4G网络的延迟平均有40ms, 这还没考虑网络延迟和硬件性能等(5G号称能降低延迟到最低1ms,但笔者持怀疑态度)
问题指向两个点: 「确定性(或者「一致性」) 和「时效性」. 其实这里还省略了UI展现的部分,比如「逻辑与显示的分离」(比如逻辑层保持30帧/s,但显示层保持60帧/s)和「手感」(≈「流畅度」)
业界做法
其实在最原始的帧同步(Basic Lockstep)中,同步的是玩家状态而不是玩家的输入, 同时为了让每个玩家每个帧都同步, 所有玩家必须等待其他玩家的状态更新,才能继续下一帧,即玩家状态的更新彼此锁定,所以就叫锁步(lockstep为啥叫lock就是这个原因).
这样,一局游戏的实时性由该局游戏中网络最差的玩家决定,也就是如果一个玩家卡住,则所有玩家一起卡顿. 在PC互联网和局域网(ping值1ms左右)时代,这个问题并不突出,但从发展趋势看,到现在基本上已经废弃这种方式.
现在通常大家说的帧同步,一般都是指确定性锁步. 即用同步用户输入input来替代同步状态.
星际争霸1使用p2p lockstep, 星际2使用CS lockstep
Dota(Warcraft 3 引擎)最高指挥官1也使用了lockstep,后来都转向CS(是否lockstep未知)
MMOs几乎不用lockstep,因为玩家数太多
如何解除lock锁定? 有人提出了以固定时间周期结算帧, 如果有延迟就到下一个时间周期结算,这就是定时帧同步(Bucket Lockstep)
根据服务器架构,又可以分为客户端相互直连的p2p,或者典型的CS客户端-后台架构. 所以总结如下:
how-怎样实现一个通用的帧同步解决方案
显然,我们选择了定时同步+CS.
网络
大部分游戏对延迟的要求都非常苛刻,所以在弱网络延迟上表现糟糕的tcp基本可以放弃治疗.目前业界基本都使用udp或可靠udp,我们使用的是可靠udp(又称lwip)+udp结合的方式.
· 对要保障可靠性的场景用lwip,比如对局结算.
· 对每帧的下行采用多倍冗余的普通udp.
根据实时性要求高的行业经验(实时语音等),冗余是对抗丢包的最佳方式
单个包的大小最好控制在MTU(含ip和udp包头)内,否则可能有少量用户收包异常. 根据线上经验,90%的用户MTU在1400 byte左右, 但有<2%的用户MTU<575 byte.
上行包一般很小,大部分游戏的单个玩家的单个input<20byte, 因为只包括用户的几个按键. 不过也有少数游戏会突破60 byte
下行包可能冗余多个帧,而每个帧大小=上行包大小*玩家数.
客户端
客户端需要考虑在网络抖动导致帧率变化的情况下, 如何用缓冲,预测,逻辑和展现分离等方式把玩家体验做得更流畅,终极目标是像单机游戏一样没有任何滞后感.游戏侧实现时
· 对浮点数计算, 可以用定数的方案来模拟,比如查表
· 对随机数,可以用伪随机+统一的初始化种子来实现
来保证程序处理的一致性
后台
而帧同步的后台像极了聊天室/群组的后台, 主要是
· 接收每个玩家的输入
· 把所有玩家的输入广播给每个玩家
区别只在于, 如何选择广播的时机,以及如何组包. 这时我们可以把后台看做视频播放器的后台,服务器按照一定帧率定时下发相同的内容给每个客户端(保证用户输入的一致性).而每帧的内容就是该帧时间周期内收到的所有用户input输入汇总.帧计数从1开始递增,如果用户在时间n没有收到第n个帧,会主动向服务器请求补帧.
因为服务器没收到/晚收到自己的输入,网络差的玩家会容易被对方击败,但不会影响其他玩家;而网络/机器好的用户玩起来很流畅,也会收到额外的人头:) 还有种思路是对用户的上行输入input做冗余,考虑单个input占用字节少,这是一种不错的优化方案
最后整体架构如图
· RelayMgr: 为GameSvr提供创建房间,销毁房间,查询房间的功能
· tconnd4relaymgr:作为RelayMgr的接入层
· RelaySvr:为同一Room(一次PVP对局)的玩家提供收集帧数据,组帧,广播帧数据的功能. 一个RelaySvr可以同时为多个房间服务
· tconnd4cluster: 集群化tconnd,作为RelaySvr的接入层, 提供服务给service_api(即Gamesvr),包括订阅房间,接收用户input,后台插帧,房间事件通知等
· tconnd_for_client*:作为RelaySvr的接入层,为客户端玩家提供UDP接入功能
· tconnd_for_client_tcp*:作为RelaySvr的接入层,为客户端玩家提供TCP接入功能
· LockStepFrame:面向客户端的接入SDK,提供建立连接,发送、接收帧数据,关闭连接等接口,同时内置测速功能
· LockStep:service_api:面向GameSvr的后台SDK,linux c++实现, linux平台, 提供创建房间,销毁房间,查询房间接口,同时提供了帧数据的订阅功能
· GameSvr: 业务侧实现的服务,linux平台,因GameSvr集成了service_api,故service_api可某种程度上等同于GameSvr,比如在调用关系上
工具
前面我们说过, 确保一致性很重要,但开发过程中很容易出现各种问题导致不一致, 发布后现网也可能出现更多不一致, 如何解决和发现呢?我们提供了一套一致性检测工具,像bugly一样方便开发定位问题,查看不一致的top机型/版本等,gcloud portal
effect-做的如何
在借鉴王者帧同步的基础上, Gcloud沉淀了一套帧同步的解决方案,包括客户端SDK,后台API
· 使用: Gcloud lockstep
· 目前仅面向内部开源
lwip基本实现了丢包10%场景下体验较流畅, 已经成功应用在当前最火的国民手游上
现网某格斗类游戏使用了帧同步组件后达到的效果:
· 丢包率(客户端上行丢包率):接近98%的用户丢包率为0, 丢包率>5%的用户占比小于0.5%
· 延时(客户端发出到收到svr回包的时间):<80ms
· 帧数据延时<200ms 占比超过97%平均:120ms左右
归一化指标是期望用一个维度来评测整体质量,还在摸索中.
to do-未来展望
标签: #游戏同步技术