UE 面试题库 · 整理版

第7章 网络与多人游戏

本章共 22 道题,覆盖:服务器/客户端架构、Replication 属性复制、RPC、权威性与网络角色、预测/校正/延迟补偿、网络优化(Relevancy / Dormancy / 优先级 / 带宽)、序列化与压缩、FFastArraySerializer 与背包系统设计、网络调试与 Bug 排查。

一、服务器/客户端架构

1.1 UE 的网络模型是什么?有哪些核心同步机制?

★★ 中等

考点: 客户端-服务器模型、网络角色、核心同步机制

参考答案:

  1. UE 采用客户端-服务器(Client-Server)模型:
    • 服务器:拥有权威游戏状态,处理游戏逻辑、碰撞与伤害判定;
    • 客户端:接收服务器同步的状态,发送玩家输入;
    • Listen Server:某个玩家的客户端同时充当服务器(Host);
    • Dedicated Server:独立的无渲染服务器进程。
  2. 网络角色(每个 Actor 都有 Role 概念):
    • ROLE_Authority:服务器拥有权威,状态以此为准;
    • ROLE_AutonomousProxy:客户端本地控制的角色代理(可做本地预测);
    • ROLE_SimulatedProxy:客户端上其他玩家的模拟代理(只做插值平滑)。
  3. 四大核心同步机制:
    • 状态同步(属性复制):自动同步 UPROPERTY(Replicated) 标记的属性;
    • RPC:在指定端(Server / Client / 所有端)执行函数;
    • 客户端预测:客户端本地立即响应输入,减少延迟感;
    • 服务器校正(回滚):预测错误时服务器发回权威状态,客户端回滚重放。
  4. 易错点:客户端不具备权威,不能直接修改由服务器控制的复制属性;角色移动类数据走不可靠通道,规则类关键数据走可靠通道。

面试点评: 能明确"谁拥有权威、客户端发输入、客户端怎么看到别人"三角关系即合格;能说清预测与校正配合可加分。


1.2 Listen Server 与 Dedicated Server 的区别是什么?各自适用于什么场景?

★★ 中等

考点: 服务器架构选型、Host 公平性、NetMode、部署方式

参考答案:

  1. 核心区别:
特性 Dedicated Server(DS) Listen Server(LS)
渲染 无(headless,纯逻辑) Host 玩家正常渲染
性能 只跑逻辑 + 网络复制 逻辑 + 渲染双重开销,受 Host 机器影响
公平性 所有玩家延迟一致 Host 玩家 0 延迟,天然优势
部署 需要独立服务器/云 任意玩家可开房间
规模 大(竞技、大型多人) 小(合作、派对游戏)
成本 需要服务器费用 无额外成本
稳定性 独立进程,Tick 稳定 Host 退出则游戏结束
  1. Host 0 延迟的公平性问题:开火判定天然有利、移动无需回滚;缓解方案是上 DS,或在匹配时标记 Host。
  2. 代码层判断:
UWorld* World = GetWorld();
if (World->GetNetMode() == NM_DedicatedServer) { /* 仅 DS,无本地玩家 */ }
if (World->GetNetMode() == NM_ListenServer)    { /* LS,Host 为本地玩家 */ }

#if WITH_SERVER_CODE   // DS 与 LS 都会编译(客户端也可能作 Host)
    // 服务器专用代码
#endif
  1. 使用建议:竞技/大型多人用 Dedicated Server;小型合作游戏、原型测试用 Listen Server。
  2. 易错点:DS 上渲染/UI 初始化应跳过,用 IsRunningDedicatedServer() 运行时兜底;WITH_SERVER_CODE 是编译期宏,不能当运行时判断用。

面试点评: 能讲清 Host 优势的公平性问题和 WITH_SERVER_CODE 用途是加分项;延伸可聊云 DS(AWS GameLift、Azure PlayFab)。


1.3 GameMode、GameState、PlayerState 在网络中的分布是怎样的?

★★ 中等

考点: Gameplay 框架网络行为、职责划分、客户端访问规范

参考答案:

  1. 存在性矩阵:
                     服务器  客户端
