动态组件:NetMode不等于Actor的本地角色
自动关联目录:动态组件:NetMode不等于Actor的本地角色
核心判断:
NetMode描述这个 World 运行在哪类进程,LocalRole描述这个 Actor 在本机的网络角色,IsLocallyControlled描述控制权。动态组件在哪创建、是否复制、由谁初始化,必须分别回答,不能用一个条件代替三者。
一、业务冲突:同一个 Actor 在不同端需要不同能力
角色、建筑和可攻击物会按玩法配置追加属性、受击、输入、表现或辅助组件。DS 只需要权威逻辑;拥有客户端需要输入与预测;其他客户端只需要表现;某些组件由服务端创建并复制,另一些则应在每个渲染客户端本地创建。
若把全部组件固化在基类,DS 会承担表现依赖,轻量玩法也会加载无关能力;运行时按配置装配是合理方向,但组件分发本质上是一张网络执行矩阵。
二、实际实现:注册顺序正确,角色筛选混在一个枚举里
实际构建器从组件 CDO 读取创建条件,在 Actor 的 PostInitializeComponents 阶段执行。满足条件后,以 Actor 为 Outer 创建组件,先设置 SetIsReplicated,再 RegisterComponent;随后 Actor 的业务 BeginPlay 会重新枚举组件并调用自定义初始化。
值得保留的有三点:
- 动态组件在业务 BeginPlay 前创建,后续组件枚举能看到完整集合;
- 复制开关在注册前确定,避免组件先以错误模式注册再修改;
- 是否创建由组件 CDO 声明,业务 Actor 不需要硬编码每个具体类型。
问题出在条件语义。一条“仅自主端”分支实际只判断 Standalone,没有检查网络 Client 上该 Pawn 是否本地控制;“所有客户端”通过“不是 Dedicated Server”实现,因此也包含 Standalone 和 Listen Server;“多端但不复制”则在所有实例创建,包括 DS 与模拟代理。这里把进程类型、Actor 角色和能力目的压进了同一个枚举。
三、失败机制:看起来都是 Client,实际所有权完全不同
在 Listen Server 中,同一进程既是 Authority,又有本地渲染与输入;在普通 Client 中,本地 Pawn 是 AutonomousProxy,其他 Pawn 是 SimulatedProxy。仅检查 NetMode != DedicatedServer 无法区分拥有者和旁观者,仅检查 ROLE_Authority 又无法区分 DS 与 Listen Server 的表现需求。
装配还只在创建阶段求值一次。Pawn 被重新 Possess、Owner 迁移或进入回放后,原来的 Desired Set 可能已经变化。公开的 CreateAllComponents 若被再次调用,也没有按稳定键检查已有组件;正常 PostInitialize 只调用一次时没问题,但接口本身并不幂等。
四、目标方案:执行域和复制模式正交
enum class EExecutionDomain {
Authority,
LocallyControlled,
RenderingClient,
EveryInstance
};
enum class EComponentReplication {
None,
ReplicatedFromAuthority
};
struct FRuntimeComponentSpec {
FName StableKey;
TSubclassOf<UActorComponent> Class;
TSet<EExecutionDomain> Domains;
EComponentReplication Replication;
};
FExecutionContext MakeContext(const AActor& Actor) {
return {
.NetMode = Actor.GetNetMode(),
.bAuthority = Actor.HasAuthority(),
.bLocallyControlled = IsLocallyControlled(Actor),
.bCanRender = Actor.GetNetMode() != NM_DedicatedServer
};
}构建器先把所有 Spec 归约为 Desired Set,再与 Actor 当前由构建器管理的组件做 Diff:创建缺失项、保留相同项、销毁不再需要的项。StableKey 而不是类名充当身份,允许同类组件承担不同槽位,也让重复调用保持幂等。
复制组件只能由 Authority 创建,客户端等待 Actor Channel 创建对应子对象,不能再本地创建一个同类镜像;纯客户端组件则在每个满足执行域的端本地创建,不进入复制列表。SceneComponent 还要在 Register 前建立 Attachment;所有组件完成后再统一开放业务初始化。
五、验证矩阵
| 运行位置 | Authority | Local Control | Rendering | 典型组件 |
|---|---|---|---|---|
| Standalone | 是 | 是 | 是 | 权威、输入、表现 |
| Dedicated Server | 是 | 否 | 否 | 权威逻辑 |
| Listen Server 本地 Pawn | 是 | 是 | 是 | 权威、输入、表现 |
| Client 自己的 Pawn | 否 | 是 | 是 | 输入、预测、表现 |
| Client 其他 Pawn | 否 | 否 | 是 | 表现 |
自动化应逐格验证组件集合、复制标志、初始化次数和销毁次数,并覆盖重复 Reconcile、Possess 切换、Actor 回池和回放。完成条件是每个 StableKey 最多一个组件,客户端不会同时持有本地镜像与复制实例,角色变化后 Desired Set 能收敛。
六、反思
现有实现选择 CDO 配置、在 PostInitialize 阶段注册、先设置复制标志,这些都是正确的 UE 工程实践。真正需要升级的是“创建条件”这层抽象:进程能力、Actor 权威、玩家控制和复制来源是四个维度,压成一个枚举后,分支越补越难证明。