龙空技术网

一文看懂帧同步

腾讯技术工程 511

前言:

如今朋友们对“游戏同步技术”大约比较注重,各位老铁们都想要剖析一些“游戏同步技术”的相关知识。那么小编同时在网络上搜集了一些有关“游戏同步技术””的相关知识,希望姐妹们能喜欢,朋友们一起来了解一下吧!

导语: 一文看懂帧同步,尽量用浅显的语言,让各个岗位的童鞋都能看懂并一起参与讨论,也欢迎各位高手不吝赐教!

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占用字节少,这是一种不错的优化方案

最后整体架构如图

gcloud lockstep后台架构

· 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-未来展望

标签: #游戏同步技术