GameMode             存在    不存在(访问返回 nullptr)
GameState            存在    存在(属性复制)
PlayerController     存在    仅自己的
PlayerState          存在    所有玩家的
Pawn / Character     存在    存在(复制,权限不同)
  1. 职责划分:
    • GameMode:只在服务器存在,管理规则、登录/登出、胜负判断,通过 GameState 广播客户端需要的信息;
    • GameState:所有端存在,复制全局状态(比分、比赛阶段、剩余时间);
    • PlayerState:所有端存在且所有人可见(名字、分数、队伍),放玩家公开数据;
    • PlayerController:只复制给 Owner 客户端,不要用它跨客户端传引用。
  2. 代码要点:
// 客户端获取规则信息必须走 GameState,而非 GameMode
AGameStateBase* GS = GetWorld()->GetGameState();
if (GS) { /* 所有端安全 */ }

// GameMode 只在服务器有效
AGameModeBase* GM = GetWorld()->GetAuthGameMode();  // 客户端返回 nullptr

// 遍历所有玩家数据
for (APlayerState* PS : GetWorld()->GetGameState()->PlayerArray) { /* 用 PS */ }
  1. 易错点:客户端直接解引用 GetWorld()->GetAuthGameMode() 会崩溃;PlayerState 默认相关给所有客户端,PlayerController 只相关给自身。

面试点评: "客户端访问 GameMode"是最经典陷阱;能说清"规则信息走 GameState、玩家数据走 PlayerState"即合格。


1.4 网络模式(NetMode)有哪些?如何在运行时和编译期判断?

★★ 中等

考点: ENetMode 枚举、运行时判断 API、编译宏

参考答案:

  1. 枚举:NM_Standalone(单机)、NM_DedicatedServer(专用服务器)、NM_ListenServer(监听服务器)、NM_Client(纯客户端)。
  2. 运行时判断:
ENetMode NetMode = GetWorld()->GetNetMode();
switch (NetMode)
{
    case NM_Standalone:      break;
    case NM_DedicatedServer: break;
    case NM_ListenServer:    break;
    case NM_Client:          break;
}

bool bIsDS      = IsRunningDedicatedServer();  // 仅 NM_DedicatedServer
bool bHasRender = IsRunningGame();             // Standalone/Client/ListenServer
bool bIsServer  = IsRunningServer();           // DS 或 LS
  1. 编译期判断:
#if WITH_SERVER_CODE   // 服务器(含 LS 与 DS)编译的代码
#endif
#if !WITH_EDITOR       // 打包环境
#endif
  1. 易错点:区分世界级别的 ENetMode 与 Actor 级别的 ENetRole;DS 通过 IsRunningDedicatedServer() 跳过视觉初始化,而非 WITH_SERVER_CODE。

面试点评: 能区分 ENetRole 与 ENetMode、能说出编译宏与运行时判断区别,说明理解到位。


二、Replication(属性复制)

2.1 属性复制(Replication)的原理是什么?哪些属性可以复制,哪些不能?

★★ 中等

考点: 复制流程、可/不可复制类型、条件复制与 Push Model

参考答案:

  1. 复制流程:服务器按更新频率扫描标记 Replicated 的属性 → 与上一次比较变化 → 变化的属性序列化为二进制包 → 经 UNetConnection 发送 → 客户端反序列化写回本地属性;只同步变化部分,节省带宽。
  2. 三个条件缺一不可:
AMyCharacter::AMyCharacter() { bReplicates = true; }            // 1.Actor 开启复制

UPROPERTY(ReplicatedUsing = OnRep_Health)                      // 2.属性声明 Replicated
float Health;

void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
{
    Super::GetLifetimeReplicatedProps(Out);
    DOREPLIFETIME(AMyCharacter, Health);                       // 3.必须在此注册
    DOREPLIFETIME_CONDITION(AMyCharacter, Ammo, COND_OwnerOnly); // 可选条件复制
}
  1. 可复制类型:基本类型(bool / int32 / float / double / uint8 / FName / FString / FVector / FRotator)、TArray<T>(T 可复制)、UObject*(复制 NetGUID 引用而非对象)、自定义结构体(成员需可复制)。
  2. 不可复制 / 需谨慎:TMap、TSet、裸指针、Lambda/函数指针、过大的数组(超出单包限制)。
  3. 高级机制:
    • 条件复制:COND_OwnerOnly 等只发给特定端;
    • Push Model(UE5 推荐):属性变化时手动 MARK_PROPERTY_DIRTY_FROM_NAME 标记,避免每帧全量比较,明显降低服务器 CPU。
  4. 易错点:忘记在 GetLifetimeReplicatedProps 注册是"不同步"最常见原因;Replicated 与 ReplicatedUsing 不能同时用于同一属性。

