虚幻引擎中Actor复制流程

Detailed Actor Replication Flow暂时没有官方翻译,尝试理解顺便翻译一下。

简介

Actor复制是一个详细的多步骤过程,其中网络驱动程序(Net Driver)确定需要向哪些连接复制哪些Actor,以及按照什么顺序复制。本页面提供了Actor复制流程的概述。

大多数Actor的复制是在UNetDriver::ServerReplicateActors函数中进行的。 在这个函数中,服务器会首先收集所有被判定对每个客户端相关的Actor,然后将自上次更新以来发生变化的属性发送给每个已连接的客户端。随后,UActorChannel::ReplicateActor 函数负责处理将特定Actor复制到某一通道的详细操作。

重要属性

对于如何更新 actor、调用某些框架回调以及用于确定在当前服务器 tick 期间是否复制 actor 的属性,有一个定义的流程。以下是一些重要的属性:

属性 描述
AActor::NetUpdateFrequency 决定Actor复制的频率。
AActor::PreReplication 在任何复制操作发生之前调用。
AActor::bOnlyRelevantToOwner 如果该Actor只会复制给其拥有者,则为True
AActor::IsRelevancyOwnerFor bOnlyRelevantToOwnerTrue时,决定Actor的相关性。
AActor::IsNetRelevantFor bOnlyRelevantToOwnerFalse时,决定Actor的相关性。
AActor::NetDormancy 决定Actor是处于休眠状态还是激活状态。

Actor复制流程概述

以下步骤构成了Actor复制过程的高级概述:

  1. 确定需要复制的Actors,并进行检查以确定它们的休眠状态、更新频率以及拥有的连接。
  2. 将通过这些检查的Actors添加到一个列表中,准备进行复制。
  3. 遍历每个连接,并根据当前的Actor和连接执行检查。完成此步骤后,得到每个连接需要复制的Actor列表。
  4. 按照优先级对每个连接的Actors进行排序。
  5. 确定该Actor是否对当前连接相关。
  6. 将该Actor复制到当前连接。

以下部分将提供上述Actor复制流程概述中每一步的详细描述。


将Actor添加到待复制列表

此步骤首先检查所有Actors,确定哪些Actors正在进行复制(通过检查是否调用了 AActor::SetReplicates(true))。对于每个正在复制的Actor,NetDriver 执行以下检查:

  • 判断当前Actor是否初始处于休眠状态(ENetDormancy::DORM_Initial)。
    • 如果是休眠状态,则跳过该Actor。
  • 检查当前Actor是否需要更新,具体通过检查 AActor::NetUpdateFrequency 属性。
    • 如果不需要更新,则跳过该Actor。
  • 如果 AActor::bOnlyRelevantToOwnertrue,则检查该Actor的拥有连接的相关性,通过调用 AActor::IsRelevancyOwnerFor 来判断。
    • 如果相关,则将其添加到该连接的“拥有者相关列表”中。
    • 在这种情况下,该Actor只会发送给单个连接。
  • 对于通过上述检查的每个Actor,调用 AActor::PreReplication。在 AActor::PreReplication 中,你可以决定是否希望某些属性仅复制到特定连接。可以使用 DOREPLIFETIME_ACTIVE_OVERRIDE 宏来控制Actor复制到哪些连接。

如果Actor通过了所有检查,将其添加到待复制列表中。


遍历每个连接

接下来,系统遍历每个连接,并为从前一步骤中获得的待复制Actor列表中的每个Actor执行以下检查和操作:

  • 判断当前Actor是否处于休眠状态,通过调用 AActor::NetDormancy
    • 如果该Actor在此连接下处于休眠状态,则跳过该Actor。
  • 如果尚未打开通道:
    • 判断客户端是否已加载当前Actor所在的关卡。
    • 如果该关卡尚未加载,则跳过该Actor。
  • 判断当前Actor是否与该连接相关,通过调用 AActor::IsNetRelevantFor
    • 如果Actor与连接不相关,则跳过该Actor。
  • 将所有在连接的“拥有者相关列表”中的Actors添加到此列表中。此时,列表中包含了所有与该连接相关且不处于休眠状态的Actors。然后按照优先级(AActor::GetNetPriority)对这些Actors进行排序,优先级从高到低排序。排序至关重要,尤其是当考虑复制大量Actor时,需要确保优先复制高优先级的Actor。

遍历排序后的Actor列表

对于该连接的每个Actor,在排序后的待复制列表中执行以下操作:

  • 如果连接尚未加载该Actor所在的关卡,则关闭通道(如果存在)并继续。
  • 每秒检查一次Actor是否与连接相关,调用 AActor::IsNetRelevantFor
    • 如果在5秒内不相关,则关闭通道。
    • 如果相关且没有通道打开,则打开通道。
  • 如果此连接在任何时刻变得饱和:
    • 对于剩余的Actors:
      • 如果相关时间少于1秒,则强制在下一tick更新。
      • 如果相关时间超过1秒,调用 AActor::IsNetRelevantFor 判断是否在下一tick更新。
  • 对于通过所有检查的Actor,通过调用 UActorChannel::ReplicateActor 将Actor复制到连接。

控制每次调用时复制的客户端数

你可以通过以下几种方式控制 UNetDriver::ServerReplicateActors 每次调用时复制的客户端数:

  • 引擎配置和命令行参数:

    • 启动项目时使用 -limitclientticks 命令行参数。
    • 修改引擎配置中的 NetClientTicksPerSecond 值(位于 [/Script/Engine.Engine] 类别)。
  • 命令行参数:

    • 启动项目时使用命令行参数:-limitclientticks -ini:Engine:[/Script/Engine.Engine]:NetClientTicksPerSecond=<VALUE>,其中 <VALUE> 是每秒希望使用的客户端ticks数。
  • 控制台变量:

    • 设置 net.MaxConnectionsToTickPerServerFrame 控制台变量。

更多信息请参考 UNetDriver::ServerReplicateActors_PrepConnections


将Actor复制到连接

UActorChannel::ReplicateActor 是复制Actor及其所有组件到连接的主要方法。其流程如下:

  • 判断这是Actor通道打开后的第一次更新。
    • 如果是第一次更新,则序列化需要的信息(初始位置、旋转等)。
  • 判断当前连接是否拥有该Actor。
    • 如果没有拥有该Actor,且该Actor的角色为 ENetRole::ROLE_AutonomousProxy,则降级为 ENetRole::ROLE_SimulatedProxy
  • 复制该Actor已更改的属性。
  • 复制每个组件已更改的属性。
  • 对于任何已删除的组件,发送特殊的删除命令。
  • 一旦Actor列表已处理完,或通道已饱和,开始考虑下一个连接,并重复该过程直到所有连接都已更新。

更多信息

有关Actor复制的更多信息,请参考以下头文件中的内容:

  • /Engine/Source/Runtime/Engine/Classes/Engine/NetDriver.h
    关于 UNetDriver::ServerReplicateActors 的信息。

  • /Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h
    关于 AActor 及其函数和属性的信息。

  • /Engine/Source/Runtime/Engine/Classes/Engine/ActorChannel.h
    关于 UActorChannelUActorChannel::ReplicateActor 的信息。

  • /Engine/Source/Runtime/Engine/Classes/Engine/EngineTypes.h
    关于 ENetRoleENetDormancy 等类型的信息。