程序丨腾讯技术大咖答疑!网络游戏同步技术专场
开发多人同步游戏遇到障碍?使用帧同步、状态同步技术遇到困难?统统不是问题!本期【我问大咖】专场,邀请到了多位腾讯专家,为你独家解答困惑。
问:以《王者荣耀》为例,帧同步如何在弱网络下保持玩家手感的?这里需要增加一个逻辑(数据)层在表现层之外吗?如果有预表现,客户端如何执行呢?怎么把握这个呢?
腾讯客户端专家 邓涛:
1.帧同步逻辑层和表现层是有明显界限的,而且循环的执行频率可能不一样。
2.预表现是表现层的预测机制。
游戏的逻辑是网络数据驱动的,当网络波动,导致数据不能在时间上均匀发送给客户端的时候,表现层如果不做特殊处理就会出现抖动。
如何避免这个抖动呢?
1.网络接收端做一个缓冲器(信号处理里面叫jitterbuffer),将接收到的包,适当均匀化,能起到一定的抗抖动作用,但是作用有限。
2.表现层继续保持自身的运动惯性,按照自己的预测(惯性)继续往前执行。
来位移来说,这个时候逻辑层位置和表现层位置就不一致了,等后面逻辑往前推进,得出新的位置,这个时候就将表现位置修正成对应的逻辑位置。
如何修正呢?
1)暴力拉扯,如你看到的,很多游戏的瞬移。
2)温柔修正,利用多余的位移,路径平滑等等方式修正,让用户看起来是流畅的。
3.怎么把握这个呢?
一般是表现层的预测有个阈值,可能是时间,或者其他的事件,对于位移可能是位移的距离,超过这个阈值可能停止预测。
修正上也有个阈值,低于某个阈值,温柔修正,超过某个阈值,暴力修正。
这个阈值怎么来的,自己测试来的,不同游戏对阈值的敏感度不一样。
对于手感的问题,直观体验是用户操作的响应时间,做法跟上述类似,表现层做预先表现,不是所有的都能预先表现,一些导致差异较大的预表现一般会锁住的,比如放个大招,这个不好修正,总不能放一半又收回来吧。
问:《王者荣耀》用的是帧同步,所以检测物理碰撞应该也是每帧检测一次,Unity物理碰撞是不可控的,所以应该不是用的Unity的物理系统,我想问一下《王者荣耀》的物理碰撞检测是怎么实现的?用的是什么算法?
腾讯客户端专家 邓涛:
确实如你所说,不能用Unity的物理碰撞,得自己实现。
据我了解,算法用的都是常规的规则几何碰撞检测算法,加了简单的规格空间分割,子弹用了swept volume,用来实现连续的碰撞检测,防止时间片不够细导致的“子弹”穿透问题。更具体和更深层次的碰撞技术,推荐你去看看一本书 《实时碰撞检测算法技术》。
问:手游版《火影忍者》是如何同步的啊?UDP 直接发送? 还是服务器转发?
腾讯客户端专家 邓涛:
1.火影用的帧同步
2.用的UDP,跟王者类似 (UDP带冗余)
3.帧同步有个帧收集的过程,需要“服务器”转发,理论上可以像星际那样,其中一台机器做服务器,但是现在的网游都是以服务器转发的 ,一方面服务器更好收集过程信息,另一方面,也更稳健,万一做主机的客户端网络不好或者崩溃了呢。
问:做帧同步,为了保证客户端计算一致,就必须要自己去实现物理系统吗?骨骼动画也用不了了?还是有其他可以解决方案?
腾讯客户端专家 邓涛:
一. 物理系统主要解决两方面的问题:
1.动力学
2.碰撞检测(碰撞解决属于动力学得部分)
如果游戏逻辑需要解决上述的问题,为了保证计算一致性,必须使用一致性的物理系统, 要么拿已有的物理引擎源码改,将浮点数运算改成定点数,(我们就常识改造过box2d),要么自己实现,很多特定的游戏逻辑对物理的需要并不复杂,自己实现也不困难。
主要注意的问题是:
1.物理引擎在某些动力学运算的时候有较高的数值精度要求,定点数在考虑数值精度的时候需要特别注意,精度在一定程度上跟运算量成正比。
2.高精度的定点数运算性能非常低,可能比浮点数的性能开销高两个甚至三个以上的数量级,所以不适合规模较大的应用场景,问题规模设定的时候要特别注意。
二.骨骼动画
跟前面一样,如果你的逻辑一定要跟骨骼动画有关系,那就得自己实现一致性得骨骼动画了。
骨骼动画得需求也有层级,对于逻辑,可能大部分时候需要骨架的动画,不需要蒙皮,如果不考虑动力学,自己实现起来也不算复杂,蒙皮和融合会比较麻烦,但是一般是表现层的问题。
三.还有其他方案吗?
有个简单的方法,就是不要这些东西。
辨识清楚什么是表现层,什么时候是逻辑层,把这个问题认识清楚,对于判定技术需求至关重要。
一般来说不做物理倾向的游戏,逻辑层对物理的需要可能没那么重。
比如,我们做一个游戏,需要在一些技能,把打死的士兵打得四处乱飞。
注意:士兵是在被打死了才飞的,表现层关注的是士兵飞散的表现,而逻辑层关注的可能就是死亡状态和时间(准备回收)。
骨骼动画有同样的问题。之前见过一个项目,3D动作类的,攻击判定框绑在角色对象身上,随骨骼运动而产生碰撞事件。
我给的改造建议是,如果没有特殊的需求,可以把碰撞体与骨骼运动的同步,不依赖于骨架的绑定关系,而依赖于时间。之前的2D序列帧动画,没有骨架的情形下,碰撞是怎么配置的呢?不就是利用时间轴同步的吗
问:想了解下有什么机制来验证一致性。毕竟浮点,随机数,物理系统等等都有可能造成不一致。
腾讯客户端专家 邓涛:
我们真做了一个通用框架来帮助开发同学检测和分析不一致性。
原理上大致如下:
1.定义个逻辑帧状态的切片(FrameStateSnapshot),帧切片里面是游戏对象的状态以及本帧发生的事件。
2.每个逻辑帧跑完,生成本帧的切片
3.将帧切片序列化成一个二进制串
4.可选:将二进制串生成MD5
5.可选:将MD5码和帧序号发送给服务器,服务器可以通过这个MD5码判定每个客户端的帧切片是不是一致
6.将帧切片的二进制串保存到本地文件(也可以带MD5)
7.打完一场之后,生成了一个状态录制文件
比较各个客户端所录制的这些数据,可以知道哪帧不一致,这个帧里面哪个数据不一致了。
问:玩家断线重连之后应该怎么处理,或者说玩家中途加入游戏,应该怎么处理,之前所有指令全部发送给该客户端吗?
腾讯客户端专家 邓涛:
1.断线重连(假定没crash)
客户端告诉服务器我当前跑到第n帧了,服务器把第n帧到最后一帧这个区间的数据发给客户端。
2.断线重连(crash或者上下文清理掉了)
服务器下发初始数据 第0帧到当前帧的数据。
3.中途加入
如果不是第二种情形的话,而是你本来不在里面,想加入一个已经开始的游戏,这个做起来比较困难,虽然有办法可以做,建议你把帧同步的问题全部搞定之后再来考虑这个问题。
问:断线重连是通过tcp将丢掉的数据包重新发给客户端吗?
腾讯客户端专家 邓涛:
其实用什么无所谓,关键是要确保数据正确发到客户端,至于如何保证,最后再说,我们先讨论发什么。
为什么要讨论发生,是因为帧同步和状态同步发的内容在某个语义层面上是不一致的。
简单说:
状态同步发的是服务器当前的结果数据。帧同步发的是客户端掉线以后到服务器当前帧的过程数据。
如果保证数据正确发送到客户端呢?
1.TCP
2.可靠性UDP
UDP的可靠性,可以建立在传输语义层面,实现出来的模型原理跟TCP差不多。
也可以建立在业务语义层面,比如帧同步,每个过帧输入包是带帧序号的,少了一个业务层是知道的,请求重发。
当然,还有一些技巧来降低udp在业务语义层的丢包概率,原理上是一个数据包多发几次,那么这个包丢失的概率就低了。
问:服务器用的什么框架和系统来承载几十万人甚至更多人同时在线呢?在此情况下还要保证游戏稳定性和流畅性,希望能给一个好的服务器框架和技术方案。
rainfu:
服务器的主要架构目前主要是两种,一种是分区分服的,一种是全区全服的。分区分服的架构下,单个区的承载上限是固定的,比如同时就只有5000在线,一个区满了,就不停的开新区就可以了,这样也可以同时承载几十万在线,但是,单个区的承载很低,所以架构很简单,在这里我们就不说了。
题主在这里想问的应该是全区全服架构下如何承载这么高的同时在线,这块目前腾讯这边的很多游戏就是这样的架构,比如端游下的QQ飞车、QQGame,手游下的很多游戏也是这样的架构,虽然在客户端表现出分区,其实只是逻辑上的分区,实际上还是全区全服架构,即,所有玩家可以在一起玩。
全区全服架构下,单台物理服务器的承载上限还是有的,比如只能承载一万人,那么当超过的时候,就需要扩充服务器,所以,可扩展性是全区全服架构的一个核心设计思想,即所有模块都要具备可扩展性,那自然就牵涉到某个模块有多台服务器的时候,负载均衡的问题,这又是一个比较复杂的问题,负载均衡有很多中方式,这个网上有很多,没有哪个是最好的,需要根据自己的系统和业务需求来设计最合适的。
另外要保证系统的稳定性,还需要考虑到高可用性,即避免单点故障,或者退一步,柔性可用,某个服务器坏掉,不影响整体服务,可能是某个服务不可用,或者某部分用户不可用,但是大部分都是ok的,不能说因为某台服务器挂掉了,导致所有服务都不可用,那是不可接受的。
目前腾讯内部的服务器架构,都是自己实现的,没有通用的对外可以用的框架,研发中心有一套自己的公共组件,暂时也没有对外,这个还是需要自己来写;之前网易的云风有写过一套skynet,是开源的,不过我没有仔细研究过,也没有用过,你可以去了解下。
问:unity5中的动画系统应该是不能用在帧同步逻辑帧上面的,现在的做法是逻辑帧切换逻辑对象的状态,比如移动,攻击,待机,这类迅速切换不需要等待的动画好处理,但比如动画中需要等待动画完成再去执行逻辑,比如变形动画,在变形动画结束之后才可以攻击,移动之类的,这个在帧同步中怎么去处理。现在的想法是在变形动画开始的多少帧之内不接受输入事件,但是并不是一个精确的做法。
gavindong:
帧同步的做法中,逻辑层和表现层应该严格的分开,攻击、移动之类的属于逻辑层,而动画播放属于表现层,所以逻辑层的东西理论上是不应该对表现层有依赖,你这种表现层播动画,逻辑层根据时间(或帧数)来驱动的做法其实正是比较规范的做法。
关于逻辑层如何得到时间,可以在开发工具流中解决,例如编写工具将动画时间统一导入到逻辑层用的表格中。
在解决这个问题后,仍然留下的精确性的问题可以分两种,一种是时间上的精确,另一种是位置上的精确。
时间上的精确性问题主要体现在两种表现衔接的时候,例如一个动画播放完成时需要立刻切入新的一个动画,如果中间有一帧间隔,则会发生表现上抖动,解决这个问题可以让美术将前一个动画稍作延长,多在最后一帧的姿态保留几帧,或是在适当的条件下直接将两段表现整体制作,整体播放,仅在逻辑层对前后两个时间段分割。
位置上的精确性需求通常体现在需要从动画中获取具体骨骼或者碰撞体的位置,依赖这个位置做后续逻辑判断的情况。解决的办法仍然是将需要依赖的每帧的对象位置从表现层的动画文件中导入到逻辑层。这样逻辑层就能独立获取出相关的位置。
如果要做得更完美,可以在表现层对输入进行预表现,在预表现结果和逻辑层实际运行结果有差异或差异过大时进行表现修正。
小编注:如果想查看更多专家答疑,可前往(PC端网址:http://gad.qq.com/wenda/activity/10016)查看,也可点击阅读原文查看噢。
----------------------
今日推荐
添加小编微信,可享双重福利
1.加入GAD程序猿交流基地
获取行业干货资讯,观看大牛分享直播
2.领取60G独家程序资料库,地址在小编朋友圈
包括腾讯内部分享、文章教程、视频教程等全套资料
↓长按添加小编GAD苏苏↓