面试点评: 能说出条件复制选项与 Push Model,说明带宽/性能优化意识强。


2.2 RepNotify(OnRep)回调机制是如何工作的?有哪些常见陷阱?

★★ 中等

考点: ReplicatedUsing、OnRep 触发时机、服务器与初始同步行为

参考答案:

  1. 声明方式(推荐带旧值参数):
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health;

UFUNCTION()
void OnRep_Health(float OldHealth);

void AMyCharacter::OnRep_Health(float OldHealth)
{
    float Delta = Health - OldHealth;   // 受伤或回复方向判断
    UpdateHealthBar(Health);
}
  1. 触发时机:
    • 客户端首次收到该 Actor 的复制属性时触发(此时 OldValue 是默认构造值);
    • 后续属性真正变化时触发(新值 != 旧值);
    • 若首次同步的值与默认值相同,首次可能不触发 OnRep。
  2. 常见陷阱:
    • 服务器不触发 OnRep:服务器需要相同逻辑时手动调用 OnRep,或把逻辑拆到独立函数;
    • 首次同步 OldValue 不可靠:用 bHasReceivedInitialSync 标志区分首帧初始化与后续变化;
void AMyCharacter::OnRep_Health(float OldHealth)
{
    if (!bHasReceivedInitialSync)
    {
        bHasReceivedInitialSync = true;
        UpdateHealthBar(Health);   // 首次只初始化 UI
        return;
    }
    PlayHitEffect(Health - OldHealth);
}
  • TArray 的 OnRep:客户端收到的是完整新数组,无法定位哪个元素变了 → 用 FFastArraySerializer 增量同步或自行 diff。
  1. 易错点:服务器上修改属性不会自动触发 OnRep,需手动调用并注意传入正确旧值。

面试点评: 能答出"服务器 OnRep 不执行"与"初始 OldValue 是默认值"两个坑,说明写过真实网络同步代码。


2.3 条件复制(Conditional Replication)有哪些条件?如何使用?

★★★ 困难

考点: ELifetimeCondition 枚举、COND_Custom、Push Model、带宽优化

参考答案:

  1. 常用条件:
条件 含义
COND_None 无条件(默认)
COND_InitialOnly 仅在首次复制(如角色名)
COND_OwnerOnly 只发给 Owner
COND_SkipOwner 不给 Owner
COND_SimulatedOnly 只给 SimulatedProxy
COND_AutonomousOnly 只给 AutonomousProxy
COND_SimulatedOrPhysics Simulated 或有物理模拟
COND_InitialOrOwner 首次 + Owner
COND_Custom 自定义(配合 PreReplication)
COND_ReplayOrOwner / COND_ReplayOnly / COND_SkipReplay 回放相关
COND_Dynamic / COND_Never 运行时动态 / 永不复制
  1. 使用示例:
DOREPLIFETIME(AMyCharacter, Health);
DOREPLIFETIME_CONDITION(AMyCharacter, Ammo, COND_OwnerOnly);
DOREPLIFETIME_CONDITION(AMyCharacter, TeamID, COND_InitialOnly);
DOREPLIFETIME_CONDITION(AMyCharacter, bIsCrouching, COND_SkipOwner);
  1. COND_Custom 高级用法(运行时按连接定制):
DOREPLIFETIME_CONDITION(AMyCharacter, StealthVisibility, COND_Custom);

void AMyCharacter::PreReplication(IRepChangedPropertyTracker& Tracker)
{
    Super::PreReplication(Tracker);
    // 隐身时才对其玩家复制可见度数据
    DOREPLIFETIME_ACTIVE_OVERRIDE(AMyCharacter, StealthVisibility, bIsStealthed);
}
  1. 与 Push Model 结合(UE5 参数化接口):
FDoRepLifetimeParams RepParams;
RepParams.bIsPushBased = true;
RepParams.Condition = COND_OwnerOnly;
DOREPLIFETIME_WITH_PARAMS_FAST(AMyCharacter, Health, RepParams);
// 变化处手动标记
MARK_PROPERTY_DIRTY_FROM_NAME(AMyCharacter, Health, this);
  1. 易错点:全部用 COND_None 浪费带宽;"复制给谁"(条件复制)与"距离裁剪/是否相关"(Relevancy)是两个不同的优化维度。

