总览
第一篇不讲宏大架构,先做一个最小闭环:WeaponPack_Rifle 插件激活时给角色挂一个 URifleFeatureComponent,停用时自动移除。你只要跑通这个闭环,就理解了 Game Features 最核心的价值:玩法不是写死在角色蓝图里,而是作为包被激活。
第一步:启用基础插件
编辑器里启用:
- Game Features
- Modular Gameplay
- Enhanced Input 和 Common UI 如果后续要跟输入/UI 集成
- Gameplay Abilities 如果武器包要给技能
C++ 项目主模块常见依赖:
PrivateDependencyModuleNames.AddRange(new[]
{
"GameFeatures",
"ModularGameplay",
"GameplayTags"
});
第二步:创建 Game Feature 插件
在插件浏览器里创建 Game Feature 插件,名字示例:WeaponPack_Rifle。如果是 C++ 武器包,用带代码模板;如果只是内容和数据,内容插件也可以。模板目录在 UE 源码里是 GameFeatures/Templates/GameFeaturePluginWithCode 和 GameFeaturePluginContentOnly。
.uplugin 建议先写成这样:
{
"FileVersion": 3,
"FriendlyName": "WeaponPack_Rifle",
"Category": "Game Features",
"CanContainContent": true,
"ExplicitlyLoaded": true,
"EnabledByDefault": false,
"BuiltInInitialFeatureState": "Registered",
"Modules": [
{
"Name": "WeaponPack_RifleRuntime",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{ "Name": "GameFeatures", "Enabled": true },
{ "Name": "ModularGameplay", "Enabled": true }
]
}
BuiltInInitialFeatureState=Registered 的意思是项目启动时发现并注册它,但不自动 Load/Active。这样你能用控制台或代码决定什么时候打开这个武器包。Lyra 的 ShooterCore.uplugin 就是类似思路:ExplicitlyLoaded=true、BuiltInInitialFeatureState=Registered。
第三步:准备一个组件
在插件里写组件:
UCLASS(Blueprintable, ClassGroup=(GameFeatures))
class URifleFeatureComponent : public UGameFrameworkComponent
{
GENERATED_BODY()
public:
virtual void BeginPlay() override
{
Super::BeginPlay();
UE_LOG(LogTemp, Log, TEXT("Rifle feature enabled on %s"), *GetNameSafe(GetOwner()));
}
};
这个组件里可以先只做日志。后面再接输入、Ability、UI、装备数据。
第四步:让角色愿意被扩展
Game Feature 不会自动侵入所有 Actor。目标 Actor 必须 opt-in。最简单是在你的 Character 或 Pawn:
void AMyHeroCharacter::BeginPlay()
{
Super::BeginPlay();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(
this,
UGameFrameworkComponentManager::NAME_GameActorReady);
}
void AMyHeroCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
AddReceiver 是关键。源码注释说 Actor 必须通过 AddReceiver/RemoveReceiver 主动接入;否则 Add Components Action 不会魔法般找到它。
第五步:创建 GameFeatureData
在插件内容目录创建 DA_GameFeatureData_Rifle,类型是 GameFeatureData。添加 Action:
| 字段 | 示例 |
|---|---|
| Action | Add Components |
| Actor Class | AMyHeroCharacter |
| Component Class | URifleFeatureComponent |
| Client Component | true |
| Server Component | true |
| Addition Flags | 先用 Add Unique |
如果组件只做 UI 或本地输入,可以只勾 Client。服务器不需要加载纯 UI 组件。
第六步:运行时测试
PIE 后执行:
ListGameFeaturePlugins
LoadGameFeaturePlugin WeaponPack_Rifle
ModularGameplay.DumpGameFrameworkComponentManagers
DeactivateGameFeaturePlugin WeaponPack_Rifle
预期结果:
- Load 后角色身上出现
URifleFeatureComponent。 - Dump 能看到 Receiver、Request、ComponentInstance。
- Deactivate 后组件被移除。
使用案例:Rifle 包最小验收
把这个 Rifle 包当成团队模板:程序只负责 URifleFeatureComponent 和接入 Receiver;策划在 DA_GameFeatureData_Rifle 里配置 ActorClass、ComponentClass、client/server 开关;测试只需要按 Load、Dump、Deactivate 三步验收。等这个闭环稳定后,再把输入、UI、AbilitySet 加进来。
架构分析
UGameFeatureAction_AddComponents::OnGameFeatureActivating 会遍历已有 WorldContext,也会监听后续 GameInstance 启动。它根据 NetMode 判断 client/server,然后调用 UGameFrameworkComponentManager::AddComponentRequest。返回的 FComponentRequestHandle 被 Action 保存;停用时 handle 清空,请求释放,组件也会被移除。
所以你不应该自己在 Action 里 NewObject 组件,也不应该手动维护所有角色列表。让 Component Manager 做引用计数和生命周期。
常见坑
- 只创建 GameFeatureData,却没把它放在插件里可被扫描的位置。
- 角色没有 AddReceiver,Add Components 动作看似配置正确但没有任何效果。
- 组件没有继承
UGameFrameworkComponent也可以被加,但少了常用辅助函数和统一风格。 - Dedicated Server 上勾了 Client-only UI 组件,Cook 和运行时资源都变重。
- Deactivate 后还持有组件指针,下一次激活可能已经是新实例。
源码依据
UGameFeatureData 持有 Actions 数组。UGameFeatureAction_AddComponents 的 FGameFeatureComponentEntry 包含 ActorClass、ComponentClass、Client/Server 开关和 AdditionFlags。UGameFrameworkComponentManager 注释明确:Actor 必须 opt-in 调用 AddReceiver/RemoveReceiver;请求通过 handle 保活,handle 销毁时请求移除。
源码路径索引
GameFeatures/Templates/GameFeaturePluginWithCodeGameFeatures/Public/GameFeatureData.hGameFeatures/Public/GameFeatureAction_AddComponents.hGameFeatures/Private/GameFeatureAction_AddComponents.cppModularGameplay/Public/Components/GameFrameworkComponentManager.h