Mapz's Blog

可以递归的函数指针

UE4:ReplicationGraph网格同步流程分析

前言

UE4 中的网格分块同步是基于 UReplicationGraphNode_GridSpatialization2D 同步节点的,本文尝试分析 UReplicationGraphNode_GridSpatialization2D 节点的同步流程

UReplicationGraphNode_GridSpatialization2D 下文简称 “分块节点”


相关配置

CellSize:网格的大小
SpatialBias:网格的边界,超过边界会重建网格
ConnectionMaxZ:大于这个高度的玩家不会参与到收集同步 Actor 的过程中


添加和移除同步对象

分块节点本身不保存和移除任何同步对象,对象的记录是通过

1
2
3
4
5
6
7
void AddActor_Static(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo) { AddActorInternal_Static(ActorInfo, ActorRepInfo, false); }
void AddActor_Dynamic(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo) { AddActorInternal_Dynamic(ActorInfo); }
void AddActor_Dormancy(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo);

void RemoveActor_Static(const FNewReplicatedActorInfo& ActorInfo);
void RemoveActor_Dynamic(const FNewReplicatedActorInfo& ActorInfo) { RemoveActorInternal_Dynamic(ActorInfo); }
void RemoveActor_Dormancy(const FNewReplicatedActorInfo& ActorInfo);

来处理的,其中 AddActor_Dynamic 是会移动的 Actor,Static 则是静态不会移动的, AddActor_Dormancy 则是休眠的时候看做静态的,非休眠的时候变为可移动的 Actor

其中 Internal 的几个函数都是 virtual ,可继承后在子类改变其行为

这里只讨论默认的行为

Add Dynamic

如果 Actor 的属性为 bAlwaysRelevant ,会什么也不干,并触发 Warning

否则加入

1
TMap<FActorRepListType, FCachedDynamicActorInfo> DynamicSpatializedActors;

Add Static

如果 Actor 还未初始化,会加入一个

1
TArray<FPendingStaticActors> PendingStaticSpatializedActors;

否则会把它的 GlobalActorReplicationInfo (下文称Actor全局同步Info)
的位置属性赋值为当前 Actor 位置

如果分块节点有范围限制,则在此 Actor 可撑大范围的前提下(通过 Class 过滤)
重新按此 Actor 位置来设定范围

设定规则为当前 Actor 2D 位置减去单个网格大小的二分之一(即是扩展到 Actor 外侧多二分之一网格大小)

然后把对象加入到

1
TMap<FActorRepListType, FCachedStaticActorInfo> StaticSpatializedActors;

并根据其位置,获取到对应的 UReplicationGraphNode_GridCell 结点(代表单个网格的节点,下文称 __网格节点__)

并调用其 UReplicationGraphNode_GridCell::AddStaticActor 函数加入到网格节点中

网格节点在分块节点下保存在

1
TArray< TArray<UReplicationGraphNode_GridCell*> > Grid;

中,为一个二维数组,按 X 和 Y 划分

UReplicationGraphNode_GridCell::AddStaticActor

网格节点继承自 UReplicationGraphNode_ActorList

自带一个同步对象列表

当 Actor全局同步Info 的 bWantsToBeDormant 为 true 的时候

会把这个 Actor 加入到网格节点的 UReplicationGraphNode_DormancyNode 子节点中

否则会直接加入网格节点

Remove Static

Add Static 的反操作,没啥可说的

Remove Dynamic

先从 DynamicSpatializedActors 中找到 Actor 的 FCachedDynamicActorInfo

然后根据其中储存的网格信息找到相关网格,然后调用网格的

1
UReplicationGraphNode_GridCell::RemoveDynamicActor(const FNewReplicatedActorInfo& ActorInfo)

移除 Actor,
然后再移除 DynamicSpatializedActors 中相应的 Actor

UReplicationGraphNode_GridCell::RemoveDynamicActor

找到网格节点的 DynamicNode ,并调用其 NotifyRemoveNetworkActor

DynamicNode 的默认实现为 UReplicationGraphNode_ActorListFrequencyBuckets

但是可以自定义其类型

最终获取同步对象的时候,Dynamic 类型的 Actor 都是从 DynamicNode 上获取的
,而从上面 Static Add 的部分我们可以看出来 Static 类型的 Actor 是直接从网格节点上获取的

关于 UReplicationGraphNode_ActorListFrequencyBuckets

这个节点用于限制每次同步的时候从节点上获取的 Actor 数量,来处理避免扎堆同步的情况,细节上先不表

AddDormancy

如果是 ActorRepInfo.bWantsToBeDormant 为 true
直接调用 AddActorInternal_Static,只是休眠参数传入 true
否则直接调用 AddActorInternal_Dynamic

简单明了

休眠状态的变化,则是通过 Add 的时候添加休眠事件的回调来处理的

RemoveDormancy

和上面那个相反的操作,没啥可说的

Actor 信息存放位置总结

Static 对象引用是放在网格节点上的
Dynamic 对象引用是放在网格节点的 Dynamic 节点上的
Dormancy 对象在休眠的时候,放在网格节点的 Dormancy 子节点,非休眠的时候,放在 Dynamic 子节点上


同步前:PrepareForReplication

我们来查看函数

1
UReplicationGraphNode_GridSpatialization2D::PrepareForReplication()

首先,遍历 DynamicSpatializedActors,做以下操作:

  1. 获取其 Actor 位置,并查看是否需要扩大同步的范围(和 AddStatic 中的操作一样)

  2. 无需重新创建网格的时候,执行以下操作

    a. 获取当前 Actor 所在网格信息

    b. 检测当前所在网格和上次所在网格的关系,如发生改变了,移除不在的网格节点中的 Actor 信息,新增新网格节点中的 Actor 信息

    c. 如果有更新网格信息,则重设 Actor 同步信息中的网格信息

然后,遍历 PendingStaticSpatializedActors,检查是否初始化完毕了,并加入 Static 列表中

如果需要重建网格,执行以下操作

  1. 移除当前的所有网格节点
  2. 遍历 DynamicSpatializedActors 和 StaticSpatializedActors,根据现有 Actor 重建网格节点并加入 Actor 信息

同步前处理内容总结

同步之前,主要做的是重新设定 Actor 的网格的工作,如有需要,会重建网格节点列表


搜集需要同步的 Actor 列表:GatherActorListsForConnection

首先,遍历每个玩家角色

  1. 获得当前玩家所在的网格信息

  2. 获得玩家上次的位置

  3. 合并相同玩家的网格信息,在合并后的网格中,收集需要同步的 Actor

    a. 和上面的 位置 一样,找到相应的网格节点的子节点,收集相关内容

  4. 把玩家所在的网格标记为 Active 网格

如果需要销毁 Dynamic 的休眠 Actor,则遍历 Active 网格,如果 Active 网格发生改变了,则

  1. 找到网格的 DormancyNode 并获取休眠 Actor 列表
  2. 找到 Active 网格的上次的玩家网格信息的 DormancyNode 并获取休眠 Actor 列表

如果 Active 网格发生改变了,并且上次的玩家网格中有休眠 Actor,则遍历休眠 Actor 列表

  1. 通知客户端 Destroy 这个 Actor
  2. 修改这个 Actor 的网格节点上的休眠节点的这个 Actor 的同步状态

收集同步列表总结

先将玩家所在网格标记为 Active 网格,合并重复网格后,收集网格中需要同步的 Actor,然后检查并通知切换了网格的客户端销毁上个网格中的休眠的动态 Actor