面试点评: 能枚举常用条件的实际场景并提到 Push Model / DOREPLIFETIME_WITH_PARAMS,说明有 UE5 实战经验。


三、RPC(远程过程调用)

3.1 Server RPC、Client RPC、NetMulticast RPC 的区别?如何选择 Reliable?

★★★ 困难

考点: RPC 分类、调用方/执行方、可靠性、WithValidation

参考答案:

  1. 三种类型总览:
类型 调用方 执行方 适用场景
Server 客户端(Owner) 服务器 客户端请求服务器执行(开火、购买)
Client 服务器 指定客户端(Owner) 服务器通知单个玩家(UI、错误提示)
NetMulticast 服务器 所有端(含服务器本地) 全局广播事件(爆炸、音效)
  1. 代码示例:
UFUNCTION(Server, Reliable, WithValidation)
void Server_Fire(FVector_NetQuantize AimLocation);

UFUNCTION(Client, Reliable)
void Client_ShowDamageNumber(float Damage, FVector Location);

UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayExplosion(FVector Location);
  1. 可靠性选择:
    • Reliable:保证到达且有序(内部有重传,类似 TCP)→ 关键逻辑(开火、死亡、购买);
    • Unreliable:可能丢失、无重传(类似 UDP)→ 高频非关键(表情、脚步声、移动)。
    • ⚠️ 每个连接 Reliable 缓冲区有限(默认约 256),高频操作使用 Reliable 会队列溢出断线,高频输入必须用 Unreliable。
  2. WithValidation:服务器校验参数合理性,返回 false 会踢出该客户端,是反作弊第一道防线:
bool AMyCharacter::Server_Fire_Validate(FVector_NetQuantize AimLocation)
{
    return AimLocation.Size() < 100000.f;  // 攻击方向合理性校验
}
  1. 易错点:Client RPC 必须由服务器调用;Server RPC 必须由 Owner 客户端调用,无 Owner 的对象调用会被静默忽略。

面试点评: 高级候选人应能解释"为什么高频不能 Reliable"以及 Reliable 缓冲区溢出后果;能提 WithValidation 防作弊更佳。


3.2 客户端能够执行 Server RPC 的对象需要满足什么条件?

★★★ 困难

考点: Ownership 链、AutonomousProxy 角色、RPC 静默失败、WithValidation

参考答案:

  1. 本质:能调用 Server RPC 的对象必须通过 Ownership 链连接到调用者的 PlayerController。
  2. 三个条件必须同时满足:
1. 客户端上该 Actor 的 Role == ROLE_AutonomousProxy(被本地控制);
2. Actor 的 Owner 链可追溯到某个 PlayerController;
3. 该 PlayerController 属于当前客户端连接(即牵扯当前玩家的 PC)。
  1. Ownership 链示例:
// 正确链:Weapon → Character → PlayerController → NetConnection
Weapon->SetOwner(MyCharacter);   // Spawn 时务必设置 Owner,否则 Server RPC 静默失败
  1. 常见失败情形:
    • 独立 Spawn、没有 Owner 的 Actor → RPC 无效(无任何报错);
    • Owner 是普通 Actor、链不追溯到 PlayerController → 无效;
    • Owner 是其他玩家的 PlayerController → 无效(不属于当前连接)。
  2. WithValidation 强化防作弊(返回 false 直接断开连接):
bool AMyCharacter::ServerFire_Validate(FVector_NetQuantize AimLocation)
{
    // 检验攻击方向与前方夹角的合理性
    float Dot = FVector::DotProduct(GetActorForwardVector(),
        (AimLocation - GetActorLocation()).GetSafeNormal());
    return Dot > 0.5f;
}
  1. 易错点:RPC 静默失败先查 Owner 链与 GetNetConnection();很多网络 Bug 来自 Owner 设置不正确且无任何报错。

面试点评: 高频考点;能记住"Owner 链须追溯到调用者自己的 PlayerController"并解释静默失败现象是得分关键。


四、权威性与网络角色

4.1 HasAuthority、Role、RemoteRole 是什么?三种网络角色如何划分?

★★ 中等

考点: ENetRole、权限判断、角色分配矩阵

参考答案:

  1. 概念:
    • HasAuthority():当前机器是否拥有该 Actor 的最终权威(服务器上全为 true);
    • Role:本机角色;RemoteRole:对端机器上这个 Actor 的角色。
  2. 角色分配矩阵:
