页面加载中
博客快捷键
按住 Shift 键查看可用快捷键
ShiftK
开启/关闭快捷键功能
ShiftA
打开/关闭中控台
ShiftD
深色/浅色显示模式
ShiftS
站内搜索
ShiftR
随机访问
ShiftH
返回首页
ShiftL
友链页面
ShiftP
关于本站
ShiftI
原版/本站右键菜单
松开 Shift 键或点击外部区域关闭
互动
最近评论
暂无评论
标签
寻找感兴趣的领域
暂无标签
    0
    文章
    0
    标签
    8
    分类
    10
    评论
    128
    功能
    深色模式
    标签
    JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
    互动
    最近评论
    暂无评论
    标签
    寻找感兴趣的领域
    暂无标签
      0
      文章
      0
      标签
      8
      分类
      10
      评论
      128
      功能
      深色模式
      标签
      JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
      随便逛逛
      博客分类
      文章标签
      复制地址
      深色模式
      AnHeYuAnHeYu
      Search⌘K
      博客
        暂无其他文档

        Iris 网络复制系统技术分析 - 第十三部分:条件复制

        December 29, 202538 分钟 阅读81 次阅读

        🎭 Iris 网络复制系统技术分析 - 第十三部分:条件复制 (Conditionals) - 智能数据分发系统

        📖 本文档详细讲解 Iris 复制系统中的条件复制机制,教你如何精确控制"什么数据发给谁"。


        📋 目录

        • 13.1 条件复制概述

        • 13.2 ELifetimeCondition 条件类型详解

        • 13.3 ReplicationConditionals 核心实现

        • 13.4 条件评估流程

        • 13.5 实际应用案例

        • 13.6 高级主题:动态条件与自定义条件

        • 13.7 性能优化与最佳实践

        • 13.8 总结


        13.1 条件复制概述

        💡 什么是条件复制?

        想象你是一个情报局长,手下有很多特工(客户端),你需要分发情报(数据)给他们:

        PLAINTEXT
        🏢 情报局(服务器)
            │
            ├── 📋 机密文件A(仅给局长看)     → 只发给 Owner
            ├── 📋 普通情报B(所有特工都能看) → 发给所有人
            ├── 📋 行动指令C(只给执行者看)   → 只发给 Autonomous
            └── 📋 观察报告D(只给旁观者看)   → 只发给 Simulated

        条件复制就是这个"情报分发规则"——决定哪些数据应该发送给哪些客户端。

        🎯 为什么需要条件复制?

        场景

        问题

        解决方案

        🎮 玩家血量

        敌人不应该看到你的精确血量

        COND_OwnerOnly

        🔫 武器弹药

        只有持有者需要知道弹药数

        COND_OwnerOnly

        🏃 移动预测

        本地玩家不需要服务器的位置数据

        COND_SkipOwner

        🎬 初始状态

        角色名字只需发送一次

        COND_InitialOnly

        🎥 回放系统

        某些数据只在回放时需要

        COND_ReplayOnly

        🏗️ 条件复制的核心组件

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────┐
        │                    条件复制系统架构                              │
        ├─────────────────────────────────────────────────────────────────┤
        │                                                                 │
        │  ┌─────────────────────┐    ┌─────────────────────┐            │
        │  │  ELifetimeCondition │    │  EReplicationCondition│           │
        │  │  (属性级条件)        │    │  (对象级条件)         │           │
        │  │  • COND_OwnerOnly   │    │  • RoleAutonomous    │           │
        │  │  • COND_SkipOwner   │    │  • ReplicatePhysics  │           │
        │  │  • COND_InitialOnly │    └─────────────────────┘            │
        │  │  • ...              │                                       │
        │  └─────────────────────┘                                       │
        │            │                           │                        │
        │            ▼                           ▼                        │
        │  ┌─────────────────────────────────────────────────────┐       │
        │  │           FReplicationConditionals                   │       │
        │  │  • GetLifetimeConditionals()  - 计算条件掩码         │       │
        │  │  • ApplyConditionalsToChangeMask() - 应用条件        │       │
        │  │  • SetConditionConnectionFilter() - 设置连接过滤     │       │
        │  └─────────────────────────────────────────────────────┘       │
        │                           │                                     │
        │                           ▼                                     │
        │  ┌─────────────────────────────────────────────────────┐       │
        │  │              FConditionalsMask (16位掩码)            │       │
        │  │  每个条件对应一个位:bit0=COND_None, bit2=COND_OwnerOnly...│
        │  └─────────────────────────────────────────────────────┘       │
        │                                                                 │
        └─────────────────────────────────────────────────────────────────┘

        📁 关键源文件索引

        文件

        位置

        说明

        ReplicationCondition.h

        Core/Public/Iris/ReplicationSystem/Conditionals/

        对象级条件枚举

        ReplicationConditionals.h

        Core/Private/Iris/ReplicationSystem/Conditionals/

        条件系统核心类

        ReplicationConditionals.cpp

        Core/Private/Iris/ReplicationSystem/Conditionals/

        条件系统实现

        CoreNetTypes.h

        CoreUObject/Public/UObject/

        ELifetimeCondition 枚举定义

        UnrealNetwork.h

        Engine/Public/Net/

        DOREPLIFETIME 宏定义


        13.2 ELifetimeCondition 条件类型详解

        📊 完整条件类型一览

        ELifetimeCondition 是 UE 网络复制的核心枚举,定义在 CoreNetTypes.h 中:

        CPP
        // Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNetTypes.hUENUM(BlueprintType)
        enum ELifetimeCondition : int
        {
            COND_None = 0,                    // 无条件,始终复制
            COND_InitialOnly = 1,             // 仅初始状态复制
            COND_OwnerOnly = 2,               // 仅复制给拥有者
            COND_SkipOwner = 3,               // 跳过拥有者
            COND_SimulatedOnly = 4,           // 仅模拟代理
            COND_AutonomousOnly = 5,          // 仅自主代理
            COND_SimulatedOrPhysics = 6,      // 模拟代理或物理复制
            COND_InitialOrOwner = 7,          // 初始状态或拥有者
            COND_Custom = 8,                  // 自定义条件
            COND_ReplayOrOwner = 9,           // 回放或拥有者
            COND_ReplayOnly = 10,             // 仅回放
            COND_SimulatedOnlyNoReplay = 11,  // 模拟代理(非回放)
            COND_SimulatedOrPhysicsNoReplay = 12, // 模拟或物理(非回放)
            COND_SkipReplay = 13,             // 跳过回放
            COND_Dynamic = 14,                // 动态条件(运行时决定)
            COND_Never = 15,                  // 永不复制
            COND_NetGroup = 16,               // 网络组条件(仅用于子对象)
            COND_Max = 17
        };

        🎮 日常生活类比

        让我们用一个公司内部通讯系统来理解这些条件:

        PLAINTEXT
        🏢 公司(服务器)发送消息给员工(客户端)
        
        ┌────────────────────┬────────────────────────────────────────────┐
        │      条件类型       │              日常类比                       │
        ├────────────────────┼────────────────────────────────────────────┤
        │ COND_None          │ 📢 全员通知 - 所有人都能看到                 │
        │ COND_InitialOnly   │ 📋 入职手册 - 只在入职时发一次               │
        │ COND_OwnerOnly     │ 💰 工资单 - 只有本人能看                     │
        │ COND_SkipOwner     │ 🎂 生日惊喜 - 除了寿星其他人都知道           │
        │ COND_SimulatedOnly │ 👀 旁观者报告 - 只给观察员看                 │
        │ COND_AutonomousOnly│ 🎮 操作指南 - 只给实际操作者看               │
        │ COND_Custom        │ 🔐 VIP专属 - 根据权限动态决定                │
        │ COND_ReplayOnly    │ 📹 监控录像 - 只在回放时显示                 │
        │ COND_Never         │ 🚫 内部机密 - 永远不外传                     │
        └────────────────────┴────────────────────────────────────────────┘

        📝 详细条件说明

        1️⃣ COND_None - 无条件复制

        CPP
        // 最常用的条件 - 数据变化就复制UPROPERTY(Replicated)
        float Health;  // 默认就是 COND_None
        
        // 显式指定void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            DOREPLIFETIME(AMyActor, Health);  // 等同于 COND_None
        }

        2️⃣ COND_InitialOnly - 仅初始复制

        CPP
        // 🎮 适用场景:角色名字、皮肤ID等不会改变的数据UPROPERTY(Replicated)
        FString PlayerName;
        
        UPROPERTY(Replicated)
        int32 SkinId;
        
        void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 只在角色首次复制时发送,之后不再更新
            DOREPLIFETIME_CONDITION(AMyCharacter, PlayerName, COND_InitialOnly);
            DOREPLIFETIME_CONDITION(AMyCharacter, SkinId, COND_InitialOnly);
        }

        流程图:

        PLAINTEXT
        服务器创建角色
               │
               ▼┌──────────────────┐│ 首次复制到客户端  ││ PlayerName: "张三"│  ✅ 发送│ SkinId: 5        │  ✅ 发送└──────────────────┘
               │
               ▼ (之后的更新)┌──────────────────┐│ PlayerName 改变? │  ❌ 不发送(InitialOnly)│ SkinId 改变?     │  ❌ 不发送(InitialOnly)└──────────────────┘

        3️⃣ COND_OwnerOnly - 仅拥有者

        CPP
        // 🔫 适用场景:弹药数、技能冷却等敏感数据UPROPERTY(Replicated)
        int32 CurrentAmmo;
        
        UPROPERTY(Replicated)
        float AbilityCooldown;
        
        void AMyWeapon::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 只有武器的拥有者能看到弹药数
            DOREPLIFETIME_CONDITION(AMyWeapon, CurrentAmmo, COND_OwnerOnly);
            DOREPLIFETIME_CONDITION(AMyWeapon, AbilityCooldown, COND_OwnerOnly);
        }

        数据流向图:

        PLAINTEXT
                服务器
                  │
            CurrentAmmo = 30
                  │
            ┌─────┴─────┐
            ▼           ▼
         玩家A        玩家B
        (Owner)      (非Owner)
            │           │
            ▼           ▼
        收到: 30     收不到❌

        4️⃣ COND_SkipOwner - 跳过拥有者

        CPP
        // 🏃 适用场景:服务器位置修正(本地玩家有预测,不需要)UPROPERTY(Replicated)
        FVector ServerPosition;
        
        UPROPERTY(Replicated)
        FRotator ServerRotation;
        
        void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 本地玩家用预测位置,不需要服务器位置
            // 其他玩家需要服务器位置来显示
            DOREPLIFETIME_CONDITION(AMyCharacter, ServerPosition, COND_SkipOwner);
            DOREPLIFETIME_CONDITION(AMyCharacter, ServerRotation, COND_SkipOwner);
        }

        为什么要跳过 Owner?

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────┐
        │                    移动预测系统                          │
        ├─────────────────────────────────────────────────────────┤
        │                                                         │
        │  本地玩家(Owner):                                     │
        │  ┌─────────────────────────────────────┐               │
        │  │ 输入 → 本地预测 → 立即响应           │               │
        │  │ 不需要等服务器位置,体验更流畅        │               │
        │  └─────────────────────────────────────┘               │
        │                                                         │
        │  其他玩家(非Owner):                                   │
        │  ┌─────────────────────────────────────┐               │
        │  │ 需要服务器位置来显示其他玩家          │               │
        │  │ 没有预测,必须依赖复制数据            │               │
        │  └─────────────────────────────────────┘               │
        │                                                         │
        └─────────────────────────────────────────────────────────┘

        5️⃣ COND_SimulatedOnly vs COND_AutonomousOnly

        CPP
        // 理解 Simulated 和 Autonomous 的区别
        
        // Autonomous(自主代理):玩家直接控制的角色// Simulated(模拟代理):其他玩家看到的你的角色
        
        UPROPERTY(Replicated)
        FVector InputVector;  // 输入向量 - 只有自主代理需要
        
        UPROPERTY(Replicated)
        FVector SmoothedPosition;  // 平滑位置 - 只有模拟代理需要
        
        void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 输入向量只发给控制这个角色的客户端
            DOREPLIFETIME_CONDITION(AMyCharacter, InputVector, COND_AutonomousOnly);
            
            // 平滑位置只发给其他客户端(用于平滑显示)
            DOREPLIFETIME_CONDITION(AMyCharacter, SmoothedPosition, COND_SimulatedOnly);
        }

        角色类型对照表:

        PLAINTEXT
        ┌──────────────────────────────────────────────────────────────┐
        │                     角色类型对照                              │
        ├────────────┬─────────────────────┬───────────────────────────┤
        │   视角     │    Autonomous       │       Simulated           │
        ├────────────┼─────────────────────┼───────────────────────────┤
        │ 玩家A看    │ 玩家A自己的角色      │ 玩家B、C、D的角色          │
        │ 玩家A的角色│                     │                           │
        ├────────────┼─────────────────────┼───────────────────────────┤
        │ 玩家B看    │ 玩家B自己的角色      │ 玩家A、C、D的角色          │
        │ 玩家B的角色│                     │                           │
        └────────────┴─────────────────────┴───────────────────────────┘

        6️⃣ COND_SimulatedOrPhysics - 模拟或物理

        CPP
        // 🚗 适用场景:物理模拟的对象(如载具)UPROPERTY(Replicated)
        FVector PhysicsVelocity;
        
        void AMyVehicle::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 当 bReplicatePhysics 为 true 时,即使是 Owner 也会收到
            DOREPLIFETIME_CONDITION(AMyVehicle, PhysicsVelocity, COND_SimulatedOrPhysics);
        }

        7️⃣ COND_Custom - 自定义条件

        CPP
        // 🔐 适用场景:需要在运行时动态控制的属性UPROPERTY(Replicated)
        float SecretData;
        
        void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 使用自定义条件
            DOREPLIFETIME_CONDITION(AMyActor, SecretData, COND_Custom);
        }
        
        // 在运行时控制是否复制void AMyActor::SetSecretDataActive(bool bActive){
            // 使用宏来设置自定义条件的激活状态
            DOREPLIFETIME_ACTIVE_OVERRIDE(AMyActor, SecretData, bActive);
        }

        8️⃣ COND_Dynamic - 动态条件

        CPP
        // 🎭 适用场景:条件在运行时可能改变UPROPERTY(Replicated)
        float DynamicData;
        
        void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 声明为动态条件
            DOREPLIFETIME_CONDITION(AMyActor, DynamicData, COND_Dynamic);
        }
        
        // 在运行时改变条件void AMyActor::ChangeDynamicCondition(ELifetimeCondition NewCondition){
            // 通过 PropertyConditionManager 设置新条件
            DOREPLIFETIME_CHANGE_CONDITION(AMyActor, DynamicData, NewCondition);
        }

        📊 条件组合速查表

        条件

        Owner 收到

        非Owner 收到

        初始状态

        后续更新

        回放

        COND_None

        ✅

        ✅

        ✅

        ✅

        ✅

        COND_InitialOnly

        ✅

        ✅

        ✅

        ❌

        ✅

        COND_OwnerOnly

        ✅

        ❌

        ✅

        ✅

        ❌

        COND_SkipOwner

        ❌

        ✅

        ✅

        ✅

        ✅

        COND_SimulatedOnly

        ❌*

        ✅

        ✅

        ✅

        ✅

        COND_AutonomousOnly

        ✅*

        ❌

        ✅

        ✅

        ❌

        COND_InitialOrOwner

        ✅

        ✅初始/❌后续

        ✅

        Owner✅

        ❌

        COND_ReplayOnly

        ❌

        ❌

        ❌

        ❌

        ✅

        COND_SkipReplay

        ✅

        ✅

        ✅

        ✅

        ❌

        COND_Never

        ❌

        ❌

        ❌

        ❌

        ❌

        *注:Simulated/Autonomous 取决于角色的 Role,不是 Owner 关系


        13.3 ReplicationConditionals 核心实现

        🏗️ FReplicationConditionals 类结构

        CPP
        // Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/Conditionals/ReplicationConditionals.h
        
        class FReplicationConditionals
        {
        public:
            // 初始化
            void Init(FReplicationConditionalsInitParams& Params);
            
            // 连接管理
            void AddConnection(uint32 ConnectionId);
            void RemoveConnection(uint32 ConnectionId);
            
            // 条件设置 API
            bool SetConditionConnectionFilter(FInternalNetRefIndex ObjectIndex, 
                                              EReplicationCondition Condition, 
                                              uint32 ConnectionId, bool bEnable);
            bool SetCondition(FInternalNetRefIndex ObjectIndex, 
                              EReplicationCondition Condition, bool bEnable);
            void SetOwningConnection(FInternalNetRefIndex ObjectIndex, uint32 ConnectionId);
            
            // 属性条件
            void InitPropertyCustomConditions(FInternalNetRefIndex ObjectIndex);
            bool SetPropertyCustomCondition(FInternalNetRefIndex ObjectIndex, 
                                            const void* Owner, uint16 RepIndex, bool bIsActive);
            bool SetPropertyDynamicCondition(FInternalNetRefIndex ObjectIndex, 
                                             const void* Owner, uint16 RepIndex, 
                                             ELifetimeCondition Condition);
            
            // 核心方法:应用条件到变化掩码
            bool ApplyConditionalsToChangeMask(uint32 ReplicatingConnectionId, 
                                               bool bIsInitialState,
                                               FInternalNetRefIndex ParentObjectIndex, 
                                               FInternalNetRefIndex ObjectIndex,
                                               uint32* ChangeMaskData, 
                                               const uint32* ConditionalChangeMaskData,
                                               const FReplicationProtocol* Protocol);
            
        private:
            // 每对象信息
            struct FPerObjectInfo
            {
                uint16 AutonomousConnectionId : 15;  // 自主代理的连接ID
                uint16 bRepPhysics : 1;              // 是否复制物理
            };
            
            // 每连接信息
            struct FPerConnectionInfo
            {
                TArray<FConditionalsMask> ObjectConditionals;  // 每个对象的条件掩码
            };
            
            // 条件掩码结构
            struct FConditionalsMask
            {
                uint16 ConditionalsMask;  // 16位,每位对应一个条件
                
                bool IsConditionEnabled(int Condition) const 
                { 
                    return ConditionalsMask & (uint16(1) << unsigned(Condition)); 
                }
            };
            
            // 获取生命周期条件
            FConditionalsMask GetLifetimeConditionals(uint32 ReplicatingConnectionId, 
                                                      FInternalNetRefIndex ParentObjectIndex, 
                                                      bool bIsInitialState) const;
        };

        🔍 GetLifetimeConditionals 实现详解

        这是条件复制的核心方法,决定了哪些条件对特定连接是"启用"的:

        CPP
        // ReplicationConditionals.cppFReplicationConditionals::FConditionalsMask 
        FReplicationConditionals::GetLifetimeConditionals(
            uint32 ReplicatingConnectionId, 
            FInternalNetRefIndex ParentObjectIndex, 
            bool bIsInitialState) const{
            FConditionalsMask ConditionalsMask{0};
            
            // 1️⃣ 获取拥有者连接
            const uint32 ObjectOwnerConnectionId = ReplicationFiltering->GetOwningConnection(ParentObjectIndex);
            const bool bIsReplicatingToOwner = (ReplicatingConnectionId == ObjectOwnerConnectionId);
            
            // 2️⃣ 获取对象信息
            const FPerObjectInfo* ObjectInfo = GetPerObjectInfo(ParentObjectIndex);
            const bool bRoleSimulated = ReplicatingConnectionId != ObjectInfo->AutonomousConnectionId;
            const bool bRoleAutonomous = ReplicatingConnectionId == ObjectInfo->AutonomousConnectionId;
            const bool bRepPhysics = ObjectInfo->bRepPhysics;
            
            // 3️⃣ 设置各条件的启用状态
            ConditionalsMask.SetConditionEnabled(COND_None, true);           // 始终启用
            ConditionalsMask.SetConditionEnabled(COND_Custom, true);         // 自定义始终启用
            ConditionalsMask.SetConditionEnabled(COND_Dynamic, true);        // 动态始终启用
            
            // 基于 Owner 的条件
            ConditionalsMask.SetConditionEnabled(COND_OwnerOnly, bIsReplicatingToOwner);
            ConditionalsMask.SetConditionEnabled(COND_SkipOwner, !bIsReplicatingToOwner);
            
            // 基于 Role 的条件
            ConditionalsMask.SetConditionEnabled(COND_SimulatedOnly, bRoleSimulated);
            ConditionalsMask.SetConditionEnabled(COND_AutonomousOnly, bRoleAutonomous);
            ConditionalsMask.SetConditionEnabled(COND_SimulatedOrPhysics, bRoleSimulated | bRepPhysics);
            
            // 基于初始状态的条件
            ConditionalsMask.SetConditionEnabled(COND_InitialOnly, bIsInitialState);
            ConditionalsMask.SetConditionEnabled(COND_InitialOrOwner, bIsReplicatingToOwner | bIsInitialState);
            
            // 回放相关条件(非回放连接时的设置)
            ConditionalsMask.SetConditionEnabled(COND_ReplayOrOwner, bIsReplicatingToOwner);
            ConditionalsMask.SetConditionEnabled(COND_SimulatedOnlyNoReplay, bRoleSimulated);
            ConditionalsMask.SetConditionEnabled(COND_SimulatedOrPhysicsNoReplay, bRoleSimulated | bRepPhysics);
            ConditionalsMask.SetConditionEnabled(COND_SkipReplay, true);
            
            return ConditionalsMask;
        }

        📊 条件掩码可视化

        PLAINTEXT
        FConditionalsMask (16位)
        ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
        │F│E│D│C│B│A│9│8│7│6│5│4│3│2│1│0│
        └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
         │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
         │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ COND_None (0)
         │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─── COND_InitialOnly (1)
         │ │ │ │ │ │ │ │ │ │ │ │ │ └───── COND_OwnerOnly (2)
         │ │ │ │ │ │ │ │ │ │ │ │ └─────── COND_SkipOwner (3)
         │ │ │ │ │ │ │ │ │ │ │ └───────── COND_SimulatedOnly (4)
         │ │ │ │ │ │ │ │ │ │ └─────────── COND_AutonomousOnly (5)
         │ │ │ │ │ │ │ │ │ └───────────── COND_SimulatedOrPhysics (6)
         │ │ │ │ │ │ │ │ └─────────────── COND_InitialOrOwner (7)
         │ │ │ │ │ │ │ └───────────────── COND_Custom (8)
         │ │ │ │ │ │ └─────────────────── COND_ReplayOrOwner (9)
         │ │ │ │ │ └───────────────────── COND_ReplayOnly (10)
         │ │ │ │ └─────────────────────── COND_SimulatedOnlyNoReplay (11)
         │ │ │ └───────────────────────── COND_SimulatedOrPhysicsNoReplay (12)
         │ │ └─────────────────────────── COND_SkipReplay (13)
         │ └───────────────────────────── COND_Dynamic (14)
         └─────────────────────────────── COND_Never (15)
        
        示例:复制给 Owner 的初始状态
        ConditionalsMask = 0b0010_0001_1000_0111 = 0x2187
                              │    │   │    │││
                              │    │   │    ││└─ COND_None ✅
                              │    │   │    │└── COND_InitialOnly ✅
                              │    │   │    └─── COND_OwnerOnly ✅
                              │    │   └──────── COND_Custom ✅
                              │    └──────────── COND_Dynamic ✅
                              └───────────────── COND_SkipReplay ✅

        13.4 条件评估流程

        🔄 完整评估流程图

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────────────┐
        │                        条件评估流程                                      │
        ├─────────────────────────────────────────────────────────────────────────┤
        │                                                                         │
        │  1️⃣ 复制开始                                                            │
        │     │                                                                   │
        │     ▼                                                                   │
        │  ┌──────────────────────────────────────┐                              │
        │  │ GetLifetimeConditionals()            │                              │
        │  │ 计算当前连接的条件掩码                 │                              │
        │  │ • 检查 Owner 关系                     │                              │
        │  │ • 检查 Role (Autonomous/Simulated)   │                              │
        │  │ • 检查是否初始状态                    │                              │
        │  │ • 检查物理复制标志                    │                              │
        │  └──────────────────────────────────────┘                              │
        │     │                                                                   │
        │     ▼                                                                   │
        │  2️⃣ 遍历每个属性                                                        │
        │     │                                                                   │
        │     ▼                                                                   │
        │  ┌──────────────────────────────────────┐                              │
        │  │ 获取属性的 ELifetimeCondition        │                              │
        │  │ 从 MemberLifetimeConditionDescriptors │                              │
        │  └──────────────────────────────────────┘                              │
        │     │                                                                   │
        │     ▼                                                                   │
        │  ┌──────────────────────────────────────┐                              │
        │  │ 条件是否在掩码中启用?                │                              │
        │  │ IsConditionEnabled(Condition)        │                              │
        │  └──────────────────────────────────────┘                              │
        │     │                                                                   │
        │     ├─── 是 ──▶ 保留 ChangeMask 位(允许复制)                          │
        │     │                                                                   │
        │     └─── 否 ──▶ 清除 ChangeMask 位(阻止复制)                          │
        │                                                                         │
        │  3️⃣ 应用自定义条件掩码                                                  │
        │     │                                                                   │
        │     ▼                                                                   │
        │  ┌──────────────────────────────────────┐                              │
        │  │ ChangeMask &= ConditionalChangeMask  │                              │
        │  │ 进一步过滤自定义条件                  │                              │
        │  └──────────────────────────────────────┘                              │
        │     │                                                                   │
        │     ▼                                                                   │
        │  4️⃣ 返回最终的 ChangeMask                                               │
        │     只有位为 1 的属性才会被序列化发送                                    │
        │                                                                         │
        └─────────────────────────────────────────────────────────────────────────┘

        📝 ApplyConditionalsToChangeMask 核心逻辑

        CPP
        // ReplicationConditionals.cppbool FReplicationConditionals::ApplyConditionalsToChangeMask(
            uint32 ReplicatingConnectionId, 
            bool bIsInitialState,
            FInternalNetRefIndex ParentObjectIndex, 
            FInternalNetRefIndex ObjectIndex,
            uint32* ChangeMaskData, 
            const uint32* ConditionalChangeMaskData,
            const FReplicationProtocol* Protocol){
            bool bMaskWasModified = false;
            FNetBitArrayView ChangeMask = MakeNetBitArrayView(ChangeMaskData, Protocol->ChangeMaskBitCount);
            
            // 检查协议是否有生命周期条件
            if (EnumHasAnyFlags(Protocol->ProtocolTraits, EReplicationProtocolTraits::HasLifetimeConditionals))
            {
                // 获取当前连接的条件掩码
                const FConditionalsMask LifetimeConditionals = 
                    GetLifetimeConditionals(ReplicatingConnectionId, ParentObjectIndex, bIsInitialState);
                
                // 获取上一次的条件掩码(用于检测条件变化)
                FConditionalsMask PrevLifeTimeConditions = 
                    ConnectionInfos[ReplicatingConnectionId].ObjectConditionals[ObjectIndex];
                
                // 遍历每个成员
                for (uint32 MemberIt = 0; MemberIt < StateDescriptor->MemberCount; ++MemberIt)
                {
                    // 获取成员的条件
                    ELifetimeCondition Condition = 
                        static_cast<ELifetimeCondition>(LifetimeConditionDescriptors[MemberIt].Condition);
                    
                    // 处理动态条件
                    if (Condition == COND_Dynamic)
                    {
                        Condition = GetDynamicCondition(ObjectIndex, Property->RepIndex);
                    }
                    
                    // 检查条件是否启用
                    if (LifetimeConditionals.IsConditionEnabled(Condition))
                    {
                        // 条件启用:如果之前被禁用,现在需要标记为脏
                        if (!PrevLifeTimeConditions.IsConditionEnabled(Condition))
                        {
                            // 条件从禁用变为启用,标记属性为脏
                            ChangeMask.SetBits(ChangeMaskDescriptor.BitOffset, ChangeMaskDescriptor.BitCount);
                            bMaskWasModified = true;
                        }
                    }
                    else
                    {
                        // 条件禁用:清除 ChangeMask 中对应的位
                        if (ChangeMask.IsAnyBitSet(ChangeMaskDescriptor.BitOffset, ChangeMaskDescriptor.BitCount))
                        {
                            ChangeMask.ClearBits(ChangeMaskDescriptor.BitOffset, ChangeMaskDescriptor.BitCount);
                            bMaskWasModified = true;
                        }
                    }
                }
                
                // 保存当前条件掩码供下次比较
                ConnectionInfos[ReplicatingConnectionId].ObjectConditionals[ObjectIndex] = LifetimeConditionals;
            }
            
            // 应用自定义条件掩码
            if (ConditionalChangeMaskData != nullptr)
            {
                // 按字进行 AND 操作
                for (uint32 WordIt = 0; WordIt < ChangeMask.GetNumWords(); ++WordIt)
                {
                    const uint32 OldMask = ChangeMaskData[WordIt];
                    const uint32 ConditionalMask = ConditionalChangeMaskData[WordIt];
                    ChangeMaskData[WordIt] = OldMask & ConditionalMask;
                }
            }
            
            return bMaskWasModified;
        }

        🎯 条件变化检测

        当条件状态改变时(如 Owner 改变),系统会自动处理:

        CPP
        // 设置拥有连接时的处理void FReplicationConditionals::SetOwningConnection(FInternalNetRefIndex ObjectIndex, uint32 OwningConnectionId){
            const uint32 OldOwningConnectionId = ReplicationFiltering->GetOwningConnection(ObjectIndex);
            
            if (OldOwningConnectionId != OwningConnectionId)
            {
                // 标记对象的生命周期条件为脏
                ObjectsWithDirtyLifetimeConditionals.SetBit(ObjectIndex);
                
                // 使受影响连接的基线失效
                InvalidateBaselinesForObjectHierarchy(ObjectIndex, ConnectionsToInvalidate);
            }
        }

        13.5 实际应用案例

        🎮 案例1:FPS 游戏角色

        CPP
        // MyFPSCharacter.hUCLASS()
        class AMyFPSCharacter : public ACharacter
        {
            GENERATED_BODY()
            
        public:
            // 🩸 血量 - 所有人都能看到(用于显示血条)
            UPROPERTY(ReplicatedUsing=OnRep_Health)
            float Health;
            
            // 🛡️ 护甲 - 只有自己能看到
            UPROPERTY(Replicated)
            float Armor;
            
            // 🔫 当前弹药 - 只有自己能看到
            UPROPERTY(Replicated)
            int32 CurrentAmmo;
            
            // 📦 总弹药 - 只有自己能看到
            UPROPERTY(Replicated)
            int32 TotalAmmo;
            
            // 🏃 服务器位置 - 其他人看到的位置
            UPROPERTY(Replicated)
            FVector ServerLocation;
            
            // 👤 玩家名字 - 只发送一次
            UPROPERTY(Replicated)
            FString PlayerName;
            
            // 🎨 皮肤ID - 只发送一次
            UPROPERTY(Replicated)
            int32 SkinId;
            
            // 💀 击杀数 - 所有人都能看到(计分板)
            UPROPERTY(Replicated)
            int32 KillCount;
            
            // 🔒 私密状态 - 可动态控制
            UPROPERTY(Replicated)
            bool bIsInvisible;  // 隐身状态
            
            virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
        };
        
        // MyFPSCharacter.cppvoid AMyFPSCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 📊 无条件复制 - 所有人都需要
            DOREPLIFETIME(AMyFPSCharacter, Health);
            DOREPLIFETIME(AMyFPSCharacter, KillCount);
            
            // 🔒 仅拥有者 - 敏感数据
            DOREPLIFETIME_CONDITION(AMyFPSCharacter, Armor, COND_OwnerOnly);
            DOREPLIFETIME_CONDITION(AMyFPSCharacter, CurrentAmmo, COND_OwnerOnly);
            DOREPLIFETIME_CONDITION(AMyFPSCharacter, TotalAmmo, COND_OwnerOnly);
            
            // ⏭️ 跳过拥有者 - 本地预测
            DOREPLIFETIME_CONDITION(AMyFPSCharacter, ServerLocation, COND_SkipOwner);
            
            // 📋 仅初始 - 不变数据
            DOREPLIFETIME_CONDITION(AMyFPSCharacter, PlayerName, COND_InitialOnly);
            DOREPLIFETIME_CONDITION(AMyFPSCharacter, SkinId, COND_InitialOnly);
            
            // 🎭 自定义条件 - 运行时控制
            DOREPLIFETIME_CONDITION(AMyFPSCharacter, bIsInvisible, COND_Custom);
        }
        
        // 动态控制隐身状态的复制void AMyFPSCharacter::SetInvisible(bool bNewInvisible){
            bIsInvisible = bNewInvisible;
            
            // 当隐身时,只有队友能看到这个状态
            // 敌人不应该知道你隐身了
            bool bShouldReplicate = !bNewInvisible;  // 取消隐身时复制给所有人
            DOREPLIFETIME_ACTIVE_OVERRIDE(AMyFPSCharacter, bIsInvisible, bShouldReplicate);
        }

        🚗 案例2:载具系统

        CPP
        // MyVehicle.hUCLASS()
        class AMyVehicle : public APawn
        {
            GENERATED_BODY()
            
        public:
            // 🚗 载具状态 - 所有人都能看到
            UPROPERTY(Replicated)
            float CurrentSpeed;
            
            // ⛽ 燃料 - 只有驾驶员能看到
            UPROPERTY(Replicated)
            float Fuel;
            
            // 🔧 引擎状态 - 只有驾驶员能看到
            UPROPERTY(Replicated)
            bool bEngineRunning;
            
            // 📍 物理位置 - 模拟或物理复制
            UPROPERTY(Replicated)
            FVector PhysicsLocation;
            
            // 🔄 物理旋转 - 模拟或物理复制
            UPROPERTY(Replicated)
            FRotator PhysicsRotation;
            
            // 🎮 输入状态 - 只有驾驶员需要
            UPROPERTY(Replicated)
            float ThrottleInput;
            
            UPROPERTY(Replicated)
            float SteeringInput;
            
            virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
        };
        
        // MyVehicle.cppvoid AMyVehicle::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 所有人都能看到速度
            DOREPLIFETIME(AMyVehicle, CurrentSpeed);
            
            // 只有驾驶员能看到燃料和引擎状态
            DOREPLIFETIME_CONDITION(AMyVehicle, Fuel, COND_OwnerOnly);
            DOREPLIFETIME_CONDITION(AMyVehicle, bEngineRunning, COND_OwnerOnly);
            
            // 物理数据:模拟代理或物理复制时发送
            // 当 bReplicatePhysics=true 时,即使是 Owner 也会收到
            DOREPLIFETIME_CONDITION(AMyVehicle, PhysicsLocation, COND_SimulatedOrPhysics);
            DOREPLIFETIME_CONDITION(AMyVehicle, PhysicsRotation, COND_SimulatedOrPhysics);
            
            // 输入状态只有自主代理需要
            DOREPLIFETIME_CONDITION(AMyVehicle, ThrottleInput, COND_AutonomousOnly);
            DOREPLIFETIME_CONDITION(AMyVehicle, SteeringInput, COND_AutonomousOnly);
        }

        🎒 案例3:背包系统

        CPP
        // MyInventoryComponent.hUCLASS()
        class UMyInventoryComponent : public UActorComponent
        {
            GENERATED_BODY()
            
        public:
            // 📦 背包容量 - 只发送一次
            UPROPERTY(Replicated)
            int32 MaxSlots;
            
            // 🎒 物品列表 - 只有拥有者能看到
            UPROPERTY(Replicated)
            TArray<FInventoryItem> Items;
            
            // 💰 金币数量 - 只有拥有者能看到
            UPROPERTY(Replicated)
            int32 Gold;
            
            // ⚖️ 当前重量 - 只有拥有者能看到
            UPROPERTY(Replicated)
            float CurrentWeight;
            
            // 🔓 是否已解锁扩展槽 - 初始或拥有者
            UPROPERTY(Replicated)
            bool bExtendedSlotsUnlocked;
            
            virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
        };
        
        // MyInventoryComponent.cppvoid UMyInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 背包容量只发送一次
            DOREPLIFETIME_CONDITION(UMyInventoryComponent, MaxSlots, COND_InitialOnly);
            
            // 物品、金币、重量只有拥有者能看到
            DOREPLIFETIME_CONDITION(UMyInventoryComponent, Items, COND_OwnerOnly);
            DOREPLIFETIME_CONDITION(UMyInventoryComponent, Gold, COND_OwnerOnly);
            DOREPLIFETIME_CONDITION(UMyInventoryComponent, CurrentWeight, COND_OwnerOnly);
            
            // 扩展槽状态:初始发送给所有人,之后只发给拥有者
            DOREPLIFETIME_CONDITION(UMyInventoryComponent, bExtendedSlotsUnlocked, COND_InitialOrOwner);
        }

        🏆 案例4:比赛系统

        CPP
        // MyMatchState.hUCLASS()
        class AMyMatchState : public AInfo
        {
            GENERATED_BODY()
            
        public:
            // ⏱️ 比赛时间 - 所有人都能看到
            UPROPERTY(Replicated)
            float MatchTime;
            
            // 📊 比分 - 所有人都能看到
            UPROPERTY(Replicated)
            int32 TeamAScore;
            
            UPROPERTY(Replicated)
            int32 TeamBScore;
            
            // 🎮 比赛阶段 - 所有人都能看到
            UPROPERTY(Replicated)
            EMatchPhase CurrentPhase;
            
            // 📹 回放数据 - 只在回放时需要
            UPROPERTY(Replicated)
            TArray<FReplayKeyFrame> ReplayKeyFrames;
            
            // 🔧 调试数据 - 永不复制(仅服务器使用)
            UPROPERTY(Replicated)
            FString DebugInfo;
            
            virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
        };
        
        // MyMatchState.cppvoid AMyMatchState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 基本比赛信息 - 所有人都需要
            DOREPLIFETIME(AMyMatchState, MatchTime);
            DOREPLIFETIME(AMyMatchState, TeamAScore);
            DOREPLIFETIME(AMyMatchState, TeamBScore);
            DOREPLIFETIME(AMyMatchState, CurrentPhase);
            
            // 回放数据 - 只在回放时发送
            DOREPLIFETIME_CONDITION(AMyMatchState, ReplayKeyFrames, COND_ReplayOnly);
            
            // 调试信息 - 永不复制
            DOREPLIFETIME_CONDITION(AMyMatchState, DebugInfo, COND_Never);
        }

        13.6 高级主题:动态条件与自定义条件

        🎭 COND_Dynamic - 运行时改变条件

        动态条件允许你在运行时改变属性的复制条件:

        CPP
        // MyDynamicActor.hUCLASS()
        class AMyDynamicActor : public AActor
        {
            GENERATED_BODY()
            
        public:
            // 使用动态条件的属性
            UPROPERTY(Replicated)
            float DynamicData;
            
            // 改变条件的方法
            UFUNCTION(BlueprintCallable)
            void SetDynamicDataCondition(ELifetimeCondition NewCondition);
            
            virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
        };
        
        // MyDynamicActor.cppvoid AMyDynamicActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 声明为动态条件
            DOREPLIFETIME_CONDITION(AMyDynamicActor, DynamicData, COND_Dynamic);
        }
        
        void AMyDynamicActor::SetDynamicDataCondition(ELifetimeCondition NewCondition){
            if (HasAuthority())
            {
                // 使用 Iris 的 API 改变条件
                // 这会触发 SetPropertyDynamicCondition
                DOREPLIFETIME_CHANGE_CONDITION(AMyDynamicActor, DynamicData, NewCondition);
            }
        }

        Iris 内部实现:

        CPP
        // ReplicationConditionals.cppbool FReplicationConditionals::SetPropertyDynamicCondition(
            FInternalNetRefIndex ObjectIndex, 
            const void* Owner, 
            uint16 RepIndex, 
            ELifetimeCondition Condition){
            // 获取旧条件
            const ELifetimeCondition OldCondition = GetDynamicCondition(ObjectIndex, RepIndex);
            
            // 设置新条件
            SetDynamicCondition(ObjectIndex, RepIndex, Condition);
            
            // 如果条件变化可能导致属性从"不复制"变为"复制"
            if (DynamicConditionChangeRequiresBaselineInvalidation(OldCondition, Condition))
            {
                // 标记属性为脏
                MarkDirty(ReplicationStateHeader, MemberChangeMask, ChangeMaskDescriptor);
                
                // 使基线失效(增量压缩需要重新建立基线)
                BaselineInvalidationTracker->InvalidateBaselines(ObjectIndex, 
                    BaselineInvalidationTracker->InvalidateBaselineForAllConnections);
            }
            
            return true;
        }
        
        // 判断条件变化是否需要使基线失效bool FReplicationConditionals::DynamicConditionChangeRequiresBaselineInvalidation(
            ELifetimeCondition OldCondition, 
            ELifetimeCondition NewCondition) const{
            // 如果旧条件可能导致属性不被复制
            const bool OldConditionMayHaveBeenDisabled = 
                !(OldCondition == COND_None || OldCondition == COND_Dynamic);
            
            // 如果新条件允许复制
            const bool NewConditionMayBeEnabled = (NewCondition != COND_Never);
            
            // 从"可能不复制"变为"可能复制"时需要使基线失效
            return OldConditionMayHaveBeenDisabled && NewConditionMayBeEnabled;
        }

        🔧 COND_Custom - 自定义激活控制

        自定义条件允许你完全控制属性是否复制:

        CPP
        // MyCustomConditionActor.hUCLASS()
        class AMyCustomConditionActor : public AActor
        {
            GENERATED_BODY()
            
        public:
            // 使用自定义条件的属性
            UPROPERTY(Replicated)
            float SecretValue;
            
            UPROPERTY(Replicated)
            TArray<int32> SecretArray;
            
            // 控制是否激活
            void SetSecretValueActive(bool bActive);
            void SetSecretArrayActive(bool bActive);
            
            virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
        };
        
        // MyCustomConditionActor.cppvoid AMyCustomConditionActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const{
            Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            
            // 声明为自定义条件
            DOREPLIFETIME_CONDITION(AMyCustomConditionActor, SecretValue, COND_Custom);
            DOREPLIFETIME_CONDITION(AMyCustomConditionActor, SecretArray, COND_Custom);
        }
        
        void AMyCustomConditionActor::SetSecretValueActive(bool bActive){
            // 使用宏设置激活状态
            DOREPLIFETIME_ACTIVE_OVERRIDE(AMyCustomConditionActor, SecretValue, bActive);
        }
        
        void AMyCustomConditionActor::SetSecretArrayActive(bool bActive){
            DOREPLIFETIME_ACTIVE_OVERRIDE(AMyCustomConditionActor, SecretArray, bActive);
        }

        宏展开解析:

        CPP
        // DOREPLIFETIME_ACTIVE_OVERRIDE 宏展开#define DOREPLIFETIME_ACTIVE_OVERRIDE(c,v,active) \
        { \
            static FProperty* sp##v = GetReplicatedProperty(StaticClass(), c::StaticClass(), \
                                                            GET_MEMBER_NAME_CHECKED(c,v)); \
            UE::Net::Private::FNetPropertyConditionManager& PropertyConditionManager = \
                UE::Net::Private::FNetPropertyConditionManager::Get(); \
            for (int32 i = 0; i < sp##v->ArrayDim; i++) \
            { \
                PropertyConditionManager.SetPropertyActiveOverride(this, sp##v->RepIndex + i, active); \
            } \
        }

        📊 Iris 中的自定义条件实现

        CPP
        // ReplicationConditionals.cppbool FReplicationConditionals::SetPropertyCustomCondition(
            FInternalNetRefIndex ObjectIndex, 
            const void* Owner, 
            uint16 RepIndex, 
            bool bIsActive){
            // 获取条件变化掩码
            FNetBitArrayView ConditionalChangeMask = 
                GetMemberConditionalChangeMask(Fragment.ExternalSrcBuffer, StateDescriptor);
            
            if (bIsActive)
            {
                // 激活:设置条件掩码位
                ConditionalChangeMask.SetBits(ChangeMaskDescriptor.BitOffset, ChangeMaskDescriptor.BitCount);
                
                // 同时标记属性为脏
                FNetBitArrayView MemberChangeMask = 
                    GetMemberChangeMask(Fragment.ExternalSrcBuffer, StateDescriptor);
                MarkDirty(ReplicationStateHeader, MemberChangeMask, ChangeMaskDescriptor);
                
                // 使基线失效
                BaselineInvalidationTracker->InvalidateBaselines(ObjectIndex, 
                    BaselineInvalidationTracker->InvalidateBaselineForAllConnections);
            }
            else
            {
                // 禁用:清除条件掩码位
                ConditionalChangeMask.ClearBits(ChangeMaskDescriptor.BitOffset, ChangeMaskDescriptor.BitCount);
            }
            
            return true;
        }

        🌐 COND_NetGroup - 子对象网络组条件

        网络组条件用于控制子对象的复制,基于连接是否属于特定组:

        CPP
        // 注册子对象时使用 COND_NetGroupvoid AMyActor::RegisterSubObjectWithNetGroup(UObject* SubObject, FNetObjectGroupHandle GroupHandle){
            if (UObjectReplicationBridge* Bridge = GetObjectReplicationBridge())
            {
                // 使用 COND_NetGroup 注册子对象
                Bridge->AddSubObject(GetNetRefHandle(), SubObject, COND_NetGroup);
                
                // 将子对象添加到网络组
                Bridge->AddToGroup(GroupHandle, SubObject);
            }
        }

        Iris 内部处理:

        CPP
        // ReplicationConditionals.cpp - GetChildSubObjectsToReplicatevoid FReplicationConditionals::GetChildSubObjectsToReplicate(
            uint32 ReplicatingConnectionId, 
            const FConditionalsMask& LifetimeConditionals,
            const FInternalNetRefIndex ParentObjectIndex, 
            FSubObjectsToReplicateArray& OutSubObjectsToReplicate){
            for (uint32 ArrayIndex = 0; ArrayIndex < SubObjectsInfo.NumSubObjects; ++ArrayIndex)
            {
                const FInternalNetRefIndex SubObjectIndex = SubObjectsInfo.ChildSubObjects[ArrayIndex];
                const ELifetimeCondition LifeTimeCondition = 
                    (ELifetimeCondition)SubObjectsInfo.SubObjectLifeTimeConditions[ArrayIndex];
                
                if (LifeTimeCondition == COND_NetGroup)
                {
                    // 检查连接是否属于子对象的网络组
                    bool bShouldReplicateSubObject = false;
                    
                    const TArrayView<const FNetObjectGroupHandle::FGroupIndexType> GroupIndexes = 
                        NetObjectGroups->GetGroupIndexesOfNetObject(SubObjectIndex);
                    
                    for (const FNetObjectGroupHandle::FGroupIndexType GroupIndex : GroupIndexes)
                    {
                        const FNetObjectGroupHandle NetGroup = NetObjectGroups->GetHandleFromIndex(GroupIndex);
                        
                        if (NetGroup.IsNetGroupOwnerNetObjectGroup())
                        {
                            // Owner 组
                            bShouldReplicateSubObject = LifetimeConditionals.IsConditionEnabled(COND_OwnerOnly);
                        }
                        else if (NetGroup.IsNetGroupReplayNetObjectGroup())
                        {
                            // Replay 组
                            bShouldReplicateSubObject = LifetimeConditionals.IsConditionEnabled(COND_ReplayOnly);
                        }
                        else
                        {
                            // 自定义子对象过滤组
                            ENetFilterStatus ReplicationStatus = ENetFilterStatus::Disallow;
                            ReplicationFiltering->GetSubObjectFilterStatus(NetGroup, ReplicatingConnectionId, 
                                                                           ReplicationStatus);
                            bShouldReplicateSubObject = ReplicationStatus != ENetFilterStatus::Disallow;
                        }
                        
                        if (bShouldReplicateSubObject)
                        {
                            OutSubObjectsToReplicate.Add(SubObjectIndex);
                            break;
                        }
                    }
                }
                else if (LifetimeConditionals.IsConditionEnabled(LifeTimeCondition))
                {
                    // 普通条件检查
                    OutSubObjectsToReplicate.Add(SubObjectIndex);
                }
            }
        }

        13.7 性能优化与最佳实践

        ⚡ 性能影响分析

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────┐
        │                    条件复制性能影响                              │
        ├─────────────────────────────────────────────────────────────────┤
        │                                                                 │
        │  📊 CPU 开销:                                                  │
        │  ┌─────────────────────────────────────────────────────────┐   │
        │  │ GetLifetimeConditionals()    │ O(1) - 位操作,非常快     │   │
        │  │ ApplyConditionalsToChangeMask│ O(n) - n=属性数量        │   │
        │  │ SetPropertyCustomCondition   │ O(1) - 单属性操作        │   │
        │  └─────────────────────────────────────────────────────────┘   │
        │                                                                 │
        │  💾 内存开销:                                                  │
        │  ┌─────────────────────────────────────────────────────────┐   │
        │  │ FPerObjectInfo      │ 2 bytes/对象                      │   │
        │  │ FConditionalsMask   │ 2 bytes/对象/连接                 │   │
        │  │ DynamicConditions   │ 仅使用 COND_Dynamic 时分配        │   │
        │  └─────────────────────────────────────────────────────────┘   │
        │                                                                 │
        │  📡 带宽节省:                                                  │
        │  ┌─────────────────────────────────────────────────────────┐   │
        │  │ COND_OwnerOnly     │ 节省 (N-1)/N 的带宽(N=连接数)    │   │
        │  │ COND_InitialOnly   │ 节省后续所有更新的带宽             │   │
        │  │ COND_SkipOwner     │ 节省 1/N 的带宽                    │   │
        │  └─────────────────────────────────────────────────────────┘   │
        │                                                                 │
        └─────────────────────────────────────────────────────────────────┘

        ✅ 最佳实践清单

        1️⃣ 选择正确的条件

        CPP
        // ✅ 好的做法:根据数据特性选择条件DOREPLIFETIME_CONDITION(AMyActor, PrivateData, COND_OwnerOnly);      // 敏感数据DOREPLIFETIME_CONDITION(AMyActor, StaticName, COND_InitialOnly);     // 不变数据DOREPLIFETIME_CONDITION(AMyActor, PredictedPos, COND_SkipOwner);     // 预测数据
        
        // ❌ 不好的做法:所有数据都用 COND_NoneDOREPLIFETIME(AMyActor, PrivateData);  // 浪费带宽,可能泄露敏感信息DOREPLIFETIME(AMyActor, StaticName);   // 不必要的重复发送

        2️⃣ 避免频繁改变动态条件

        CPP
        // ❌ 不好的做法:每帧改变条件void AMyActor::Tick(float DeltaTime){
            // 每帧都改变条件会导致频繁的基线失效
            DOREPLIFETIME_CHANGE_CONDITION(AMyActor, SomeData, 
                bSomeCondition ? COND_OwnerOnly : COND_None);
        }
        
        // ✅ 好的做法:只在状态真正改变时更新void AMyActor::OnStateChanged(bool bNewState){
            if (bNewState != bCachedState)
            {
                bCachedState = bNewState;
                DOREPLIFETIME_CHANGE_CONDITION(AMyActor, SomeData, 
                    bNewState ? COND_OwnerOnly : COND_None);
            }
        }

        3️⃣ 合理使用 COND_Custom

        CPP
        // ✅ 好的做法:在适当时机激活/禁用void AMyActor::OnBecomeRelevant(){
            // 变得相关时激活
            DOREPLIFETIME_ACTIVE_OVERRIDE(AMyActor, DetailedData, true);
        }
        
        void AMyActor::OnBecomeIrrelevant(){
            // 不相关时禁用,节省带宽
            DOREPLIFETIME_ACTIVE_OVERRIDE(AMyActor, DetailedData, false);
        }
        
        // ❌ 不好的做法:频繁切换void AMyActor::Tick(float DeltaTime){
            // 每帧切换会产生不必要的开销
            DOREPLIFETIME_ACTIVE_OVERRIDE(AMyActor, DetailedData, 
                FMath::RandBool());
        }

        4️⃣ 考虑子对象的条件

        CPP
        // ✅ 好的做法:子对象也使用适当的条件void AMyCharacter::RegisterInventorySubObject(UInventoryComponent* Inventory){
            if (UObjectReplicationBridge* Bridge = GetObjectReplicationBridge())
            {
                // 背包只复制给拥有者
                Bridge->AddSubObject(GetNetRefHandle(), Inventory, COND_OwnerOnly);
            }
        }
        
        // ❌ 不好的做法:忽略子对象条件void AMyCharacter::RegisterInventorySubObject(UInventoryComponent* Inventory){
            if (UObjectReplicationBridge* Bridge = GetObjectReplicationBridge())
            {
                // 默认复制给所有人,可能泄露敏感信息
                Bridge->AddSubObject(GetNetRefHandle(), Inventory, COND_None);
            }
        }

        📊 条件选择决策树

        PLAINTEXT
        开始选择条件
            │
            ├─ 数据是否敏感(如弹药、金币)?
            │   ├─ 是 → COND_OwnerOnly
            │   └─ 否 ↓
            │
            ├─ 数据是否永不改变(如名字、皮肤)?
            │   ├─ 是 → COND_InitialOnly
            │   └─ 否 ↓
            │
            ├─ 本地玩家是否有预测(如位置)?
            │   ├─ 是 → COND_SkipOwner
            │   └─ 否 ↓
            │
            ├─ 是否只有控制者需要(如输入状态)?
            │   ├─ 是 → COND_AutonomousOnly
            │   └─ 否 ↓
            │
            ├─ 是否只有旁观者需要(如平滑位置)?
            │   ├─ 是 → COND_SimulatedOnly
            │   └─ 否 ↓
            │
            ├─ 是否涉及物理复制?
            │   ├─ 是 → COND_SimulatedOrPhysics
            │   └─ 否 ↓
            │
            ├─ 是否需要运行时控制?
            │   ├─ 是,完全控制 → COND_Custom
            │   ├─ 是,改变条件 → COND_Dynamic
            │   └─ 否 ↓
            │
            ├─ 是否只在回放时需要?
            │   ├─ 是 → COND_ReplayOnly
            │   └─ 否 ↓
            │
            ├─ 是否永远不需要复制?
            │   ├─ 是 → COND_Never
            │   └─ 否 → COND_None
        

        🐛 常见问题排查

        问题

        可能原因

        解决方案

        属性没有复制

        条件不满足

        检查 Owner/Role 关系

        敏感数据泄露

        使用了 COND_None

        改用 COND_OwnerOnly

        初始数据重复发送

        未使用 COND_InitialOnly

        添加 InitialOnly 条件

        预测抖动

        服务器位置覆盖本地预测

        使用 COND_SkipOwner

        动态条件不生效

        未正确设置

        检查 DOREPLIFETIME_CHANGE_CONDITION

        自定义条件无效

        未初始化

        确保调用 InitPropertyCustomConditions


        13.8 总结

        🎯 核心概念回顾

        PLAINTEXT
        ┌─────────────────────────────────────────────────────────────────┐
        │                    条件复制系统总结                              │
        ├─────────────────────────────────────────────────────────────────┤
        │                                                                 │
        │  📋 ELifetimeCondition - 属性级条件                             │
        │  ┌─────────────────────────────────────────────────────────┐   │
        │  │ • COND_None         - 无条件,始终复制                   │   │
        │  │ • COND_OwnerOnly    - 仅拥有者                          │   │
        │  │ • COND_SkipOwner    - 跳过拥有者                        │   │
        │  │ • COND_InitialOnly  - 仅初始状态                        │   │
        │  │ • COND_SimulatedOnly- 仅模拟代理                        │   │
        │  │ • COND_AutonomousOnly- 仅自主代理                       │   │
        │  │ • COND_Custom       - 自定义控制                        │   │
        │  │ • COND_Dynamic      - 运行时改变                        │   │
        │  └─────────────────────────────────────────────────────────┘   │
        │                                                                 │
        │  🔧 EReplicationCondition - 对象级条件                          │
        │  ┌─────────────────────────────────────────────────────────┐   │
        │  │ • RoleAutonomous    - 自主角色标识                       │   │
        │  │ • ReplicatePhysics  - 物理复制标识                       │   │
        │  └─────────────────────────────────────────────────────────┘   │
        │                                                                 │
        │  🏗️ FReplicationConditionals - 核心管理类                       │
        │  ┌─────────────────────────────────────────────────────────┐   │
        │  │ • GetLifetimeConditionals()     - 计算条件掩码           │   │
        │  │ • ApplyConditionalsToChangeMask() - 应用条件过滤         │   │
        │  │ • SetPropertyCustomCondition()  - 设置自定义条件         │   │
        │  │ • SetPropertyDynamicCondition() - 设置动态条件           │   │
        │  └─────────────────────────────────────────────────────────┘   │
        │                                                                 │
        └─────────────────────────────────────────────────────────────────┘

        ✅ 关键要点

        1. 选择正确的条件:根据数据特性选择最合适的条件,既保护敏感数据又节省带宽

        2. 理解 Owner 和 Role:

          • Owner:谁拥有这个对象(通常是 PlayerController)

          • Role:Autonomous(控制者)vs Simulated(旁观者)

        3. 善用 InitialOnly:对于不变的数据(名字、皮肤等),使用 COND_InitialOnly 避免重复发送

        4. 保护敏感数据:弹药、金币、技能冷却等使用 COND_OwnerOnly

        5. 支持本地预测:对于有客户端预测的数据(位置、速度),使用 COND_SkipOwner

        6. 谨慎使用动态条件:频繁改变条件会导致基线失效,影响增量压缩效率

        📚 相关文档

        文档

        内容

        Part5_Filtering.md

        过滤系统 - 对象级过滤

        Part6_Prioritization.md

        优先级系统 - 发送顺序控制

        Part11_Polling_DirtyDetection.md

        脏数据检测 - 变化追踪

        Part12_ObjectReference_Dependency.md

        对象引用 - 依赖管理


        本文档基于 Unreal Engine 5.5.0 Iris 源代码分析
        源码目录:Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/Conditionals/

        最后更新于 April 13, 2026
        On this page
        暂无目录