【技术探索】Lyra 框架拆解(七):网络同步框架 —— Iris + ReplicationGraph + FastShared 路径
系列导航
| 篇 | 主题 |
|---|---|
| 一 | 架构哲学总览 |
| 二 | Experience 系统 |
| 三 | Character 组件体系 |
| 四 | GAS 集成层 |
| 五 | Equipment & Inventory |
| 六 | Input 系统 |
| 七 | 网络同步框架(本文) |
| 八 | UI 系统 |
一句话
Lyra 的网络同步架构不是”用一套方案解决所有问题”——它构建了四层分层协作体系,从底层的 Iris 序列化到上层的 VerbMessage 消息广播,每一层解决一个粒度的同步需求。一个值得注意的设计决策:ReplicationGraph 默认禁用——先跑起来,性能瓶颈出现时再逐层启用优化。
一,五层同步架构
1 | Gameplay 层: UPROPERTY(Replicated) / FFastArray / RPC |
核心原则:每一层可以独立替换或禁用。 这是 Lyra 最务实的网络设计决策——不是”给你一套完美但必须全用的系统”,而是”给你一套可逐层启用的工具集”。
二,Iris 与 ReplicationGraph 的分工
Iris:下一代复制系统
Iris 是 UE5 新的底层复制框架(替代旧的 UNetDriver 复制管线),在 LyraGame.Build.cs 中通过一行宏启用:
1 | SetupIrisSupport(Target); |
Iris 负责怎么复制:序列化格式选择、过滤算法、优先级计算、Delta 压缩、带宽分配。通过 ObjectReplicationBridgeConfig 按类指定过滤策略:
1 | // 按类指定过滤方式 |
ReplicationGraph:自定义节点体系
ReplicationGraph 负责复制什么:哪些 Actor 对哪些连接是相关的。它定义了 6 种节点类型:
| 节点 | Actor 类型 | 策略 |
|---|---|---|
| GridSpatialization2D | 大部分 Gameplay Actor | 空间网格——只复制到”附近”的连接 |
| AlwaysRelevant_ForConnection | PlayerController + ViewTarget | 始终复制到拥有者 |
| PlayerStateFrequencyLimiter | PlayerState | 限制更新频率,降低带宽 |
| ActorList | ALyraCharacter | 独立列表,配合 FastShared 路径使用 |
| TearOff | 销毁的 Actor | 延迟清理,确保客户端收到销毁前的最后状态 |
| FrequencyBuckets | 低优先级 Actor | 分频复制——远距离 Actor 以更低频率更新 |
关键设计:ReplicationGraph 默认禁用
1 | bDisableReplicationGraph = true; // 默认不走 ReplicationGraph |
当 ReplicationGraph 禁用时,Iris 的 ObjectReplicationBridgeConfig 接管相关性过滤。这是 Lyra 的”先跑起来,优化再说”哲学的体现——小项目用 Iris 默认过滤足够,大项目再接入 ReplicationGraph 做精细路由。
三,FastShared 路径:独立带宽 + 共享序列化
ReplicationGraph 一个重要优化是 FastShared 路径——专门为移动数据设计:
1 | ALyraCharacter 移动数据: |
ALyraCharacter 通过 FLyraReplicatedAcceleration 结构体走这条路径——加速度数据从 12 字节压缩到 3 字节(75% 节省),且所有观察同一个角色的客户端共享同一份序列化结果。
这对大逃杀/Battle Royale 场景至关重要——一个角色可能被 30+ 个客户端同时观察,每条连接单独复制移动数据会吃掉所有上行带宽。
四,FSharedRepMovement:移动压缩详解
传统的 FRepMovement 结构通过 ReplicatedMovement 复制——包含 Location(12 bytes) + Rotation(12 bytes) + Velocity(12 bytes) + 额外数据 ≈ 50+ bytes。
Lyra 的 FSharedRepMovement 使用以下优化:
- 量化位置:世界坐标通过网格对齐降低精度 → 减少 bit 数
- 加速度压缩:
FLyraReplicatedAcceleration从 12 bytes → 3 bytes(量化为 256 级方向 + 精度缩放) - Delta 压缩:只发送相对于上一帧的变化,而非完整值
- 独立带宽预算:移动复制使用独立的带宽限制(而非与其他属性竞争)
五,FLyraVerbMessage:轻量事件广播
传统 RPC 是”一对一”(Server→OwningClient)或”一对多”(NetMulticast),但有些游戏事件需要更灵活的订阅模型:
1 | 击杀事件(Elimination.Message): |
FLyraVerbMessage 提供了一条双通道架构:
- FFastArray 复制通道:
FVerbMessageArray使用FFastArraySerializer→ 服务器上有新消息时增量复制到所有客户端 - GameplayMessageSubsystem 分发通道:客户端收到消息后通过
UGameplayMessageSubsystem广播 → 任何感兴趣的 Subsystem 自行订阅
消息的生产者和消费者完全解耦——ShooterCore 的 Elimination 消息可以被 ShooterExplorer 的 UI 订阅,双方互不知道对方存在。这是 Lyra 在 Gameplay 层实现的最大粒度的模块化通信。
六,FFastArraySerializer:结构化数据复制的基石
Lyra 中几乎所有的结构化列表数据都使用 FFastArraySerializer:
| 使用处 | 数据结构 | 特点 |
|---|---|---|
| Inventory | FLyraInventoryList |
增量更新 + SubObject 注册 |
| Equipment | FLyraEquipmentList |
装备列表的原子增删 |
| VerbMessage | FVerbMessageArray |
消息队列的自动回收 |
| GameplayTag Stack | Tag 堆叠计数 | StackCount 变化即复制 |
FFastArraySerializer 的核心优势:
- 增量复制:只复制变化了的 Entry,不复制整个数组
- 客户端回调:
PreReplicatedRemove+PostReplicatedAdd+PostReplicatedChange——客户端在数组变化时收到明确通知 - SubObject 支持:通过
RegisteredSubObject实现嵌套 UObject 的复制依赖
七,SignificanceManager:预留的优化层
ULyraSignificanceManager 已经注册在 DefaultEngine.ini 中,但 RegisterObject() 的实现被注释掉了(代码中有 @TODO 标记)。它的理论协作路径是:
1 | ReplicationGraph::GetSignificance(Obj) |
Lyra 的设计风格:基建先到位(类已创建、已注册),调优留给项目开发者。”你知道有这个优化点,框架已经给你留好了位置”——比”框架没考虑这个场景,你需要从头实现”好得多。
下一篇(终篇):UI 系统 — 拆解 Lyra 的四层 UI 架构:CommonUI → CommonGame → UIExtension → LyraGame。以及 GameplayTag 驱动的发布/订阅式 UI 注入如何实现”GameFeature 插件的 UI 完全解耦”。