服务器视角:所有 Actor          Role=ROLE_Authority,RemoteRole=SimulatedProxy 或 AutonomousProxy
客户端A(自己 Pawn):Role=AutonomousProxy,RemoteRole=Authority
客户端A(他人 Pawn):Role=SimulatedProxy,  RemoteRole=Authority
  1. 三种角色:
角色 含义 职责
ROLE_Authority 权威端(服务器) 执行规则、碰撞、伤害判定
ROLE_AutonomousProxy 客户端本地拥有的代理 本地预测,接受服务器校正
ROLE_SimulatedProxy 远端模拟代理 只做插值,完全由服务器驱动
  1. 常用判断代码:
if (HasAuthority()) { /* 服务器逻辑 */ }
if (GetLocalRole() == ROLE_AutonomousProxy) { /* 本地客户端预测 */ }
if (GetLocalRole() == ROLE_SimulatedProxy)  { /* 远端插值 */ }
  1. 与 IsLocallyControlled() 的区别:后者是 Pawn 视角的"本地控制"概念(ListenServer 的 Host pawn 也返回 true);更精确的组合是 Role + NetMode。
  2. 易错点:在 SimulatedProxy 上执行本应只由 Authority 做的逻辑;排查网络 Bug 先打印两端 Role。

面试点评: 能画出"服务器 / 客户端A / 客户端B"三视角的 Role/RemoteRole 矩阵,并说明三条角色各自需要不同处理(预测 vs 插值)则理解深入。


4.2 AIController 为什么只在服务端存在?

★★★ 困难

考点: 服务器权威原则、安全、一致性、客户端如何看到 AI

参考答案:

  1. 服务器权威:AI 决策(移动目标、攻击)影响游戏状态,必须由服务器决定;
  2. 安全性:客户端可被作弊者篡改(让 AI 不攻击、自行寻路),AI 在客户端运行有风险;
  3. 一致性:各客户端独立跑 AI,浮点精度与 Tick 顺序差异会导致行为不同步;
  4. 带宽优化:AI 不收玩家输入、无客户端预测需求,客户端无存在必要;
  5. 客户端看到 AI 的方式:服务器的 AIPawn 状态通过复制到客户端,以 SimulatedProxy 呈现并插值,客户端不做任何逻辑决策。
服务器:AIController(行为树/寻路)→ AIPawn 移动由 CMC 复制出
客户端:AIPawn 作为 SimulatedProxy 插值呈现,客户端无 AIController
  1. 易错点:客户端不需要 spawn AIController,如需个性化表现只用复制的状态数据驱动动画。

面试点评: 能给出"权威、安全、一致、带宽"四层理由并解释客户端如何"看到"AI 行为是完整高分。


五、预测、校正与延迟补偿

5.1 网络预测、服务器校正(Correction)与延迟补偿(Lag Compensation)分别是什么?

★★★ 困难

考点: 客户端预测、ServerMove、校正、延迟补偿回溯、插值与平滑

参考答案:

  1. 客户端预测(Client Prediction):客户端把输入发服务器后本地立即执行(不等确认),体验零延迟;CharacterMovementComponent 自动完成。
T0  按下输入 → 本地预测移动
T1  → 发送 ServerMove 数据给服务器
T2  服务器执行权威移动并回发结果
T3  客户端比对本地预测与服务器结果 → 一致则无事,不一致则校正
  1. 服务器校正(Correction / Reconciliation):服务器返回每个 Move 的权威状态;误差超过阈值(CMC 默认约 1cm)时客户端回滚到服务器位置并重放未确认的输入。相关参数:NetworkMaxSmoothUpdateDistance(超过直接 Snap)、NetworkNoSmoothUpdateDistance(超过完全不更新)、NetworkSmoothingMode(Exponential / Linear / Replay,用于平滑视觉过渡)。
  2. 延迟补偿(Lag Compensation):解决"客户端看到敌人在位置 P 开枪,但服务器收到时敌人已到 P'"的不公平问题。服务器开火时把相关 Pawn 回溯(Rewind) 到命中判定时刻再对射线:
    • 服务器需保存最近若干 Tick 的历史状态;
    • 必须限制补偿上限(如 MaxPingBeforeCorrection),防高 Ping 玩家被外挂利用。
  3. 延迟/抖动的缓解手段:客户端预测(即时响应)、插值(平滑其他玩家移动)、外推、延迟补偿、NetSmooth 平滑系数。
