UE4网络同步思考(二)---大世界同步方案ReplicationGraph

在上一篇文章中

Jerry:UE4网络同步思考(一)---经典同步方案zhuanlan.zhihu.com图标

介绍了下UE经典网络同步方案,适用于绝大多数射击游戏同步场景,但眼下吃鸡玩法正兴,Fortnite大世界有多达50000+个同步对象,按照传统的同步方案会给服务器的CPU带来巨大的压力。

专用服务器(Dedicated Server,简称DS)CPU的消耗位于网络同步的发送端,因此我们先回顾下经典网络同步方案中的发端算法。


举个图例,DS每帧会遍历游戏世界里的所有对象,这里假定是A1,A2,A3,排除掉不需要进行同步或者不相关的A1,再根据A2和A3的同步属性划规到不同的连接通道Connection里面,这里A2与所有客户端连接都相关,而A3仅与Connection2相关。因同步带宽有限,只能保证高优先级的Actor最先同步,所以需要进行排序。排序后就是每个Actor内部的同步属性比较,以及将变化属性的地址和变化量写入包中并发送。

注意经典发端算法存在的几个问题:

(1)每帧遍历所有Actor,还要逐个计算每一个Actor与每一个客户端连接的相关性,即使这些相关性是确定的(如同队玩家)。

(2)一些场景中固定不动的同步对象,比如拾取物Pickup,房屋门窗(因有破坏性所以需同步)都会在每帧计算相关性,而客户端玩家在有限时间内不太可能有较大范围的空间移动。

以上,如能利用起大世界的空间相关性,就可以进一步优化网络同步的发端算法。

一种想法是将大世界网格化,每一个需要同步的Actor都会落在其中一个格子里,那么只要规划好每个格子的大小,就只需要同步玩家周围的九个格子里面的Actor即可,如下图所示橙色区域所示,还要包含玩家蓝色太阳自身所在的格子,形成九宫格:

这样对于静态物体,比如拾取物pickup,就不再需要每帧计算相关性了,只要有玩家当前所在位置,周围九宫格内(含自身)的Actor都是需要同步的。

对于动态物体,比如其他玩家,他们的位置每帧都可能在变化,仍可空间化到具体的栅格中来,只需要每帧更新玩家所在的栅格即可。

所以对一个客户端玩家来言,每帧需要做两件同步相关的事情:

(1)根据位置拉取它周围九宫格内的同步对象

(2)自身位置变换时需更新所在栅格,要保证其他客户端连接能及时地同步自己。

上述算法已经考虑到了空间相关性,但每个格子划分多大也是要讲究的,划分太大则同步对象太多,CPU负担降不下来,划分太小又可能因为同步不及时带来体验上的问题(eg:被一个看不见的敌人杀掉)。

对于Pickup来说,因为大多集中在室内,所以同步距离可以少一些,但对于玩家来说,同步距离则要大到视距,这样格子划分的大小很难统一。保守做法就是统一按最大视距来划分格子,但这样优化量就很少。另一种做法是分门别类,根据类型划规不同大小的栅格,然后分开处理不同类型对象的同步,缺点就是存在多套栅格系统,是一种以空间换时间的做法。

UE4的ReplicatonGraph解决了上面的问题,相较于上述将一个对象放置于一个栅格中,转而设置这个对象的多个影响栅格,具体如下图示:

每个对象设置了一个同步相关的CullDistance,

将之栅格化:

橙色区域为其影响的栅格:

只要客户端玩家走进了橙色栅格中的任意一个,都会收到这个太阳对象的同步信息。以下示例:

这个方案的优点是可以根据实际需要设置不同的CullDistance,同时保证了栅格自身大小可以固定不变。而且一般地,相同类型对象的CullDistance是相同的,即只要把CullDistance这个变量配置到类Class身上即可。

这样Class配置会很多吗?当父类Class与子类Class的关键同步属性相同时,只会考虑父类Class,所以不必担心。

同样分析下静态/动态物体的空间化同步,对于静态物体,位置总是固定的,因此不需要实时计算影响栅格,对于动态物体,因为空间相关性,短时间内栅格变化的概率也比较低,即使有变化,也只是少数几个,不必要每帧都新算一遍影响栅格,大家可以研究下UE4是如何增量计算栅格变化的。

以上是需要空间化的Class,还有一些诸如总是同步的对象,仅与某个客户端连接相关(或小队客户端连接相关)的对象,UE4都是分开处理的,具体大家可以参考官方示例ShooterGame里面的ReplicationGraph配置。

总结一下,采用ReplicationGraph充分利用了大世界Actor虽然多但却具有空间相关性的特性,并且考虑不同类型Actor的特性(总是相关的,与特定连接相关的,以及空间化相关的),有效减少了不必要的相关性计算,从而有效节省服务器CPU的负载。

编辑于 2019-02-18

文章被以下专栏收录