技术博客

条件复制与 COND_* 标志位全集:只给该看的人发

第 73 / 100 篇 · 2025-12-24 · ue5 · 网络复制源码分析

复制协议那篇提过条件过滤发生在比较之前(FilterChangeList),这篇把 ELifetimeCondition 全集逐个过一遍——每个标志位的判定条件比文档写的细,用错的表现是"客户端收到的值忽有忽无"。基于 UE5.3 源码(RepLayout.cpp、ActorChannel.cpp)。

COND_* 全集与判定逻辑

条件在服务端序列化之前过滤:不满足条件的连接,这个属性的 changelist 里根本不会出现。判定发生在 FRepLayout::FilterChangeList,依据是每个连接的 FRepChangedPropertyTracker。逐条过:

标志位 判定 典型用途
COND_None 无条件,发给所有相关连接 默认值
COND_InitialOnly 只随 actor 的初始 bunch 发一次 静态配置、出生点
COND_OwnerOnly 只发给 actor 的 owner 连接 私密状态(背包、弹药精确值)
COND_SkipOwner 除 owner 外都发 表现层的旁观者视角
COND_SimulatedOnly 只发给 simulated proxy(非 autonomous) 服务器校正用的缓冲状态
COND_AutonomousOnly 只发给 autonomous proxy 技能冷却等只给操作者看
COND_ReplayOnly 只在回放(demo)时发 观战系统的独立通道
COND_ReplayOrOwner owner 或回放 私密状态 + 观战兼容
COND_Custom 走 IsCustomConditionActive 虚函数自定义 按队伍/阵营过滤
COND_SimulatedOrPhysics simulated 或物理体 物理表现的差异化
COND_OrphanedOwner owner 连接已断开但 actor 还在 5.3 新增,处理断线残留

最容易踩的是 COND_SkipOwner 和 COND_OwnerOnly 的判定主体:它们是按 UNetConnection 判的,不是按 APlayerController。"Owner"的归属链走 AActor::GetNetConnection,中间隔着 Pawn → Controller → PlayerState 的转换,如果你手动改过 owner 或用了非标准归属(比如 AI 控制但表现上归玩家),判定结果会和直觉不一致。

COND_Custom 是被低估的一个。AActor::IsCustomConditionActive 虚函数让你写任意逻辑(按队伍、按距离、按观察者模式),条件在 Filter 阶段逐属性调用——每连接每属性每 tick 一次,逻辑要轻,别在里面做射线检测。

RepNotify 的两侧语义

OnRep_Xxx 的行为在服务端和客户端完全不同,这个差异是状态机 bug 的重灾区:

  • 服务端:Set 后 OnRep 只在值变化且比较通过时触发(比较阶段在服务端也跑)。如果服务端自己也需要 OnRep 的副作用(比如扣血时同步刷新 UI),得手动调,或把逻辑从 OnRep 挪出来。
  • 客户端:值随 bunch 到达时触发,但收到和上次相同的值默认不触发(REPNOTIFY_OnChanged)。改成 REPNOTIFY_Always 后每次到达都触发,哪怕值没变。

REPNOTIFY_Always 的典型场景:一个属性是"事件计数器"(每次事件 +1),客户端要靠 OnRep 的触发次数做事。值相同不触发的默认行为会让连续两次相同值的更新漏掉一次触发。但代价是带宽——值没变也发,增量协议的"只发变化"被打破了。

条件过滤的位置:比较之后、序列化之前,按连接裁剪 changelist CompareProperties 产出全量 changelist FilterChangeList 按本连接条件裁剪 Serialize 只写留下的属性 连接 A(owner) Health ✓(COND_None) Ammo ✓(COND_OwnerOnly) CorrectionBuffer ✗(COND_SimulatedOnly) SecretDoorCode ✓(COND_OwnerOnly) 连接 B(旁观者) Health ✓ Ammo ✗ 被过滤,bunch 里没有 CorrectionBuffer ✓ SecretDoorCode ✗ 私密信息不外泄

组合使用的实战判断

一个属性该配什么条件,判断顺序是:

  1. 这个信息有没有私密性? 有 → COND_OwnerOnly 起步,观战要看得加 COND_ReplayOrOwner。
  2. 有没有"只对操作者"和"只对旁观者"的差异? 用 COND_AutonomousOnly / COND_SkipOwner 对,而不是写两个属性。
  3. 有没有按动态规则过滤的需求?(队伍、观察者模式)→ COND_Custom,但注意它的调用频率。
  4. 剩下的默认 COND_None。

验证手段:net.DebugPropertyReplications 或 LogNetTraffic,对同一 actor 的两个不同条件连接抓 bunch,对比属性出现与否;OnRep 不触发的 bug 先查条件——值可能根本没发给你这个连接。

条件过滤是每层连接独立的,所以它和 dormancy、Push Model 组合时没有额外成本:过滤在 changelist 层面做,dormant actor 根本不产生 changelist,Push Model 只登记 dirty 属性。三层各管各的,叠加使用是正交的。