// 对 SimulatedProxy 插值示例
if (GetLocalRole() == ROLE_SimulatedProxy)
{
    SetActorLocation(FMath::VInterpTo(GetActorLocation(), TargetLoc, DeltaTime, InterpSpeed));
}
  1. 网络模拟参数:netPktLag=100、netPktLagVariance=20、netPktLoss=5。
  2. 易错点:伤害判定必须在服务器执行延迟补偿,而非客户端;补偿需设上限防作弊。

面试点评: 能画出时序并解释"为什么射击判定要在服务器上延迟补偿而不是客户端做"是对网络架构理解深入的表现。


5.2 CharacterMovementComponent 的网络同步原理与预测回滚如何实现?

★★★ 困难

考点: CMC 完整流程、FSavedMove、ServerMove、自定义移动预测扩展

参考答案:

  1. CMC 网络移动完整流程:
客户端(AutonomousProxy):
  1. 收集输入(加速度、跳跃、蹲伏、移动模式)
  2. PerformMovement() 本地预测
  3. 保存 FSavedMove(用于回滚重放)
  4. 发送 ServerMove RPC 给服务器

服务器(Authority):
  5. 收到 ServerMove 执行权威 PerformMovement()
  6. 对比客户端上报位置与服务器位置
  7. 误差可接受 → ClientAckGoodMove(确认)
  8. 误差过大 → ClientAdjustPosition(校正)

客户端收到响应:
  9a) Ack:清理已确认的 FSavedMove
  9b) Adjust:回滚到服务器位置 → 重放未确认输入 → 平滑视觉
  1. FSavedMove_Character 保存递增字段:起始位置/速度/旋转、输入加速度、跳跃/蹲伏旗标、DeltaTime、移动模式、时间戳。这就是客户端能"重放未确认输入"的数据基础。
  2. 服务器误差判定(简化):
float Error = FVector::Dist(ClientLoc, ServerLoc);
if (Error > FMath::Max(NetworkMaxSmoothUpdateDistance, NetworkNoSmoothUpdateDistance))
    ClientAdjustPosition(ServerLoc, ...);   // 发送校正
else
    ClientAckGoodMove(ClientTimeStamp);      // 确认移动
  1. 技能预测模式(客户端立即表现、服务器做权威验证):
void Fire() { PlayFireEffect(); ServerFire(); }   // 本地立即表现 + 请求服务器

void ServerFire_Implementation()
{
    if (!CanFire()) return;                     // 服务器验证弹药、冷却
    FHitResult Hit;
    if (PerformLineTrace(Hit)) ApplyDamage(Hit.GetActor(), Damage);
    Multicast_PlayEffect(Hit.ImpactPoint);      // 广播其他客户端表现
}
  1. 扩展 CMC 支持自定义移动:继承 FSavedMove_Character 增加自定义字段(实现 SetMoveFor / GetCompressedFlags / CanCombineWith);继承 UCharacterMovementComponent 重写 AllocateNewMove() 与 GetPredictionData_Client()。
  2. 易错点:客户端不能修改 SimulatedProxy 的 Transform;NetworkSmoothingMode 不合适会造成对方视角抖动。

面试点评: 能口述完整"预测 → 服务器权威 → 校正/回滚 → 重放"链条,并能扩展 CMC 支持自定义移动(冲刺、飞行)是强烈加分项。


六、网络优化

6.1 什么是 Relevancy(相关性)与优先级(Priority)?如何用在大世界优化?

★★★ 困难

考点: Actor 过滤、距离裁剪、优先级排序、Replication Graph

参考答案:

  1. 相关性决定该 Actor 是否需要复制给某个客户端(不相关的完全不发送),是最大带宽优化手段。服务器对每个连接按序检查:
1. bAlwaysRelevant = true → 直接相关(GameState、全局管理器)
2. Owner 链追溯到 PlayerController → 相关
3. 距离检查(NetCullDistanceSquared)→ 范围内相关
4. bNetUseOwnerRelevancy → 继承 Owner 相关性
5. 自定义 IsNetRelevantFor() → 自定义规则
  1. 相关字段:
bOnlyRelevantToOwner = true;                     // 只对 Owner 相关(PlayerState 默认行为)
NetCullDistanceSquared = FMath::Square(5000.f); // 距离裁剪
NetUpdateFrequency = 100.f;   // 最高更新频率
MinNetUpdateFrequency = 2.f;  // 最低更新频率
NetPriority = 1.f;
  1. 自定义相关性(队友强制可见):
