UE 网络同步机制避坑:从 BeginPlay、OnRep 到 SubObject、FastArray、Reliable 与 ReplicationGraph
本文面向已经写过 UE 网络玩法、但经常被复制时序、RPC 路由、Owner、Relevancy、Dormancy、SubObject、FastArray 搞混的开发者。
适用范围以 UE4.26 / UE4.27 到 UE5.x 的传统 Generic Replication / ReplicationGraph 为主。UE5 Iris 在 SubObject、FastArray、ReplicationFragment 等细节上有差异,文中会单独标注。
UE 的网络同步最容易误解的一点是:它不是把服务端和客户端的对象自动变成完全一样。UE 真正做的是:
flowchart TB
A["服务端拥有权威状态"]
B["NetDriver 按连接筛选 Actor"]
C["ActorChannel 负责
“某个 Actor 对某个连接”的复制"]
D["属性 / RPC / Component / SubObject
被序列化成 bunch"]
E["客户端接收 bunch"]
F["创建 Actor / 解析对象引用
写入属性 / 执行 RPC / 触发 OnRep"]
A --> B --> C --> D --> E --> F
所以,玩法开发时比“这个变量怎么同步”更重要的问题是:
1 | 这个数据是谁拥有? |
只要这几个问题没有想清楚,后面无论用 Replicated、RepNotify、RPC、FastArray、SubObject 还是 ReplicationGraph,都很容易写出“本机 PIE 正常、Dedicated Server 一跑就炸”的网络 Bug。
总体心智模型:状态、事件、权限、可见性不要混在一起
UE 网络玩法里最常见的错,不是 API 拼错,而是概念边界混乱。
Authority、Owner、Relevancy、Dormancy 是四件事
| 概念 | 解决的问题 | 常见误解 |
|---|---|---|
| Authority | 谁能最终决定状态 | 以为 Owner 就是 Authority |
| Owner / Owning Connection | Client RPC、OwnerOnly 条件复制发给谁 | 以为 Actor 在服务端创建,所以 Owner 就是服务器 |
| Relevancy | 某个连接当前是否应该看到这个 Actor | 以为 bReplicates=true 就所有人一定收到 |
| Dormancy | Actor 是否暂时跳过复制检查 | 休眠后改属性却不唤醒 |
状态和事件必须分开
属性复制适合长期状态:
1 | Health |
这些数据的特点是:客户端最终需要收敛到服务端最新值,中间过程可以丢。
RPC 更适合瞬时事件或请求:
1 | PlayMuzzleFX |
这些数据的特点是:它发生一次就是一次,错过了不应该靠“属性最终值”来推断。
一个实用分层
flowchart LR
subgraph 玩法网络分层
direction TB
A["Client Request
客户端请求服务端做事
Server RPC(必须服务端校验)"]
B["Authoritative State
服务端权威状态
Replicated Property / FastArray / SubObject"]
C["Transient Event
瞬时表现
Unreliable RPC / GameplayCue / EventId"]
D["Private View
私有数据
COND_OwnerOnly / Client RPC"]
E["Public View
所有人可见的简化状态
普通属性复制 / ReplicationGraph Relevancy"]
end
如果你正在设计一个背包、技能、建筑、任务、UGC 运行时系统,优先用这个分层去拆,而不是一上来就问“这个字段加不加 Replicated”。
BeginPlay 和 OnRep 顺序为什么容易踩坑
很多人会下意识认为客户端初始化顺序是:
flowchart TB
A["BeginPlay"]
B["UI / 组件 / 引用都准备好了"]
C["之后才会执行 OnRep"]
A --> B --> C
这个理解在网络环境里非常危险。
动态复制 Actor 的初始属性通常会先于 BeginPlay 被读入
对动态 Spawn 的 Replicated Actor 来说,客户端第一次收到它时,会通过 ActorChannel 的初始 bunch 创建 Actor,并读入初始复制属性。官方 API 对 PostNetInit 的描述是:Actor 被生成并读入 replicated properties 后调用。
也就是说,对于动态复制 Actor,初始复制属性并不是一定晚于 BeginPlay。
但是,这并不意味着你可以放心在 BeginPlay 里假设一切都准备好了。因为真实项目里还有:
1 | PlayerState / Pawn / Controller 互相引用 |
所以最安全的心智模型不是“BeginPlay 一定先”或“OnRep 一定先”,而是:
1 | BeginPlay 和 OnRep 都只是若干初始化入口之一。 |
案例:OnRep_CurrentWeapon 里访问 UI 崩溃
错误写法:
1 | UPROPERTY(ReplicatedUsing = OnRep_CurrentWeapon) |
问题是 OnRep_CurrentWeapon 执行时:
1 | WeaponWidget 可能还没创建 |
更安全的写法是把每个入口都收敛到一个 TryRefresh:
1 | void AMyCharacter::OnRep_CurrentWeapon() |
核心思想:
1 | OnRep 只表示“网络数据到了” |
案例:Pawn BeginPlay 里拿不到 PlayerState
错误写法:
1 | void AMyCharacter::BeginPlay() |
客户端上可能出现:
1 | Character BeginPlay 执行了 |
更安全写法:
1 | void AMyCharacter::BeginPlay() |
结论:
1 | Pawn BeginPlay 不是 PlayerState Ready 的保证。 |
多个 OnRep 之间没有确定顺序
一个非常重要的官方规则:不同 replicated 变量的 OnRep 调用顺序没有确定性。它不保证按声明顺序、内存顺序,也不保证按服务端标脏顺序。
错误写法:
1 | UPROPERTY(ReplicatedUsing = OnRep_Weapon) |
更安全:
1 | void AMyCharacter::OnRep_Weapon() |
如果多个字段必须以严格顺序整体生效,优先考虑:
1 | 1. 把它们合并到同一个 replicated struct。 |
Component 的 ReadyForReplication
UE5 中 UActorComponent::ReadyForReplication() 是一个很重要的时机:它表示 owning actor 已经正式 ready for replication,适合在 replicated component 里注册 subobject。
例如:
1 | void UMyInventoryComponent::ReadyForReplication() |
不要把 Actor 的 BeginPlay、Component 的 BeginPlay、SubObject 的复制注册时机混成一件事。
Replicated Actor 指针为什么可能先是 null,之后又能指向正确对象
客户端收到一个 replicated Actor 指针时,对方 Actor 可能还没创建完成。如果
OnRep_Target的时候Target == nullptr,那当 Target 创建完成后,Target 指针怎么指向正确 Actor?
答案是:UE 复制的是对象引用的 NetGUID,不是内存地址。
对象引用复制的本质:FNetworkGUID
服务端复制 Actor 指针时,可以理解为发送:
1 | TargetActor -> FNetworkGUID |
客户端收到后通过 PackageMap 查询:
1 | FNetworkGUID -> 本地 UObject / AActor |
如果 Target Actor 此时还没在客户端创建:
1 | NetGUID 暂时 unmapped |
等后续 Target Actor 的创建 bunch 到达:
flowchart TB
A["客户端创建 Target Actor"]
B["NetGUID 映射到本地 Actor 实例"]
C["之前 unmapped 的引用可以被修正"]
A --> B --> C
例子:Marker 先到,Enemy 后到
服务端:
1 | AEnemy* Enemy = GetWorld()->SpawnActor<AEnemy>(EnemyClass); |
客户端可能收到:
sequenceDiagram
participant S as Server
participant C as Client
S->>C: Marker bunch(含 Marker.Target NetGUID → Enemy)
Note over C: Enemy 还没创建
NetGUID 暂时未映射
C->>C: Marker.Target == nullptr
C->>C: OnRep_Target 执行(Target 为 null)
S->>C: Enemy bunch
C->>C: Enemy 创建,NetGUID 映射成功
C->>C: Marker.Target 自动修正为 Enemy
业务层不要依赖第一次 OnRep 必然有效
稳妥写法:
1 | void AMyMarker::OnRep_Target() |
RPC 参数里的未映射引用更危险
属性引用可以被后续 remap,但 RPC 参数里的对象引用要更加谨慎。若 RPC 到达时对象还没有映射,某些情况下会以 null 参数执行;是否延迟未映射 RPC 还受 net.DelayUnmappedRPCs 等配置影响。
所以,不要把关键业务只写成:
1 | Client_PlayLockOn(TargetActor); |
更稳的方式是:
1 | 1. 同步 TargetActor 指针。 |
PlayerState 上 COND_OwnerOnly 的属性会不会同步给非 Owner
PlayerState 上一个复制属性,只设置
COND_OwnerOnly。这个 PlayerState 同步到非 owner 客户端上时,这个属性的初始值会不会同步到其他客户端?会不会触发 OnRep?
结论:
1 | 不会。 |
更准确地说:
1 | PlayerState 这个 Actor 本身会同步到所有相关客户端。 |
例子
1 | UPROPERTY(ReplicatedUsing = OnRep_PrivateInventory) |
客户端 A 是这个 PlayerState 的 Owner:
1 | 客户端 A: |
客户端 B 不是 Owner:
1 | 客户端 B: |
客户端 B 上这个字段通常只是:
1 | C++ 构造默认值 |
但不会是服务端真实值。
私有数据和公开数据要分开
推荐:
1 | // 所有人可见:等级、队伍、击杀数、当前装备外观 |
不要在非 Owner 客户端读取 OwnerOnly 字段做 UI 或玩法判断。
RPC、Client RPC、NetMulticast 与 Dedicated Server
Client RPC 只能发给拥有者
Client RPC 的目标不是“所有客户端”,而是:
1 | 拥有这个 Actor 的那个 UNetConnection |
在 Dedicated Server 下:
flowchart TB
A["Server 调用 Client RPC"]
B["UE 查找这个 Actor 的
Owning Connection"]
C["只把 RPC 发给该连接
对应的客户端"]
A --> B --> C
例子:
1 | UFUNCTION(Client, Reliable) |
服务端:
1 | PlayerAController->Client_ShowInventoryError(TEXT("背包满了")); |
结果:
1 | 客户端 A 收到 |
这正是 Client RPC 的用途:私有通知。
如果 Actor 没有 Owning Connection 呢?
如果服务端在一个没有 owning client 的世界 Actor 上调用 Client RPC:
1 | WorldTipActor->Client_ShowTip(); |
它不会神奇地发给所有客户端。Client RPC 不是广播。如果想广播,应该使用:
1 | 1. NetMulticast:瞬时表现广播。 |
NetMulticast 也不是万能广播
NetMulticast 只有服务端调用时,才会在服务端以及该 Actor 当前 relevant 的客户端执行。
错误理解:
1 | Client 调用 Multicast -> 所有人收到 |
实际:
1 | Client 调用 Multicast -> 通常只在本地执行 |
适合用 NetMulticast 的场景:
1 | 爆炸特效 |
不适合:
1 | 门是否打开 |
这些应该用 replicated state。
Dedicated Server 没有本地玩家、UI、音频、渲染
Dedicated Server 是纯服务端进程:
1 | 没有 LocalPlayer |
但它有:
1 | World |
错误写法:
1 | void AMyCharacter::Die() |
更安全:
1 | void AMyCharacter::Die() |
Reliable RPC 的真实含义:可靠但不是万能
Reliable 的含义不是:
1 | 一定立刻执行 |
更准确的理解是:
1 | 在连接和 Channel 仍然有效的前提下, |
Reliable 的 Head-of-line Blocking
假设同一个 ActorChannel 上:
1 | Reliable RPC A 丢包 |
由于可靠顺序要求,B 不能越过 A 执行。
结果:
1 | A 堵住 |
所以不要这样:
1 | void AMyCharacter::Tick(float DeltaSeconds) |
如果这些都是 Reliable,高延迟或丢包下很快就会把可靠队列堆起来。
Reliable 适合什么
适合:
1 | 背包使用请求 |
不适合:
1 | 每帧瞄准 |
建议:
1 | 重要低频请求:Reliable |
Reliable 的顺序边界
可以依赖:
1 | 同一 Actor 上 Reliable RPC 的相对顺序。 |
不要依赖:
1 | 不同 Actor 的 RPC 顺序。 |
Replicated SubObject:复制 UObject 的正确方式
普通 UObject 默认不是独立网络对象。UE 原生网络复制的主角是:
1 | AActor |
但玩法系统经常需要轻量对象:
1 | 背包对象 UInventoryObject |
如果每个都做成 Actor,成本很高。SubObject 的意义就是:
1 | 把 UObject 作为某个 Actor 或 ActorComponent 的 replicated subobject 复制。 |
复制引用和复制对象本体是两件事
这是 SubObject 最容易错的点。
1 | UPROPERTY(Replicated) |
这只是复制“指向它的引用”。它不等于复制这个 UObject 本体。
如果对象本体没有通过:
1 | AddReplicatedSubObject |
或旧路径:
1 | ReplicateSubobjects / Channel->ReplicateSubobject |
注册进入复制流程,客户端不会自动拥有它的完整复制状态。
UObject 自身需要支持网络
1 | UCLASS() |
UE5 推荐:Registered SubObject List
1 | UCLASS() |
实现:
1 | AMyPlayerState::AMyPlayerState() |
注意:删除或替换已经注册的 SubObject 前,先 RemoveReplicatedSubObject。 注册列表里保留的是原始指针,忘记移除后再 GC,可能造成悬空指针或崩溃。
UE4/兼容路径:ReplicateSubobjects
1 | bool AMyPlayerState::ReplicateSubobjects( |
UE5 Generic Replication / ReplicationGraph 仍可用这条兼容路径,但 Iris 只支持 registered subobject list。所以新项目建议优先走 registered list。
SubObject RPC 要额外处理
Replicated SubObject 默认不等于“UObject RPC 自动可用”。如果 SubObject 需要自己发 RPC,通常要重写:
1 | virtual int32 GetFunctionCallspace( |
简单项目里更推荐:SubObject 不直接发 RPC,通过 Owner Actor / Component 转发。
FastArray:列表型状态的增量同步
普通 TArray 复制的问题:
1 | UPROPERTY(ReplicatedUsing = OnRep_Items) |
如果数组有 100 个元素,只改了 1 个:
1 | 普通数组复制可能造成较大带宽浪费。 |
FastArray 的目标是:
1 | 只同步变化的元素。 |
基本结构
Item:
1 | USTRUCT() |
Container:
1 | USTRUCT() |
Traits:
1 | template<> |
Owner:
1 | UPROPERTY(Replicated) |
FastArray 的本质
FastArray 不是“魔法数组”,它的本质是:
1 | 每个连接维护一份上次发送 / 接收状态。 |
可以类比:
1 | 普通 TArray:发整张表。 |
必须 Mark Dirty
新增或修改元素:
1 | Entry.Count += 1; |
删除元素:
1 | Entries.RemoveAt(Index); |
这不是风格问题,而是 FastArray 增量复制能否正确生效的前提。
不要把数组下标当稳定身份
不要依赖:
1 | Entries[3] |
作为长期身份。更稳的是显式存:
1 | int32 SlotIndex; |
FastArray 依赖 ReplicationID 做元素身份,业务层也应该有自己的稳定 ID。
网络同步顺序不要过度假设
常见错误假设:
1 | 服务端先设置属性,再发 RPC,客户端一定先收到属性。 |
这些都不能作为强业务前提。
典型错误代码
服务端:
1 | CurrentWeapon = NewWeapon; |
客户端:
1 | void AMyCharacter::Client_PlayEquipAnimation_Implementation() |
风险:
1 | Client RPC 先执行。 |
稳定写法:事件 ID + 状态收敛
服务端:
1 | CurrentWeapon = NewWeapon; |
客户端:
1 | void AMyCharacter::OnRep_CurrentWeapon() |
核心思想:
1 | 不要依赖“网络消息到达顺序”。 |
大量 Actor 同步为什么会爆性能
如果有:
1 | 5000 个建筑 |
并且每个 Actor 都:
1 | bReplicates = true; |
服务器每帧可能要做:
1 | 每个连接遍历大量 Actor。 |
复杂度接近:
1 | Actor 数量 × 连接数量 |
爆的不是只有带宽,还有:
1 | 服务器 CPU |
生存 / UGC / 建造类游戏的对象分层
| 对象类型 | 推荐策略 |
|---|---|
| 玩家角色 | 正常复制,高优先级,移动交给 CharacterMovement |
| NPC | 距离相关,远处低频或聚合 |
| 建筑 | 大部分时间 Dormant,状态变化时唤醒 |
| 资源箱 / 采集物 | 默认休眠,只同步剩余量、是否被采集、刷新时间 |
| 掉落物 | 距离相关,数量多时聚合 |
| 纯装饰 | 不复制,客户端根据地图数据或种子本地生成 |
常用优化手段
1 | NetCullDistanceSquared:距离裁剪。 |
ReplicationGraph 原理与实现方法
传统复制大致是:
1 | 对每个连接 |
大量 Actor + 大量连接时,这会让服务器 CPU 成为瓶颈。
ReplicationGraph 的思想是:
1 | 用持久化节点提前组织 Actor。 |
ReplicationGraph 不是替代属性复制 / RPC
ReplicationGraph 主要优化的是:
1 | “哪些 Actor 需要考虑复制给这个连接” |
它不是替代:
1 | 属性复制 |
常见节点划分
1 | 全局状态 Actor |
启用 ReplicationGraph
DefaultEngine.ini:
1 | [/Script/OnlineSubsystemUtils.IpNetDriver] |
或者用代码绑定:
1 | UReplicationDriver::CreateReplicationDriverDelegate().BindLambda( |
基础结构
1 | UCLASS(Transient, Config = Engine) |
初始化节点:
1 | void UMyReplicationGraph::InitGlobalGraphNodes() |
路由 Actor:
1 | void UMyReplicationGraph::RouteAddNetworkActorToNodes( |
ReplicationGraph 和 Iris 的关系
需要特别注意:ReplicationGraph 和 Iris 是两套独立系统,不是叠加关系。 一个 NetDriver 不能同时把 ReplicationGraph 当作传统复制筛选层、又使用 Iris 作为同一路复制系统的替代实现。是否启用 Iris、是否使用 ReplicationGraph,需要按项目版本和目标平台统一规划。
Actor 网络复制调用栈:属性、RPC、OnRep 到底怎么走
服务端发送路径
简化调用栈:
1 | UWorld::Tick |
不同 UE 版本函数名和内部拆分会有差异,但主路径基本是:
1 | TickFlush -> ServerReplicateActors -> UActorChannel::ReplicateActor |
TickDispatch 和 TickFlush
可以简化理解:
1 | TickDispatch:收包,处理远端发来的 packet。 |
服务器:
1 | TickDispatch |
客户端:
1 | TickDispatch |
属性复制流程
以 Health 为例:
1 | UPROPERTY(ReplicatedUsing = OnRep_Health) |
服务端修改:
1 | Health = 50.f; |
复制时:
flowchart TB
A["Actor 被判定 relevant"]
B["ActorChannel 存在"]
C["FObjectReplicator 比较
当前对象数据和 Shadow State"]
D["发现 Health 变化"]
E["序列化 Health 到 bunch"]
F["更新该连接的
发送历史 / shadow"]
A --> B --> C --> D --> E --> F
重点:属性是否发送是按连接判断的。 同一个 Actor 的同一个属性,可能发给 A,不发给 B。这取决于:
1 | Relevancy |
客户端属性接收和 OnRep
客户端收到属性 bunch:
flowchart TB
A["UActorChannel 解析 bunch"]
B["FObjectReplicator::ReceivedBunch"]
C["FRepLayout 反序列化属性"]
D["把新值写入对象内存"]
E["记录需要触发的 RepNotify"]
F["处理 unmapped object references"]
G["调用 OnRep 函数"]
A --> B --> C --> D --> E --> F --> G
所以在 OnRep_Health() 中读到的是新值。
如果需要旧值,可以使用带参数 OnRep:
1 | UPROPERTY(ReplicatedUsing = OnRep_Health) |
具体支持和签名细节以项目 UE 版本为准。
服务端不会自动触发 OnRep
C++ 中服务端修改 RepNotify 属性,不会自动执行客户端式 OnRep。
推荐:
1 | void AMyCharacter::SetHealth(float NewHealth) |
把真正逻辑放到 HandleHealthChanged,而不是在服务端硬调 UI 逻辑。
玩法开发最容易出错清单:逐条举例
Actor 是否 bReplicates = true
错误:
1 | AChest::AChest() |
正确:
1 | AChest::AChest() |
是否由 Server Spawn
错误:
1 | // Client 本地 Spawn |
正确:
1 | Server_RequestEquipWeapon(WeaponId); |
服务端:
1 | AWeapon* Weapon = GetWorld()->SpawnActor<AWeapon>(WeaponClass); |
Server RPC 是否在 Owner Actor 上调用
错误:
1 | Chest->Server_Open(); |
如果 Chest 不属于这个客户端,Server RPC 可能被丢弃或不按预期执行。
正确:
1 | MyCharacter->Server_RequestOpenChest(Chest); |
服务端校验:
1 | if (!CanInteractWith(Chest)) |
Client RPC 是否有正确 Owner
错误:
1 | WorldTipActor->Client_ShowTip(); |
正确:
1 | PlayerController->Client_ShowTip(); |
Multicast 是否由 Server 调用
错误:
1 | // Client 调用 |
正确:
1 | Server_RequestExplode(); |
Reliable 是否被高频调用
错误:
1 | void Tick(float DeltaSeconds) |
正确:
1 | 压缩 Rotator。 |
RPC 参数是否包含不可信数据
错误:
1 | Server_AddGold(999999); |
正确:
1 | Server_RequestClaimReward(RewardId); |
服务端:
1 | if (!RewardSystem.CanClaim(PlayerState, RewardId)) |
属性是否注册到 GetLifetimeReplicatedProps
错误:
1 | UPROPERTY(Replicated) |
正确:
1 | void AMyWeapon::GetLifetimeReplicatedProps( |
OnRep 是否 null-safe
错误:
1 | void OnRep_Target() |
正确:
1 | void OnRep_Target() |
OnRep 是否依赖 UI 初始化
错误:
1 | void OnRep_Health() |
正确:
1 | void OnRep_Health() |
UI 创建后主动拉当前状态:
1 | HealthBar->SetPercent(Character->GetHealthPercent()); |
Dormant 时是否先唤醒再改属性
错误:
1 | SetNetDormancy(DORM_DormantAll); |
正确:
1 | FlushNetDormancy(); |
是否大量 Actor AlwaysRelevant
错误:
1 | bAlwaysRelevant = true; |
然后所有建筑、资源、掉落物都这样写。
正确:
1 | 全局少量对象 AlwaysRelevant。 |
推荐的网络玩法分层架构
背包示例
sequenceDiagram
participant Client
participant Server
participant Others as 其他客户端
Client->>Client: 玩家点击使用物品
Client->>Server: Server_UseItem(SlotIndex, ClientPredictedItemId)
Note over Server: 校验:Slot 是否存在 / ItemId 匹配
数量足够 / 是否在冷却 / 状态允许
alt 校验通过
Server->>Server: 修改 Inventory FastArray
Server-->>Client: OwnerOnly 同步给自己
Server-->>Others: 公开装备变化同步
Client->>Client: OnRep / FastArray 刷新成功状态
else 校验失败
Server-->>Client: Client RPC 返回失败原因
end
建筑示例
1 | 建筑 Actor: |
UGC 大世界对象示例
不要每个装饰物都复制 Actor。可以:
1 | 服务端同步 UGC Cell 数据版本。 |
调试与测试建议
常用命令
优先使用当前官方网络模拟命令:
1 | NetEmulation.PktLag 100 |
常用查看:
1 | stat net |
部分旧命令或 RepGraph 专用命令在不同 UE 版本中可能存在差异,建议在目标版本控制台自动补全或源码中确认。
推荐测试矩阵
1 | Dedicated Server + 2 Clients |
不要只在本机 0 延迟 PIE 里测。
总结
UE 网络同步最容易错的不是 API,而是边界混乱:
1 | 把客户端请求当成权威结果。 |
正确做法:
1 | Client 负责输入、预测、表现。 |
最后给一句最适合写进团队 Code Review 的话:
网络玩法不要依赖“某条消息先到”。应该让所有状态最终收敛到服务端权威结果,让所有客户端逻辑在依赖条件满足后再执行。
参考资料
Unreal Engine Documentation - Replicated Object Execution Order
https://dev.epicgames.com/documentation/unreal-engine/replicated-object-execution-order-in-unreal-engineUnreal Engine Documentation - Detailed Actor Replication Flow
https://dev.epicgames.com/documentation/unreal-engine/detailed-actor-replication-flow-in-unreal-engineUnreal Engine Documentation - Remote Procedure Calls
https://dev.epicgames.com/documentation/unreal-engine/remote-procedure-calls-in-unreal-engineUnreal Engine Documentation - Object Replication / Replicating UObjects
https://dev.epicgames.com/documentation/unreal-engine/replicating-uobjects-in-unreal-engineUnreal Engine Documentation - Replicating Object References
https://dev.epicgames.com/documentation/unreal-engine/replicating-object-references-in-unreal-engineUnreal Engine API - FFastArraySerializer
https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/NetCore/FFastArraySerializerUnreal Engine Documentation - Replication Graph
https://dev.epicgames.com/documentation/unreal-engine/replication-graph-in-unreal-engineUnreal Engine Documentation - Actor Network Dormancy
https://dev.epicgames.com/documentation/unreal-engine/actor-network-dormancy-in-unreal-engineUnreal Engine Documentation - Actor Relevancy
https://dev.epicgames.com/documentation/unreal-engine/actor-relevancy-in-unreal-engineUnreal Engine Documentation - Console Commands for Network Debugging
https://dev.epicgames.com/documentation/unreal-engine/console-commands-for-network-debugging-in-unreal-engine