总览
Add Components 是 Game Features 最常用的 Action。它让一个玩法包能给已有 Actor 动态添加组件。比如武器包给 Pawn 加瞄准辅助组件,活动包给 PlayerState 加积分组件,比赛模式包给 GameState 加规则组件。
三方配合
| 角色 | 负责 |
|---|---|
UGameFeatureAction_AddComponents | 在玩法包激活时提交“给某类 Actor 加某组件”的请求 |
UGameFrameworkComponentManager | 保存请求、匹配 Receiver、创建/销毁组件 |
| 目标 Actor | 调用 AddReceiver/RemoveReceiver,声明自己愿意被扩展 |
如果目标 Actor 不 AddReceiver,Action 配得再对也不会生效。这是最常见的坑。
目标 Actor 接入模板
void AMyPlayerState::BeginPlay()
{
Super::BeginPlay();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(
this,
UGameFrameworkComponentManager::NAME_GameActorReady);
}
void AMyPlayerState::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
项目里建议把这段放到统一基类:AModularPlayerController、AModularCharacter、AModularPlayerState、AModularGameState。Lyra 也有 ModularGameplayActors 插件作为参考。
AddComponents 配置表
| 字段 | 说明 |
|---|---|
ActorClass | 要扩展的 Actor 基类 |
ComponentClass | 要添加的组件类 |
bClientComponent | 是否在客户端世界添加 |
bServerComponent | 是否在服务器世界添加 |
AdditionFlags | AddUnique、AddIfNotChild、UseAutoGeneratedName |
AddUnique 通常应该打开,避免多个插件或重复激活加出重复组件。UseAutoGeneratedName 用于避免类名复用导致对象回收行为混乱,但要清楚自己为什么需要。
Extension Event 的作用
Component Manager 不只会加组件,还能发送扩展事件:
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(
Actor,
UGameFrameworkComponentManager::NAME_GameActorReady);
项目自定义 Action 可以监听某类 Actor 的 Extension Event,在 NAME_GameActorReady 后才给它注入输入、UI 或 Ability。这样比在 BeginPlay 里乱猜时机可靠。
使用案例:给 PlayerController 加 UI 入口
活动包激活后,给 AMyPlayerController 添加 UEventEntryComponent。组件 BeginPlay 时不直接创建 UI,而是等 LocalPlayer、HUD RootLayout、Common UI 初始化完成后再注册入口。活动包停用,组件被 Component Manager 销毁,入口也在组件 EndPlay 中移除。
这个设计的好处是:活动 UI 不是主工程硬编码出来的,活动结束时不会残留入口。
架构分析
UGameFeatureAction_AddComponents 激活时遍历当前 WorldContext,并监听后续 GameInstance 启动和世界变化。它根据 NetMode 判断是否应该添加 client/server 组件,然后调用 UGameFrameworkComponentManager::AddComponentRequest。组件请求是引用计数的,多个相同请求不会创建多份;handle 释放后请求减少,最后一个 handle 消失时组件会被移除。
Component Manager 内部有 Receiver、ComponentClassToComponentInstanceMap、ReceiverClassToComponentClassMap、ReceiverClassToEventMap。它不是简单的 GetAllActorsOfClass,而是一个面向运行时扩展的管理器。
常见坑
- 目标 Actor 没有 AddReceiver。
- AddReceiver 放太晚,Game Feature 已激活但 Actor 没收到已有请求。
- RemoveReceiver 忘记调用,编辑器 PIE 多次后 Dump 里有脏 Receiver。
- Client-only 组件在服务器也勾选,浪费资源或触发加载错误。
- 组件 BeginPlay 假设其它组件已准备好,结果初始化顺序不稳定。
源码依据
UGameFrameworkComponentManager 是 UGameInstanceSubsystem。源码注释说明请求会自动作用于已在内存中的 Receiver,也会作用于后续注册的 Receiver;请求通过 FComponentRequestHandle 保活,handle 销毁时移除请求。UGameFeatureAction_AddComponents 在激活时处理已有 WorldContext 和后续 GameInstance,并根据 NetMode 决定 client/server 添加。
源码路径索引
GameFeatures/Public/GameFeatureAction_AddComponents.hGameFeatures/Private/GameFeatureAction_AddComponents.cppModularGameplay/Public/Components/GameFrameworkComponentManager.hModularGameplay/Private/Components/GameFrameworkComponentManager.cppModularGameplay/Public/Components/GameFrameworkComponent.h