bool AMyActor::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& Src) const
{
    if (IsTeammateOf(ViewTarget)) return true;   // 队友不受距离裁剪
    return Super::IsNetRelevantFor(RealViewer, ViewTarget, Src); // 默认距离判断
}
  1. 优先级(带宽不足时的取舍,用于通道排序):
EffectivePriority ≈ NetPriority ÷ Distance × TimeSinceLastUpdate
参考值:PlayerController / Pawn / GameState / PlayerState ≈ 3.0
       普通 Actor ≈ 1.0,纯视觉特效 ≈ 0.5
  1. Replication Graph(UE4.21+):把相关性计算从 O(N*M) 优化为空间索引(Grid)+ 常相关节点 + 休眠缓存,适合超大规模玩家。
  2. 易错点:相关性与条件复制分工不同(前者决定"是否发",后者决定"发给谁、什么条件下发")。

面试点评: 能讲清"Relevancy 过滤 Actor、Priority 决定带宽紧张时谁先发"的分层机制,加分项是提 Replication Graph。


6.2 网络休眠(Dormancy)机制是什么?如何使用?

★★★ 困难

考点: DORM 枚举、FlushNetDormancy、典型适用对象

参考答案:

  1. 背景:大量静态/低频 Actor 若每帧检查属性变化会浪费服务器 CPU;休眠后停止逐帧比较,仅在唤醒时复制一次。
  2. DORM 枚举:
DORM_Never           永不休眠
DORM_Awake          当前醒着,但可进入休眠
DORM_DormantAll     对所有连接休眠
DORM_DormantPartial  对部分连接休眠(基于 Player/Faction 高级用法)
DORM_Initial        初始休眠,首次复制完成后自动休眠
  1. 使用示例:
AMyPickup::AMyPickup() { NetDormancy = DORM_DormantAll; }

void Activate()
{
    bIsActive = true;
    FlushNetDormancy();   // 唤醒并触发复制,之后重新休眠
}

void Deactivate()
{
    bIsActive = false;
    SetNetDormancy(DORM_DormantAll);
}
  1. 典型场景:关卡中低频对象(门、箱子)→ DORM_DormantAll;需先同步初始位置再休眠 → DORM_Initial;阵营保密对象 → 按连接 DormantPartial。
  2. 易错点:FlushNetDormancy() 有性能开销,不要在 Tick 中每帧调用;休眠中的 Actor 修改复制属性前需确认已唤醒。

面试点评: 能说出 DORM_Initial 场景与 FlushNetDormancy 注意要点,说明有真实性能优化经验。


6.3 网络整体带宽优化有哪些手段?

★★★ 困难

考点: 带宽优化手段、频率控制、压缩、优先级、分析工具

参考答案:

  1. 原则:减少不必要复制、压缩数据、降低更新频率、相关性剔除。
  2. 手段汇总:
    • 最小化复制属性集:只复制必要数据;
    • 条件复制:DOREPLIFETIME_CONDITION(..., COND_OwnerOnly) 等;
    • 相关性:bOnlyRelevantToOwner、NetCullDistanceSquared 距离裁剪;
    • 降低频率:SetNetUpdateFrequency() 与 MinNetUpdateFrequency;
    • 压缩数据:FVector_NetQuantize、自定义 NetSerialize、位打包;
    • 优先级:NetPriority 保证带宽紧张时关键数据先发;
    • 休眠:静态 Actor 用 DORM_DormantAll;
    • 增量/批量:FFastArraySerializer 增量数组同步。
  3. 组合实例(100 人大逃杀):
优化1 Relevancy  → 远处的玩家/道具不复制
优化2 Dormancy  → 静态物体休眠
优化3 条件复制 → 弹药只给自己、队伍信息只给队友
优化4 自定义相关性 → 队友始终可见
→ 结果:每客户端实际接收 Actor 数从 100 降到 20-30
  1. 分析工具:stat net、stat nettraffic、stat rep、Network Profiler、Replication Graph。
  2. 易错点:MinNetUpdateFrequency 过低会造成长时间静默后爆发性更新;优化顺序从"高频大包"的 Actor(移动、属性)入手。

面试点评: 能结合大逃杀场景说明各优化手段的组合,说明有大规模多人项目经验。