大世界复制的带宽大头不是动态 actor,而是大量存在但几乎不变的东西:摆好的箱子、装饰物、关卡几何。这类 actor 每 tick 都过一遍比较、序列化、发送,纯属浪费——它的客户端状态五分钟内都不会变。DORM 机制就是给这类 actor 的 channel 装一个开关。基于 UE5.3 源码(Actor.cpp、NetDriver.cpp)梳理。
EDormancy 五档的精确语义
ENetDormancy 是个枚举,但档与档之间的差别比直觉细:
| 档位 | 语义 |
|---|---|
DORM_Never |
永不休眠,默认值。每个复制 tick 都全量走。 |
DORM_Awake |
可以休眠,但当前醒着。 |
DORM_DormantAll |
全休眠:本 actor 的所有属性都不复制了。 |
DORM_DormantPartial |
部分休眠:有一小撮属性(按条件白名单)仍然复制。 |
DORM_Initial |
初始即休眠:actor 刚复制完初始状态就立即休眠。 |
关键区分在 DormantAll 和 DormantPartial:前者一刀切,适合"摆好就永远不动"的纯装饰;后者配合 AActor::GetDormancy 的重写——你可以在特定条件下临时返回 DORM_Awake,让个别属性(比如被交互状态)继续同步,其余时间休眠。5.x 里 DormantPartial 常与 Push Model 搭配:用 IRepChangedPropertyTracker 精确控制哪些属性在休眠期仍可标记 dirty。
DORM_Initial 是最容易用错的档。它跳过了"复制一轮再睡"的步骤——actor 的初始状态复制完立刻休眠。前提是你的 actor 初始状态之后真的没有任何要同步的东西,包括动态生成的 component。用错的表现是:客户端 spawned 出来的 actor 永远缺后续状态。
状态机:谁负责唤醒
dormant 状态下 actor 的 channel 进入"只监听"模式:ServerReplicateActors 的 gather 阶段直接跳过 dormant actor 的比较和序列化,channel 本身还开着(bunch 还能进来),只是不主动发。要让它重新说话,两条路:
显式唤醒:调 AActor::FlushNetDormancy()。这会把 actor 标回 awake,下个复制 tick 走完整一轮比较把积累的变化全发出去,然后按当前 GetDormancy() 的返回值决定是重新睡下还是保持醒着。语义上它是"我现在有一波状态要同步,同步完继续睡"。
隐式唤醒:5.x 里 Push Model 的 MarkPropertyDirty 配合 dormant actor 时,引擎会自动 flush 对应的 channel——因为属性被标记 dirty 就意味着它要同步,和 dormant 语义冲突,引擎选择尊重 dirty 标记。这让 DormantPartial 的实际使用体验好很多:不用手动管唤醒,标 dirty 就行。
带宽收益的量级
账很好算。1000 个静态 actor,每个 10 个复制属性,属性平均 4 字节:醒着时每个 actor 每 tick(NetUpdateFrequency 默认 100)最坏 400 字节带宽;全 dormant 后是 0,只有 Flush 时的尖峰。对 50 个连接的专服,这是 $1000 \times 50 \times 100 \times 400\text{B} = 2\text{GB/s}$ 到接近 0 的差别——当然真实场景 relevancy 会先砍掉远距离的,但中距离"可见但静止"的 actor 全靠 dormancy 兜。
这也是为什么 UE 把 DORM_Initial 设为 AStaticMeshActor 的默认值:摆进关卡的静态网格几乎不需要后续同步。
实操坑位清单
- FlushNetDormancy 别在 Tick 里无脑调。每帧 Flush 等于 DORM_Never,还白付一轮状态切换。正确姿势是"状态变化事件里 Flush 一次"。
- 客户端不会自己知道 dormancy 变化。actor 睡下后客户端那边的
OnRep自然不触发,如果客户端逻辑依赖"它睡了我就能干某件事"(比如离线渲染优化),要自己处理时序。 bNetLoadOnClient和 dormancy 正交:前者管 actor 是否随关卡加载到客户端,后者管加载后的复制行为,别混。- 验证 dormancy 是否生效:开
LogNetTraffic或stat net,对比 dormancy 全开的场景带宽基线; spike 时看 Flush 频率是不是过高。
DORM 的设计哲学一句话概括:用"状态不变"这个先验换带宽。它和 relevancy(空间维度砍连接)、Push Model(时间维度砍比较)是正交的三层优化,大世界项目三层都要上。