5.3 数据同步的分层设计:什么走复制、什么走 RPC、什么走外围服务
项目层面的数据流设计,比「某个属性怎么复制」更影响架构健康。四层模型供参考:
四层模型
| 层 | 内容 | 通道 | 频率 |
|---|---|---|---|
| L0 实时核心状态 | 位置、朝向、移动模式 | 属性复制 + 客户端预测 | 30~60Hz |
| L1 游戏逻辑状态 | 血量、Buff、技能、装备、库存 | 属性复制 / FastArray | 事件驱动 |
| L2 一次性事件 | 受击表现、特效、提示 | RPC(Client/Multicast) | 事件驱动 |
| L3 持久化元数据 | 等级、成就、好友、商城 | 游戏服务器 ↔ Web/DB,不进 UE 复制 | 登录/事件 |
各层的设计要点
L0:只放「必须每帧平滑变化」的数据。这一层的带宽是常驻成本,参数精打细算(旋转可以压缩、速度可由位置差分算)。
L1:事件驱动的状态。这里是最容易臃肿的一层——每加一个系统就想往复制属性里塞。纪律:
- 每个 L1 属性问:「它变化时,所有相关客户端必须立刻知道吗?」不是 → 改成本地计算/聚合查询。
- 用 FastArray 处理列表、用条件复制(OwnerOnly/SimulatedOnly)收窄广播面。
L2:纯表现事件。原则是事件幂等 + 不携带逻辑后果——就算这层全部丢失,游戏逻辑仍然正确。
L3:与 UE 复制完全解耦。玩家等级、商城购买这些走 HTTP/WebSocket 到你的后端服务,UE 网络只负责「对局内」。这条路的好处:
- 对局服务器重启/换服不影响元数据
- Web 服务可独立扩展(Web 团队并行开发)
- 对局内复制的属性集保持精简
桥接接口:L3 到 L1/L2 的边界要清晰——玩家进入对局时,GameMode 从 Web 拉一次档案权威数据(L3→L1)写进 PlayerState;对局结束时,把结算数据推回 Web(L1→L3)。其他时候两层互不相干。
反面案例:全都搓进复制层
某项目把「玩家成就完成进度」「好友在线状态」「商城限时折扣」都做成了复制属性——结果:
- 对局内带宽被无关状态占去 30%
- 成就逻辑混在对局代码里,网页版改活动要对局热更
- 每次加运营活动都要改网络代码
拆解后:L3 全走 Web,对局内只在入口/出口同步一次,网络层代码量减少 40%,运营改活动不动引擎。
规范模板:新同步需求的决策树
新数据/新功能
├─ 每帧变 + 影响表现? → L0(复制+预测/平滑)
├─ 事件变 + 多端共享? → L1(复制属性+OnRep)
├─ 一次性 + 纯表现? → L2(RPC)
└─ 与对局无关的元数据? → L3(不进复制,走 Web)
心得:网络层的代码是「一旦写进去就很难拆出来」的类型——因为同步机制渗进每个系统的细节。分层模型的意义在于「新功能先归层,再动手」,避免架构被无数个临时决定侵蚀。
