⚙️ Iris 网络复制系统技术分析 - 第十六部分:配置与集成

📖 本章导读:想象你刚买了一台新电脑,开机后需要进行各种设置——连接 WiFi、调整分辨率、安装软件……Iris 系统也是如此!本章将手把手教你如何"开箱"并配置 Iris,让它完美融入你的项目。无论你是想快速上手还是深度定制,这里都有你需要的答案!🎮
🎯 16.1 启用 Iris:从零开始的第一步
💡 16.1.1 什么是"启用 Iris"?
PLAINTEXT
🎮 日常类比:切换手机输入法
想象你的手机上有两种输入法:
📱 系统自带输入法(传统 NetDriver):稳定但功能有限
⌨️ 第三方输入法(Iris):功能强大但需要手动启用
启用 Iris 就像切换输入法:
1️⃣ 首先要安装(确保插件存在)
2️⃣ 然后要启用(配置开关)
3️⃣ 最后要设为默认(让系统使用它)
┌────────────────────────────────────────────────────────────────────┐
│ 🔄 网络复制系统切换示意图 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 传统 NetDriver 复制系统 Iris 复制系统 │
│ ═══════════════════════ ════════════════ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 🎮 游戏对象 │ │ 🎮 游戏对象 │ │
│ │ ┌───┐ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ ┌───┐ │ │
│ │ │ A │ │ B │ │ C │ │ │ │ A │ │ B │ │ C │ │ │
│ │ └───┘ └───┘ └───┘ │ │ └───┘ └───┘ └───┘ │ │
│ └─────────┬───────────┘ └─────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 📦 传统复制 │ 切换 ➡️ │ 🚀 Iris 复制 │ │
│ │ • 逐个对象处理 │ │ • 批量并行处理 │ │
│ │ • 固定优先级 │ │ • 智能优先级 │ │
│ │ • 简单过滤 │ │ • 高级过滤系统 │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ⚠️ 注意:两种系统不能同时使用,必须选择其一! │
│ │
└────────────────────────────────────────────────────────────────────┘📂 16.1.2 关键源文件索引
文件 | 路径(Engine/Source/Runtime/Experimental/Iris/) | 职责 |
|---|---|---|
|
| Iris 全局配置开关 |
|
| 配置实现 |
|
| 模块加载入口 |
|
| 对象复制桥接配置 |
|
| 过滤系统配置 |
|
| 空间网格过滤配置 |
|
| 复制状态描述符配置 |
🔌 16.1.3 启用 Iris 插件
Iris 作为 UE5 的实验性功能,需要手动启用。插件位于:
PLAINTEXT
Engine/Plugins/Experimental/Iris/
├── Iris.uplugin # 插件描述文件
├── Source/
│ ├── IrisCore/ # 核心模块
│ └── IrisStub/ # 存根模块(禁用时使用)方式一:通过编辑器启用
PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 📋 编辑器启用步骤 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ 打开 UE 编辑器 │
│ ┌─────────────────────────────────────────┐ │
│ │ Edit → Plugins │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 2️⃣ 搜索 "Iris" │
│ ┌─────────────────────────────────────────┐ │
│ │ 🔍 Search: [Iris____________] │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 3️⃣ 勾选启用 │
│ ┌─────────────────────────────────────────┐ │
│ │ ☑️ Iris │ │
│ │ Experimental network replication │ │
│ │ system for Unreal Engine │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 4️⃣ 重启编辑器 │
│ ⚠️ 插件更改需要重启才能生效! │
│ │
└────────────────────────────────────────────────────────────────────┘方式二:通过 .uproject 文件启用
JSON
// YourProject.uproject{
"Plugins": [
{
"Name": "Iris",
"Enabled": true
}
]}⚙️ 16.1.4 三种启用方式详解
Iris 提供了灵活的启用方式,适应不同的开发场景:
PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 🎛️ Iris 启用方式对比 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 方式一:命令行参数(最高优先级) │
│ ════════════════════════════════ │
│ 适用场景:临时测试、快速切换 │
│ │
│ 方式二:控制台变量(运行时可改) │
│ ════════════════════════════════ │
│ 适用场景:开发调试、动态切换 │
│ │
│ 方式三:配置文件(持久化设置) │
│ ════════════════════════════════ │
│ 适用场景:正式项目、团队协作 │
│ │
│ 优先级:命令行 > 控制台变量 > 配置文件 │
│ │
└────────────────────────────────────────────────────────────────────┘🔹 方式一:命令行参数
BASH
# 启用 Iris
UE4Editor.exe YourProject.uproject -UseIrisReplication=1
# 禁用 Iris(使用传统系统)
UE4Editor.exe YourProject.uproject -UseIrisReplication=0源码解析:
CPP
// 源码位置:IrisConfig.cppEReplicationSystem GetUseIrisReplicationCmdlineValue(){
int32 UseIrisReplication = 0;
// 解析命令行参数
if (FParse::Value(FCommandLine::Get(), TEXT("UseIrisReplication="), UseIrisReplication))
{
// 命令行指定了值,使用指定的系统
return UseIrisReplication > 0 ? EReplicationSystem::Iris : EReplicationSystem::Generic;
}
// 未指定,使用默认值
return EReplicationSystem::Default;
}🔹 方式二:控制台变量
CPP
// 控制台命令
net.Iris.UseIrisReplication 1 // 启用 Iris
net.Iris.UseIrisReplication 0 // 禁用 Iris源码解析:
CPP
// 源码位置:IrisConfig.cppnamespace UE::Net
{
// 控制台变量定义
static int32 CVarUseIrisReplication = 0;
static FAutoConsoleVariableRef CVarUseIrisReplicationRef(
TEXT("net.Iris.UseIrisReplication"), // 变量名
CVarUseIrisReplication, // 绑定的变量
TEXT("Enables Iris replication system. " // 帮助文本
"0 will fallback to legacy replication system."),
ECVF_Default // 变量标志
);
// 查询函数
bool ShouldUseIrisReplication()
{
return CVarUseIrisReplication > 0;
}
// 设置函数
void SetUseIrisReplication(bool EnableIrisReplication)
{
CVarUseIrisReplication = EnableIrisReplication ? 1 : 0;
}
}🔹 方式三:配置文件
INI
; 位置:Config/DefaultEngine.ini[/Script/Engine.Engine]; 启用 Iris 复制系统net.Iris.UseIrisReplication=1🔄 16.1.5 模块加载流程
当 Iris 插件被启用时,IrisCoreModule 会在引擎启动时自动加载:
CPP
// 源码位置:IrisCoreModule.cppclass FIrisCoreModule : public IModuleInterface
{
virtual void StartupModule() override
{
// 1️⃣ 加载依赖模块
FModuleManager::LoadModuleChecked<IModuleInterface>("NetCore");
// 2️⃣ 检查命令行参数
const EReplicationSystem CmdlineRepSystem = UE::Net::GetUseIrisReplicationCmdlineValue();
if (CmdlineRepSystem != EReplicationSystem::Default)
{
// 命令行优先级最高
const bool bEnableIris = CmdlineRepSystem == EReplicationSystem::Iris;
UE::Net::SetUseIrisReplication(bEnableIris);
}
// 3️⃣ 注册属性序列化器
RegisterPropertyNetSerializerSelectorTypes();
// 4️⃣ 初始化 Legacy Push Model 支持
UE_NET_IRIS_INIT_LEGACY_PUSH_MODEL();
// 5️⃣ 监听模块变化
ModulesChangedHandle = FModuleManager::Get().OnModulesChanged()
.AddRaw(this, &FIrisCoreModule::OnModulesChanged);
}
};PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 🔄 Iris 模块加载流程图 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 引擎启动 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ 加载 IrisCore 模块 │ │
│ │ FIrisCoreModule::Startup() │ │
│ └─────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ 加载依赖:NetCore 模块 │ │
│ └─────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ ┌───────────────────────┐ │
│ │ 检查命令行参数 │ ──► │ -UseIrisReplication=? │ │
│ │ GetUseIrisReplicationCmd() │ └───────────────────────┘ │
│ └─────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ 注册属性序列化器 │ │
│ │ RegisterDefaultProperty... │ │
│ └─────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ 初始化 Legacy Push Model │ │
│ └─────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ ✅ Iris 模块就绪 │ │
│ └─────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘📋 16.2 配置文件详解
💡 16.2.1 配置系统概述
PLAINTEXT
🎮 日常类比:汽车仪表盘设置
想象你买了一辆新车,可以自定义很多设置:
🚗 座椅位置 → 轮询频率配置
🔊 音响音量 → 优先级配置
🌡️ 空调温度 → 过滤器配置
💡 仪表亮度 → 增量压缩配置
Iris 的配置系统就像汽车的设置菜单:
- 每种设置都有默认值(出厂设置)
- 你可以根据需要调整(个性化配置)
- 不同场景可能需要不同配置(城市/高速模式)
┌────────────────────────────────────────────────────────────────────┐
│ 📊 Iris 配置文件层次结构 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ DefaultEngine.ini(引擎默认配置) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ [/Script/IrisCore.ObjectReplicationBridgeConfig] │ │
│ │ • PollConfigs → 轮询频率配置 │ │
│ │ • FilterConfigs → 过滤器配置 │ │
│ │ • PrioritizerConfigs → 优先级器配置 │ │
│ │ • DeltaCompressionConfigs → 增量压缩配置 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ [/Script/IrisCore.ReplicationFilteringConfig] │ │
│ │ • bEnableObjectScopeHysteresis → 滞后过滤开关 │ │
│ │ • DefaultHysteresisFrameCount → 默认滞后帧数 │ │
│ │ • HysteresisProfiles → 滞后配置档案 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ [/Script/IrisCore.NetObjectGridFilterConfig] │ │
│ │ • CellSizeX/Y → 网格单元大小 │ │
│ │ • DefaultCullDistance → 默认裁剪距离 │ │
│ │ • bUseExactCullDistance → 精确裁剪开关 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘📦 16.2.2 UObjectReplicationBridgeConfig(对象复制桥接配置)
这是 Iris 最核心的配置类,控制着对象如何被复制。
CPP
// 源码位置:ObjectReplicationBridgeConfig.hUCLASS(transient, config=Engine)
class UObjectReplicationBridgeConfig : public UObject
{
GENERATED_BODY()
public:
// 获取配置单例
IRISCORE_API static const UObjectReplicationBridgeConfig* GetConfig();
// 各种配置的访问接口
IRISCORE_API TConstArrayView<FObjectReplicationBridgePollConfig> GetPollConfigs() const;
IRISCORE_API TConstArrayView<FObjectReplicationBridgeFilterConfig> GetFilterConfigs() const;
IRISCORE_API TConstArrayView<FObjectReplicationBridgePrioritizerConfig> GetPrioritizerConfigs() const;
IRISCORE_API TConstArrayView<FObjectReplicationBridgeDeltaCompressionConfig> GetDeltaCompressionConfigs() const;
private:
// 📊 轮询频率配置
UPROPERTY(Config)
TArray<FObjectReplicationBridgePollConfig> PollConfigs;
// 🔍 过滤器配置
UPROPERTY(Config)
TArray<FObjectReplicationBridgeFilterConfig> FilterConfigs;
// ⚡ 优先级器配置
UPROPERTY(Config)
TArray<FObjectReplicationBridgePrioritizerConfig> PrioritizerConfigs;
// 📦 增量压缩配置
UPROPERTY(Config)
TArray<FObjectReplicationBridgeDeltaCompressionConfig> DeltaCompressionConfigs;
// 🚨 关键类配置(协议不匹配时断开连接)
UPROPERTY(Config)
TArray<FObjectReplicatedBridgeCriticalClassConfig> CriticalClassConfigs;
// 📈 类型统计配置
UPROPERTY(Config)
TArray<FObjectReplicationBridgeTypeStatsConfig> TypeStatsConfigs;
// 🗺️ 默认空间过滤器名称
UPROPERTY(Config)
FName DefaultSpatialFilterName;
};🔄 轮询频率配置(PollConfig)
控制对象属性检查的频率:
CPP
// 源码位置:ObjectReplicationBridgeConfig.hUSTRUCT()
struct FObjectReplicationBridgePollConfig
{
GENERATED_BODY()
// 类名(完整路径)
UPROPERTY()
FName ClassName;
// 轮询频率(每秒次数)
// 0 = 每帧轮询
// 最慢:255 * MaxTickRate(约 8.5 秒 @ 30Hz)
UPROPERTY()
float PollFrequency = 0.0f;
// 是否包含子类
UPROPERTY()
bool bIncludeSubclasses = true;
};配置示例:
INI
; Config/DefaultEngine.ini[/Script/IrisCore.ObjectReplicationBridgeConfig]
; 玩家角色:每帧轮询(最高频率)
+PollConfigs=(ClassName=/Script/MyGame.MyPlayerCharacter, PollFrequency=0.0, bIncludeSubclasses=true)
; NPC:每秒 10 次轮询
+PollConfigs=(ClassName=/Script/MyGame.MyNPC, PollFrequency=10.0, bIncludeSubclasses=true)
; 环境物体:每秒 2 次轮询(低频率)
+PollConfigs=(ClassName=/Script/MyGame.MyEnvironmentActor, PollFrequency=2.0, bIncludeSubclasses=true)PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 📊 轮询频率配置建议表 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 对象类型 │ 建议频率 │ 原因 │
│ ─────────────────┼────────────┼─────────────────────────────── │
│ 🎮 玩家角色 │ 0(每帧) │ 需要最快响应 │
│ 🤖 敌人 AI │ 10-30 Hz │ 战斗中需要较快更新 │
│ 👥 NPC │ 5-10 Hz │ 交互时需要合理响应 │
│ 📦 可拾取物品 │ 2-5 Hz │ 状态变化不频繁 │
│ 🏠 建筑/环境 │ 1-2 Hz │ 几乎不变化 │
│ 🌳 植被/装饰 │ 0.5-1 Hz │ 极少变化 │
│ │
│ ⚠️ 注意:频率越高,CPU 开销越大! │
│ │
└────────────────────────────────────────────────────────────────────┘🔍 过滤器配置(FilterConfig)
控制对象使用哪种过滤器:
CPP
// 源码位置:ObjectReplicationBridgeConfig.hUSTRUCT()
struct FObjectReplicationBridgeFilterConfig
{
GENERATED_BODY()
// 类名
UPROPERTY()
FName ClassName;
// 动态过滤器名称
UPROPERTY()
FName DynamicFilterName;
// 过滤器配置档案(可选)
UPROPERTY()
FName FilterProfile;
// 是否强制应用于所有实例
UPROPERTY()
bool bForceEnableOnAllInstances = false;
};配置示例:
INI
; Config/DefaultEngine.ini[/Script/IrisCore.ObjectReplicationBridgeConfig]
; Pawn 使用空间过滤,并应用特殊的滞后配置
+FilterConfigs=(ClassName=/Script/Engine.Pawn, DynamicFilterName=Spatial, FilterProfile=PawnFilterProfile)
; 武器使用连接过滤(只复制给拥有者)
+FilterConfigs=(ClassName=/Script/MyGame.MyWeapon, DynamicFilterName=Connection, bForceEnableOnAllInstances=true)
; 全局管理器不使用过滤(始终复制)
+FilterConfigs=(ClassName=/Script/MyGame.MyGameManager, DynamicFilterName=None)⚡ 优先级器配置(PrioritizerConfig)
控制对象使用哪种优先级器:
CPP
// 源码位置:ObjectReplicationBridgeConfig.hUSTRUCT()
struct FObjectReplicationBridgePrioritizerConfig
{
GENERATED_BODY()
// 类名
UPROPERTY()
FName ClassName;
// 优先级器名称
// "Default" = 默认空间优先级器
UPROPERTY()
FName PrioritizerName;
// 是否强制应用于所有实例
UPROPERTY()
bool bForceEnableOnAllInstances = false;
};配置示例:
INI
; Config/DefaultEngine.ini[/Script/IrisCore.ObjectReplicationBridgeConfig]
; 玩家使用球形优先级器
+PrioritizerConfigs=(ClassName=/Script/Engine.PlayerController, PrioritizerName=Sphere)
; 投射物使用视野优先级器
+PrioritizerConfigs=(ClassName=/Script/MyGame.MyProjectile, PrioritizerName=FieldOfView)
; AI 使用默认优先级器
+PrioritizerConfigs=(ClassName=/Script/MyGame.MyAICharacter, PrioritizerName=Default)📦 增量压缩配置(DeltaCompressionConfig)
控制哪些类启用增量压缩:
CPP
// 源码位置:ObjectReplicationBridgeConfig.hUSTRUCT()
struct FObjectReplicationBridgeDeltaCompressionConfig
{
GENERATED_BODY()
// 类名
UPROPERTY()
FName ClassName;
// 是否启用增量压缩
UPROPERTY()
bool bEnableDeltaCompression = true;
};配置示例:
INI
; Config/DefaultEngine.ini[/Script/IrisCore.ObjectReplicationBridgeConfig]
; 玩家角色启用增量压缩(属性多,变化频繁)
+DeltaCompressionConfigs=(ClassName=/Script/Engine.Character, bEnableDeltaCompression=true)
; 简单物体禁用增量压缩(属性少,开销不值得)
+DeltaCompressionConfigs=(ClassName=/Script/MyGame.MySimpleActor, bEnableDeltaCompression=false)🔍 16.2.3 UNetObjectGridFilterConfig(空间网格过滤配置)
控制空间过滤器的行为:
CPP
// 源码位置:NetObjectGridFilter.hUCLASS(transient, config=Engine, MinimalAPI)
class UNetObjectGridFilterConfig : public UNetObjectFilterConfig
{
GENERATED_BODY()
public:
// 视图位置相关帧数(避免边界闪烁)
UPROPERTY(Config)
uint32 ViewPosRelevancyFrameCount = 2;
// 默认裁剪前等待帧数
UPROPERTY(Config)
uint16 DefaultFrameCountBeforeCulling = 4;
// 网格单元大小(X 轴)
UPROPERTY(Config)
float CellSizeX = 20000.0f;
// 网格单元大小(Y 轴)
UPROPERTY(Config)
float CellSizeY = 20000.0f;
// 最大裁剪距离(0 = 禁用)
UPROPERTY(Config)
float MaxCullDistance = 0.0f;
// 默认裁剪距离
UPROPERTY(Config)
float DefaultCullDistance = 15000.0f;
// 是否使用精确裁剪距离
UPROPERTY(Config)
bool bUseExactCullDistance = true;
// 过滤器配置档案
UPROPERTY(Config)
TArray<FNetObjectGridFilterProfile> FilterProfiles;
};配置示例:
INI
; Config/DefaultEngine.ini[/Script/IrisCore.NetObjectGridFilterConfig]
; 网格单元大小(根据地图大小调整)CellSizeX=10000.0CellSizeY=10000.0
; 默认裁剪距离DefaultCullDistance=20000.0
; 使用精确距离计算bUseExactCullDistance=true
; 裁剪前等待帧数(避免闪烁)DefaultFrameCountBeforeCulling=8
; 特殊配置档案
+FilterProfiles=(FilterProfileName=BossProfile, FrameCountBeforeCulling=30)
+FilterProfiles=(FilterProfileName=ItemProfile, FrameCountBeforeCulling=2)PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 🗺️ 网格过滤器工作原理图解 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 游戏世界被划分为网格单元: │
│ │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ │ │ │ │ │
│ │ Cell │ Cell │ Cell │ Cell │ │
│ │ (0,2) │ (1,2) │ (2,2) │ (3,2) │ │
│ │ │ │ │ │ │
│ ├─────────┼─────────┼─────────┼─────────┤ │
│ │ │ 🎮 │ 👾 │ │ │
│ │ Cell │ Player │ Enemy │ Cell │ │
│ │ (0,1) │ (1,1) │ (2,1) │ (3,1) │ │
│ │ │ ↓ │ │ │ │
│ ├─────────┼────┼────┼─────────┼─────────┤ │
│ │ │ │ │ │ │ │
│ │ Cell │ CullDist│ Cell │ Cell │ │
│ │ (0,0) │ (1,0) │ (2,0) │ (3,0) │ │
│ │ │ │ │ │ │
│ └─────────┴─────────┴─────────┴─────────┘ │
│ │
│ CellSizeX = 10000.0 (每个单元 100 米宽) │
│ CellSizeY = 10000.0 (每个单元 100 米高) │
│ │
│ 玩家在 (1,1),裁剪距离 20000: │
│ ✅ 复制:(0,0) (1,0) (2,0) (0,1) (1,1) (2,1) (0,2) (1,2) (2,2) │
│ ❌ 不复制:(3,0) (3,1) (3,2) 等超出距离的单元 │
│ │
└────────────────────────────────────────────────────────────────────┘🔄 16.2.4 UReplicationFilteringConfig(复制过滤配置)
控制过滤系统的滞后行为:
CPP
// 源码位置:ReplicationFilteringConfig.hUCLASS(transient, config = Engine)
class UReplicationFilteringConfig final : public UObject
{
GENERATED_BODY()
public:
// 是否启用对象范围滞后
bool IsObjectScopeHysteresisEnabled() const;
// 默认滞后帧数
uint8 GetDefaultHysteresisFrameCount() const;
// 连接更新节流
uint8 GetHysteresisUpdateConnectionThrottling() const;
private:
// 启用滞后过滤
UPROPERTY(Config)
bool bEnableObjectScopeHysteresis = true;
// 默认滞后帧数
UPROPERTY(Config)
uint8 DefaultHysteresisFrameCount = 0;
// 连接更新节流(每 N 帧更新一次)
UPROPERTY(Config)
uint8 HysteresisUpdateConnectionThrottling = 1;
// 滞后配置档案
UPROPERTY(Config)
TArray<FObjectScopeHysteresisProfile> HysteresisProfiles;
};配置示例:
INI
; Config/DefaultEngine.ini[/Script/IrisCore.ReplicationFilteringConfig]
; 启用滞后过滤bEnableObjectScopeHysteresis=true
; 默认滞后 4 帧DefaultHysteresisFrameCount=4
; 每 4 个连接更新一次(节省 CPU)HysteresisUpdateConnectionThrottling=4
; 清除默认配置
!HysteresisProfiles=ClearArray
; Pawn 滞后 30 帧(避免角色突然消失)
+HysteresisProfiles=(FilterProfileName=PawnFilterProfile, HysteresisFrameCount=30)
; 物品滞后 10 帧
+HysteresisProfiles=(FilterProfileName=ItemFilterProfile, HysteresisFrameCount=10)PLAINTEXT
🎮 日常类比:滞后过滤 = 延迟关灯
想象你家有智能灯泡,设置了"人离开后 30 秒自动关灯":
┌────────────────────────────────────────────────────────────────────┐
│ 💡 滞后过滤工作原理 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 时间轴: │
│ ──────────────────────────────────────────────────────────► │
│ │
│ Frame 0 Frame 10 Frame 20 Frame 30 Frame 40 │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 👤 │ │ │ │ │ │ │ │ │ │
│ │在范围│ │离开 │ │仍在 │ │滞后 │ │正式 │ │
│ │内 │ │范围 │ │复制 │ │结束 │ │停止 │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ✅ 复制 ✅ 复制 ✅ 复制 ✅ 复制 ❌ 停止 │
│ │
│ HysteresisFrameCount = 30 │
│ 对象离开范围后,仍继续复制 30 帧,然后才停止 │
│ │
│ 好处: │
│ • 避免边界闪烁(对象在边界来回移动时) │
│ • 平滑过渡(给客户端时间处理对象消失) │
│ • 减少重新创建开销(短暂离开又回来的情况) │
│ │
└────────────────────────────────────────────────────────────────────┘📝 16.2.5 UReplicationStateDescriptorConfig(复制状态描述符配置)
控制结构体的序列化行为:
CPP
// 源码位置:ReplicationStateDescriptorConfig.hUSTRUCT()
struct FSupportsStructNetSerializerConfig
{
GENERATED_BODY()
// 结构体名称
UPROPERTY()
FName StructName;
// 是否可以使用默认的 Iris 结构体序列化器
UPROPERTY()
bool bCanUseStructNetSerializer = true;
};
UCLASS(transient, config=Engine)
class UReplicationStateDescriptorConfig : public UObject
{
GENERATED_BODY()
public:
IRISCORE_API TConstArrayView<FSupportsStructNetSerializerConfig> GetSupportsStructNetSerializerList() const;
private:
// 支持默认序列化器的结构体列表
UPROPERTY(Config)
TArray<FSupportsStructNetSerializerConfig> SupportsStructNetSerializerList;
};配置示例:
INI
; Config/DefaultEngine.ini[/Script/IrisCore.ReplicationStateDescriptorConfig]
; 标记某些结构体可以使用 Iris 默认序列化器; (即使它们实现了自定义 NetSerialize)
+SupportsStructNetSerializerList=(StructName=/Script/MyGame.FMyCustomStruct, bCanUseStructNetSerializer=true)
; 标记某些结构体必须使用自定义序列化
+SupportsStructNetSerializerList=(StructName=/Script/MyGame.FMySpecialStruct, bCanUseStructNetSerializer=false)🔌 16.3 与 NetDriver 集成
💡 16.3.1 NetDriver 检测机制
PLAINTEXT
🎮 日常类比:双系统电脑
想象你的电脑装了 Windows 和 Linux 双系统:
💻 开机时选择启动哪个系统
🔄 两个系统共享硬件资源
⚠️ 同一时间只能运行一个系统
Iris 和传统 NetDriver 的关系也是如此:
- 游戏启动时检测应该使用哪个系统- 两者共享网络连接资源- 同一时间只能使用一个复制系统
┌────────────────────────────────────────────────────────────────────┐
│ 🔄 NetDriver 与 Iris 的关系 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ UNetDriver │
│ │ │
│ │ 管理网络连接 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 网络连接层 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Conn 1 │ │ Conn 2 │ │ Conn 3 │ │ Conn N │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ └───────┼────────────┼────────────┼────────────┼──────────┘ │
│ │ │ │ │ │
│ └────────────┴─────┬──────┴────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ 复制系统选择 │ │
│ │ ┌─────────────┬─────────────┐ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ │ │ │
│ │ 传统系统 Iris 系统 │ │ │
│ │ (Generic) (Iris) │ │ │
│ └──────────────────────────────┘ │ │
│ │
└────────────────────────────────────────────────────────────────────┘🔄 16.3.2 ReplicationSystem 创建流程
当游戏需要网络复制时,会创建 UReplicationSystem:
CPP
// 源码位置:ReplicationSystem.cppUReplicationSystem* FReplicationSystemFactory::CreateReplicationSystem(
const UReplicationSystem::FReplicationSystemParams& Params){
LLM_SCOPE_BYTAG(IrisInitialization);
// 1️⃣ 验证必需参数
if (!Params.ReplicationBridge)
{
UE_LOG(LogIris, Error, TEXT("Cannot create ReplicationSystem without a ReplicationBridge"));
return nullptr;
}
// 2️⃣ 查找可用的系统槽位
for (uint32 It = 0, EndIt = MaxReplicationSystemCount; It != EndIt; ++It)
{
if (ReplicationSystems[It] != nullptr)
{
continue;
}
// 3️⃣ 创建 ReplicationSystem 实例
UReplicationSystem* ReplicationSystem = NewObject<UReplicationSystem>();
ReplicationSystems[It] = ReplicationSystem;
ReplicationSystem->AddToRoot();
// 4️⃣ 分配 ReplicationSystemId
const uint32 ReplicationSystemId = It;
if (ReplicationSystemId > MaxReplicationSystemId)
{
MaxReplicationSystemId = ReplicationSystemId;
}
UE_LOG(LogIris, Display,
TEXT("Iris ReplicationSystem[%i]: %s (0x%p) is created"),
ReplicationSystemId, *ReplicationSystem->GetName(), ReplicationSystem);
// 5️⃣ 初始化系统
ReplicationSystem->Init(ReplicationSystemId, Params);
// 6️⃣ 广播创建事件
if (GetReplicationSystemCreatedDelegate().IsBound())
{
GetReplicationSystemCreatedDelegate().Broadcast(ReplicationSystem);
}
return ReplicationSystem;
}
// 槽位已满
LowLevelFatalError(TEXT("Too many ReplicationSystems have already been created (%u)"),
MaxReplicationSystemCount);
return nullptr;
}PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐│ 🏗️ ReplicationSystem 创建流程 │├────────────────────────────────────────────────────────────────────┤│ ││ 游戏启动/关卡加载 ││ │ ││ ▼ ││ ┌─────────────────────────────┐ ││ │ 检查是否需要网络复制 │ ││ └─────────────┬───────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────┐ ┌───────────────────────┐ ││ │ ShouldUseIrisReplication() │ ──► │ 检查 CVar/命令行 │ ││ └─────────────┬───────────────┘ └───────────────────────┘ ││ │ ││ Yes │ No ││ │ │ │ ││ ▼ │ ▼ ││ ┌───────────┐ │ ┌───────────────┐ ││ │ 创建 Iris │ │ │ 使用传统系统 │ ││ │ 复制系统 │ │ │ NetDriver │ ││ └─────┬─────┘ │ └───────────────┘ ││ │ │ ││ ▼ │ ││ ┌─────────────────────────────┐ ││ │ 创建 ReplicationBridge │ ││ │ (UEngineReplicationBridge) │ ││ └─────────────┬───────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────┐ ││ │ CreateReplicationSystem() │ ││ │ 分配 ReplicationSystemId │ ││ └─────────────┬───────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────┐ ││ │ 初始化子系统 │ ││ │ • 过滤系统 │ ││ │ • 优先级系统 │ ││ │ • 序列化系统 │ ││ └─────────────┬───────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────┐ ││ │ ✅ Iris 系统就绪 │ ││ └─────────────────────────────┘ ││ │└────────────────────────────────────────────────────────────────────┘🔀 16.3.3 混合模式支持
在某些情况下,你可能需要在传统系统和 Iris 之间切换:
CPP
// 检查当前使用的复制系统if (UE::Net::ShouldUseIrisReplication())
{
// 使用 Iris 特有的功能
UReplicationSystem* RepSystem = GetReplicationSystem();
if (RepSystem)
{
// Iris 特有操作
RepSystem->SetCullDistanceSqrOverride(Handle, NewDistance);
}
}
else
{
// 使用传统 NetDriver 功能
AActor* Actor = GetActor();
if (Actor)
{
Actor->NetCullDistanceSquared = NewDistance;
}
}📋 16.3.4 迁移策略
从传统系统迁移到 Iris 的推荐步骤:
PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 📋 Iris 迁移清单 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 阶段一:准备工作 ✅ │
│ ───────────────── │
│ □ 确保 UE5.5+ 版本 │
│ □ 启用 Iris 插件 │
│ □ 备份项目配置 │
│ □ 了解现有网络架构 │
│ │
│ 阶段二:基础配置 ⚙️ │
│ ───────────────── │
│ □ 设置 net.Iris.UseIrisReplication=1 │
│ □ 配置基本的过滤器 │
│ □ 配置基本的优先级器 │
│ □ 测试基本的网络功能 │
│ │
│ 阶段三:优化调整 🔧 │
│ ───────────────── │
│ □ 配置轮询频率 │
│ □ 启用 Push Model(如果需要) │
│ □ 配置增量压缩 │
│ □ 调整空间过滤参数 │
│ │
│ 阶段四:测试验证 🧪 │
│ ───────────────── │
│ □ 单人测试 │
│ □ 多人测试(2-4 人) │
│ □ 压力测试(大量玩家) │
│ □ 性能对比测试 │
│ │
│ 阶段五:正式上线 🚀 │
│ ───────────────── │
│ □ 更新生产配置 │
│ □ 监控网络指标 │
│ □ 收集玩家反馈 │
│ □ 持续优化 │
│ │
└────────────────────────────────────────────────────────────────────┘🎮 16.4 PIE 多实例支持
💡 16.4.1 什么是 PIE 多实例?
PLAINTEXT
🎮 日常类比:同一台电脑开多个游戏窗口
想象你在测试多人游戏:
🖥️ 窗口 1:服务器 + 玩家 1
🖥️ 窗口 2:客户端玩家 2
🖥️ 窗口 3:客户端玩家 3
每个窗口都有自己独立的游戏世界,但共享同一个编辑器进程。
这就是 PIE(Play In Editor)多实例!
┌────────────────────────────────────────────────────────────────────┐
│ 🎮 PIE 多实例架构 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ UE 编辑器进程 │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ PIE 实例 0 │ │ PIE 实例 1 │ │ PIE 实例 2 │ │ │
│ │ │ (Server) │ │ (Client 1) │ │ (Client 2) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ RepSystem 0 │ │ RepSystem 1 │ │ RepSystem 2 │ │ │
│ │ │ World 0 │ │ World 1 │ │ World 2 │ │ │
│ │ │ Actors 0 │ │ Actors 1 │ │ Actors 2 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────────┼─────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ 网络模拟层 │ │ │
│ │ │ (本地回环) │ │ │
│ │ └──────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘🆔 16.4.2 ReplicationSystemId 的作用
每个 PIE 实例都有唯一的 ReplicationSystemId:
CPP
// 源码位置:NetRefHandle.hclass FNetRefHandle
{
union
{
struct
{
// 对象 ID(60 位)
uint64 Id : IdBits;
// 复制系统 ID(4 位)- 标识属于哪个 PIE 实例
uint64 ReplicationSystemId : ReplicationSystemIdBits;
};
uint64 Value;
};
public:
// 获取复制系统 ID
uint32 GetReplicationSystemId() const
{
check(ReplicationSystemId != 0);
return (uint32)(ReplicationSystemId - 1);
}
// 检查是否为完整句柄(包含系统 ID)
bool IsCompleteHandle() const
{
return Value != InvalidValue && ReplicationSystemId != 0U;
}
};PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 🆔 FNetRefHandle 位布局 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 64 位总长度: │
│ ┌─────────────────────────────────────────────────────┬────────┐ │
│ │ Id (60 bits) │RepSysId│ │
│ │ │(4 bits)│ │
│ └─────────────────────────────────────────────────────┴────────┘ │
│ │◄────────────────── 60 位 ──────────────────────────►│◄─4位─►│ │
│ │
│ Id 字段: │
│ • 最低位 = 静态/动态标志(0=动态,1=静态) │
│ • 其余位 = 对象唯一标识符 │
│ │
│ ReplicationSystemId 字段: │
│ • 0 = 无效/未分配 │
│ • 1-15 = 有效的 PIE 实例 ID(实际值 = 存储值 - 1) │
│ │
│ 示例: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ PIE 实例 0 的对象:Id=12345, RepSysId=1 (存储值) │ │
│ │ PIE 实例 1 的对象:Id=12345, RepSysId=2 (存储值) │ │
│ │ PIE 实例 2 的对象:Id=12345, RepSysId=3 (存储值) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ 相同 Id 的对象在不同 PIE 实例中是完全独立的! │
│ │
└────────────────────────────────────────────────────────────────────┘🔒 16.4.3 实例隔离机制
Iris 确保不同 PIE 实例之间的数据完全隔离:
CPP
// 源码位置:ReplicationSystem.cpp// 静态数组存储所有 ReplicationSystem 实例
UReplicationSystem* FReplicationSystemFactory::ReplicationSystems[MaxReplicationSystemCount];
// 最大支持 16 个 PIE 实例(4 位 = 2^4 = 16)constexpr uint32 MaxReplicationSystemCount = 16;
// 获取指定 ID 的 ReplicationSystemUReplicationSystem* GetReplicationSystem(uint32 ReplicationSystemId){
if (ReplicationSystemId < MaxReplicationSystemCount)
{
return ReplicationSystems[ReplicationSystemId];
}
return nullptr;
}CPP
// 源码位置:ReplicationSystem.hclass UReplicationSystem
{
public:
// 获取当前实例的 PIE ID
int32 GetPIEInstanceID() const { return PIEInstanceID; }
// 设置 PIE ID(内部使用)
void SetPIEInstanceID(int32 InPIEInstanceID) { PIEInstanceID = InPIEInstanceID; }
private:
// 系统唯一 ID
uint32 Id;
// PIE 实例 ID
int32 PIEInstanceID;
};🐛 16.4.4 PIE 调试注意事项
PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ ⚠️ PIE 调试常见问题 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 问题 1:对象在错误的实例中查找 │
│ ─────────────────────────────── │
│ 症状:调试时找不到对象,或找到错误的对象 │
│ 原因:使用了不完整的 Handle(缺少 ReplicationSystemId) │
│ 解决:使用 IsCompleteHandle() 检查,确保 Handle 包含系统 ID │
│ │
│ 问题 2:断点在所有实例中触发 │
│ ─────────────────────────────── │
│ 症状:设置断点后,每个 PIE 实例都会触发 │
│ 原因:所有实例共享同一份代码 │
│ 解决:使用条件断点,检查 ReplicationSystemId │
│ │
│ 问题 3:日志混乱 │
│ ─────────────────────────────── │
│ 症状:日志来自多个实例,难以区分 │
│ 原因:所有实例共享同一个日志输出 │
│ 解决:日志中包含 ReplicationSystemId,如: │
│ [RepSys:0] Server log message │
│ [RepSys:1] Client 1 log message │
│ │
│ 调试技巧: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ // 条件断点示例(只在服务器实例中断) │ │
│ │ ReplicationSystem->GetPIEInstanceID() == 0 │ │
│ │ │ │
│ │ // 日志中包含实例 ID │ │
│ │ UE_LOG(LogIris, Log, │ │
│ │ TEXT("[RepSys:%d] Object created: %s"), │ │
│ │ ReplicationSystemId, *ObjectName); │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘🔧 16.4.5 PIE 调试命令
CPP
// 使用调试命令时指定实例 ID// 源码位置:IrisDebugging.h
// 输出指定实例的对象状态extern "C" IRISCORE_API void DebugOutputNetObjectState(
uint64 NetRefHandleId,
uint32 ReplicationSystemId // 指定 PIE 实例
);
// 获取指定实例的调试信息extern "C" IRISCORE_API FNetReplicatedObjectDebugInfo DebugNetObjectById(
UObject* Instance,
uint32 ReplicationSystemId // 指定 PIE 实例
);
// 获取指定实例的 ReplicationSystemextern "C" IRISCORE_API UReplicationSystem* GetReplicationSystemForDebug(uint32 Id);控制台命令示例:
PLAINTEXT
; 打印实例 0(服务器)的所有复制对象
Net.Iris.PrintReplicatedObjects 0
; 打印实例 1(客户端 1)的连接信息
Net.Iris.PrintConnectionInfo 1
; 打印所有实例的系统信息
Net.Iris.PrintSystemInfo📊 16.5 完整配置示例
🎮 16.5.1 FPS 射击游戏配置
INI
; Config/DefaultEngine.ini - FPS 射击游戏优化配置
;=============================================================================; Iris 基础配置;=============================================================================[/Script/Engine.Engine]net.Iris.UseIrisReplication=1
;=============================================================================; 对象复制桥接配置;=============================================================================[/Script/IrisCore.ObjectReplicationBridgeConfig]
; 默认空间过滤器DefaultSpatialFilterName=Spatial
; --- 轮询频率配置 ---; 玩家角色:每帧轮询(最高响应)
+PollConfigs=(ClassName=/Script/Engine.Character, PollFrequency=0.0, bIncludeSubclasses=true)
; 投射物:每帧轮询(需要精确位置)
+PollConfigs=(ClassName=/Script/MyFPS.FPSProjectile, PollFrequency=0.0, bIncludeSubclasses=true)
; 武器:每秒 30 次(中等频率)
+PollConfigs=(ClassName=/Script/MyFPS.FPSWeapon, PollFrequency=30.0, bIncludeSubclasses=true)
; 可拾取物品:每秒 5 次(低频率)
+PollConfigs=(ClassName=/Script/MyFPS.FPSPickup, PollFrequency=5.0, bIncludeSubclasses=true)
; --- 过滤器配置 ---; 玩家使用空间过滤,带滞后
+FilterConfigs=(ClassName=/Script/Engine.Character, DynamicFilterName=Spatial, FilterProfile=CharacterProfile)
; 投射物使用空间过滤,无滞后(快速消失)
+FilterConfigs=(ClassName=/Script/MyFPS.FPSProjectile, DynamicFilterName=Spatial, FilterProfile=ProjectileProfile)
; 武器跟随拥有者
+FilterConfigs=(ClassName=/Script/MyFPS.FPSWeapon, DynamicFilterName=Connection)
; --- 优先级器配置 ---; 玩家使用视野优先级器
+PrioritizerConfigs=(ClassName=/Script/Engine.Character, PrioritizerName=FieldOfView)
; 投射物使用球形优先级器
+PrioritizerConfigs=(ClassName=/Script/MyFPS.FPSProjectile, PrioritizerName=Sphere)
; --- 增量压缩配置 ---; 角色启用增量压缩
+DeltaCompressionConfigs=(ClassName=/Script/Engine.Character, bEnableDeltaCompression=true)
;=============================================================================; 空间网格过滤配置;=============================================================================[/Script/IrisCore.NetObjectGridFilterConfig]; 小地图,较小的网格CellSizeX=5000.0CellSizeY=5000.0
; 较短的裁剪距离(室内地图)DefaultCullDistance=10000.0
; 使用精确距离bUseExactCullDistance=true
; 快速裁剪(FPS 需要快速响应)DefaultFrameCountBeforeCulling=2
; 角色配置档案
+FilterProfiles=(FilterProfileName=CharacterProfile, FrameCountBeforeCulling=8)
; 投射物配置档案(快速消失)
+FilterProfiles=(FilterProfileName=ProjectileProfile, FrameCountBeforeCulling=1)
;=============================================================================; 过滤滞后配置;=============================================================================[/Script/IrisCore.ReplicationFilteringConfig]bEnableObjectScopeHysteresis=trueDefaultHysteresisFrameCount=4HysteresisUpdateConnectionThrottling=2🌍 16.5.2 开放世界 RPG 配置
INI
; Config/DefaultEngine.ini - 开放世界 RPG 优化配置
;=============================================================================; Iris 基础配置;=============================================================================[/Script/Engine.Engine]net.Iris.UseIrisReplication=1
;=============================================================================; 对象复制桥接配置;=============================================================================[/Script/IrisCore.ObjectReplicationBridgeConfig]
DefaultSpatialFilterName=Spatial
; --- 轮询频率配置 ---; 玩家:每帧
+PollConfigs=(ClassName=/Script/MyRPG.RPGPlayerCharacter, PollFrequency=0.0, bIncludeSubclasses=true)
; 战斗中的 NPC:每秒 15 次
+PollConfigs=(ClassName=/Script/MyRPG.RPGCombatNPC, PollFrequency=15.0, bIncludeSubclasses=true)
; 普通 NPC:每秒 5 次
+PollConfigs=(ClassName=/Script/MyRPG.RPGNPC, PollFrequency=5.0, bIncludeSubclasses=true)
; 环境物体:每秒 1 次
+PollConfigs=(ClassName=/Script/MyRPG.RPGEnvironmentActor, PollFrequency=1.0, bIncludeSubclasses=true)
; 远处的装饰物:每秒 0.5 次
+PollConfigs=(ClassName=/Script/MyRPG.RPGDecoration, PollFrequency=0.5, bIncludeSubclasses=true)
; --- 过滤器配置 ---; 玩家使用空间过滤,长滞后
+FilterConfigs=(ClassName=/Script/MyRPG.RPGPlayerCharacter, DynamicFilterName=Spatial, FilterProfile=PlayerProfile)
; NPC 使用空间过滤,中等滞后
+FilterConfigs=(ClassName=/Script/MyRPG.RPGNPC, DynamicFilterName=Spatial, FilterProfile=NPCProfile)
; 环境物体使用空间过滤,短滞后
+FilterConfigs=(ClassName=/Script/MyRPG.RPGEnvironmentActor, DynamicFilterName=Spatial, FilterProfile=EnvironmentProfile)
; --- 优先级器配置 ---; 玩家使用球形优先级器(带所有者加成)
+PrioritizerConfigs=(ClassName=/Script/MyRPG.RPGPlayerCharacter, PrioritizerName=SphereWithOwnerBoost)
; NPC 使用默认优先级器
+PrioritizerConfigs=(ClassName=/Script/MyRPG.RPGNPC, PrioritizerName=Default)
; --- 增量压缩配置 ---; 所有角色启用增量压缩
+DeltaCompressionConfigs=(ClassName=/Script/Engine.Character, bEnableDeltaCompression=true)
; 环境物体禁用(属性少)
+DeltaCompressionConfigs=(ClassName=/Script/MyRPG.RPGEnvironmentActor, bEnableDeltaCompression=false)
;=============================================================================; 空间网格过滤配置;=============================================================================[/Script/IrisCore.NetObjectGridFilterConfig]; 大地图,较大的网格CellSizeX=50000.0CellSizeY=50000.0
; 较长的裁剪距离(开放世界)DefaultCullDistance=50000.0
; 最大裁剪距离限制MaxCullDistance=100000.0
; 使用精确距离bUseExactCullDistance=true
; 较长的裁剪延迟(避免频繁创建/销毁)DefaultFrameCountBeforeCulling=30
; 玩家配置档案(长滞后)
+FilterProfiles=(FilterProfileName=PlayerProfile, FrameCountBeforeCulling=60)
; NPC 配置档案
+FilterProfiles=(FilterProfileName=NPCProfile, FrameCountBeforeCulling=30)
; 环境配置档案
+FilterProfiles=(FilterProfileName=EnvironmentProfile, FrameCountBeforeCulling=10)
;=============================================================================; 过滤滞后配置;=============================================================================[/Script/IrisCore.ReplicationFilteringConfig]bEnableObjectScopeHysteresis=trueDefaultHysteresisFrameCount=15HysteresisUpdateConnectionThrottling=4🏎️ 16.5.3 竞速游戏配置
INI
; Config/DefaultEngine.ini - 竞速游戏优化配置
;=============================================================================; Iris 基础配置;=============================================================================[/Script/Engine.Engine]net.Iris.UseIrisReplication=1
;=============================================================================; 对象复制桥接配置;=============================================================================[/Script/IrisCore.ObjectReplicationBridgeConfig]
DefaultSpatialFilterName=Spatial
; --- 轮询频率配置 ---; 所有车辆:每帧轮询(高速移动需要精确)
+PollConfigs=(ClassName=/Script/MyRacing.RacingVehicle, PollFrequency=0.0, bIncludeSubclasses=true)
; 道具:每秒 10 次
+PollConfigs=(ClassName=/Script/MyRacing.RacingPowerUp, PollFrequency=10.0, bIncludeSubclasses=true)
; 赛道物体:每秒 2 次
+PollConfigs=(ClassName=/Script/MyRacing.RacingTrackObject, PollFrequency=2.0, bIncludeSubclasses=true)
; --- 过滤器配置 ---; 车辆使用空间过滤,极短滞后
+FilterConfigs=(ClassName=/Script/MyRacing.RacingVehicle, DynamicFilterName=Spatial, FilterProfile=VehicleProfile)
; 道具使用空间过滤
+FilterConfigs=(ClassName=/Script/MyRacing.RacingPowerUp, DynamicFilterName=Spatial, FilterProfile=PowerUpProfile)
; --- 优先级器配置 ---; 车辆使用球形优先级器
+PrioritizerConfigs=(ClassName=/Script/MyRacing.RacingVehicle, PrioritizerName=Sphere)
; --- 增量压缩配置 ---; 车辆启用增量压缩
+DeltaCompressionConfigs=(ClassName=/Script/MyRacing.RacingVehicle, bEnableDeltaCompression=true)
;=============================================================================; 空间网格过滤配置;=============================================================================[/Script/IrisCore.NetObjectGridFilterConfig]; 赛道形状,矩形网格CellSizeX=20000.0CellSizeY=20000.0
; 较长的裁剪距离(高速移动)DefaultCullDistance=30000.0
; 使用精确距离bUseExactCullDistance=true
; 极短的裁剪延迟(快速响应)DefaultFrameCountBeforeCulling=2
; 车辆配置档案
+FilterProfiles=(FilterProfileName=VehicleProfile, FrameCountBeforeCulling=4)
; 道具配置档案
+FilterProfiles=(FilterProfileName=PowerUpProfile, FrameCountBeforeCulling=2)
;=============================================================================; 过滤滞后配置;=============================================================================[/Script/IrisCore.ReplicationFilteringConfig]bEnableObjectScopeHysteresis=trueDefaultHysteresisFrameCount=2HysteresisUpdateConnectionThrottling=1📋 16.6 总结与最佳实践
🎯 16.6.1 核心概念回顾
PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 📚 第十六部分核心概念 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ 启用 Iris │
│ ───────────── │
│ • 命令行参数:-UseIrisReplication=1(最高优先级) │
│ • 控制台变量:net.Iris.UseIrisReplication(运行时可改) │
│ • 配置文件:DefaultEngine.ini(持久化设置) │
│ │
│ 2️⃣ 配置文件体系 │
│ ───────────────── │
│ • UObjectReplicationBridgeConfig:核心配置 │
│ • UNetObjectGridFilterConfig:空间过滤配置 │
│ • UReplicationFilteringConfig:滞后过滤配置 │
│ • UReplicationStateDescriptorConfig:序列化配置 │
│ │
│ 3️⃣ NetDriver 集成 │
│ ───────────────── │
│ • ReplicationSystem 创建流程 │
│ • 与传统系统的切换 │
│ • 迁移策略 │
│ │
│ 4️⃣ PIE 多实例支持 │
│ ───────────────── │
│ • ReplicationSystemId 隔离机制 │
│ • FNetRefHandle 位布局 │
│ • 调试注意事项 │
│ │
└────────────────────────────────────────────────────────────────────┘✅ 16.6.2 最佳实践清单
PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ ✅ Iris 配置最佳实践 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 启用阶段 │
│ ──────── │
│ ✅ 先在开发环境测试,再部署到生产环境 │
│ ✅ 使用命令行参数进行快速测试 │
│ ✅ 确保团队所有成员使用相同的配置 │
│ ✅ 保留传统系统作为回退方案 │
│ │
│ 轮询配置 │
│ ──────── │
│ ✅ 重要对象(玩家、敌人)使用高频轮询 │
│ ✅ 次要对象(环境、装饰)使用低频轮询 │
│ ✅ 考虑使用 Push Model 减少轮询开销 │
│ ✅ 监控 CPU 使用,避免过度轮询 │
│ │
│ 过滤配置 │
│ ──────── │
│ ✅ 根据游戏类型选择合适的网格大小 │
│ ✅ 设置合理的滞后帧数,避免闪烁 │
│ ✅ 为不同类型的对象使用不同的配置档案 │
│ ✅ 测试边界情况(对象在边界来回移动) │
│ │
│ 优先级配置 │
│ ──────── │
│ ✅ 玩家视野内的对象优先级更高 │
│ ✅ 拥有者的对象优先级更高 │
│ ✅ 考虑游戏玩法需求调整优先级 │
│ │
│ 调试阶段 │
│ ──────── │
│ ✅ 使用 PIE 多实例测试多人场景 │
│ ✅ 监控日志,关注 LogIris 类别 │
│ ✅ 使用调试命令检查对象状态 │
│ ✅ 注意 ReplicationSystemId,避免混淆实例 │
│ │
└────────────────────────────────────────────────────────────────────┘📊 16.6.3 配置参数速查表
配置类 | 参数 | 默认值 | 说明 |
|---|---|---|---|
IrisConfig |
| 0 | 启用 Iris 复制系统 |
PollConfig |
| 0.0 | 轮询频率(Hz),0=每帧 |
PollConfig |
| true | 是否包含子类 |
FilterConfig |
| - | 过滤器名称 |
FilterConfig |
| - | 配置档案名称 |
GridFilterConfig |
| 20000.0 | 网格单元大小 |
GridFilterConfig |
| 15000.0 | 默认裁剪距离 |
GridFilterConfig |
| true | 使用精确距离 |
GridFilterConfig |
| 4 | 裁剪前等待帧数 |
FilteringConfig |
| true | 启用滞后过滤 |
FilteringConfig |
| 0 | 默认滞后帧数 |
DeltaCompressionConfig |
| true | 启用增量压缩 |
🔧 16.6.4 常见问题排查
PLAINTEXT
┌────────────────────────────────────────────────────────────────────┐
│ 🔧 常见问题与解决方案 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 问题 1:Iris 没有启用 │
│ ───────────────────── │
│ 症状:网络复制使用传统系统 │
│ 检查: │
│ • 插件是否启用? │
│ • net.Iris.UseIrisReplication 值是否为 1? │
│ • 命令行是否有 -UseIrisReplication=0? │
│ │
│ 问题 2:对象不复制 │
│ ───────────────── │
│ 症状:某些对象在客户端不显示 │
│ 检查: │
│ • 对象是否被过滤器过滤? │
│ • 裁剪距离是否太小? │
│ • 对象是否正确注册到复制系统? │
│ │
│ 问题 3:对象闪烁 │
│ ───────────────── │
│ 症状:对象在边界附近频繁出现/消失 │
│ 解决: │
│ • 增加 DefaultFrameCountBeforeCulling │
│ • 增加 HysteresisFrameCount │
│ • 检查网格大小是否合适 │
│ │
│ 问题 4:性能问题 │
│ ───────────────── │
│ 症状:服务器 CPU 使用过高 │
│ 解决: │
│ • 降低不重要对象的轮询频率 │
│ • 启用 Push Model │
│ • 增加网格单元大小(减少计算量) │
│ • 使用性能分析工具定位瓶颈 │
│ │
│ 问题 5:PIE 调试混乱 │
│ ───────────────── │
│ 症状:日志/断点在多个实例中触发 │
│ 解决: │
│ • 使用条件断点检查 ReplicationSystemId │
│ • 日志中包含实例 ID │
│ • 使用调试命令时指定实例 ID │
│ │
└────────────────────────────────────────────────────────────────────┘📚 延伸阅读
第五部分:过滤系统详解 → 深入了解各种过滤器的实现
第六部分:优先级系统详解 → 深入了解各种优先级器的实现
第十一部分:轮询与脏数据检测 → 了解 Push Model 的使用
第十五部分:调试与性能分析 → 学习如何诊断 Iris 问题
本文档基于 Unreal Engine 5.5.0 Iris 源代码分析(源码目录:Engine/Source/Runtime/Experimental/Iris/)
