总览
Game Feature 真正在项目里有用,往往不是只加一个组件,而是一起加输入、UI、技能、数据和提示。Lyra 给了很好的参考:玩法包激活后,可以给玩家加输入映射、给 HUD 加 Widget、给 Actor 授予 Ability/Attribute/AbilitySet。
先分清:Lyra Action 是项目级代码
这些类不在引擎 GameFeatures 插件里,而在 Lyra 项目源码里:
UGameFeatureAction_AddInputContextMappingUGameFeatureAction_AddInputBindingUGameFeatureAction_AddWidgetsUGameFeatureAction_AddAbilitiesUGameFeatureAction_WorldActionBase
你的项目可以直接学习结构,但最好写自己的版本,适配自己的 PlayerController、HUD、ASC 和输入系统。
WorldActionBase 思路
Lyra 抽了一个 GameFeatureAction_WorldActionBase:激活时遍历已有 WorldContext,并监听 GameInstanceStart;停用时解绑。你写“和世界有关”的 Action,都可以沿用这个思路。
void UMyGameFeatureAction_WorldActionBase::OnGameFeatureActivating(FGameFeatureActivatingContext& Context)
{
FDelegateHandle Handle = FWorldDelegates::OnStartGameInstance.AddUObject(
this,
&ThisClass::HandleGameInstanceStart,
FGameFeatureStateChangeContext(Context));
for (const FWorldContext& WorldContext : GEngine->GetWorldContexts())
{
if (Context.ShouldApplyToWorldContext(WorldContext))
{
AddToWorld(WorldContext, Context);
}
}
}
注入 Enhanced Input
输入 Action 一般监听 PlayerController 或 Pawn 的 Extension Event。等 Controller/Pawn ready 后,找到 UEnhancedInputLocalPlayerSubsystem,添加 IMC:
Subsystem->AddMappingContext(InputMappingContext, Priority);
ActiveData.Players.Add(PlayerController, InputMappingContext);
停用时遍历 ActiveData,把 MappingContext 移除。不要只 Add 不 Remove,否则切换玩法包后输入还在。
注入 Common UI
UI Action 推荐让项目有一个统一的 HUD Layout 或 UI Extension Subsystem。Game Feature 只声明“我要把某 Widget 放到哪个槽位”:
| 字段 | 示例 |
|---|---|
| Slot Tag | UI.Layer.HUD.WeaponStatus |
| Widget Class | WBP_RifleAmmoPanel |
| Target Actor | PlayerController/HUD |
| Visibility Policy | Active 时显示,Deactivate 时移除 |
停用时必须移除 Widget 或注销扩展点。否则活动结束后 HUD 还挂着入口。
注入 GAS
Lyra 的 AddAbilities 思路是:对某类 Actor 监听 Extension Event,找到 ASC,然后授予 Ability、AttributeSet、AbilitySet,并保存句柄。
struct FActorExtensions
{
TArray<FGameplayAbilitySpecHandle> Abilities;
TArray<UAttributeSet*> Attributes;
TArray<FMyAbilitySet_GrantedHandles> AbilitySetHandles;
};
停用时用保存的 handle 移除 Ability 和 Attribute。不要按类名粗暴 Clear,因为多个玩法包可能授予同类 Ability。
使用案例:Rifle 武器包
Rifle 包激活后做四件事:
- Add Components:给 Pawn 加
URifleWeaponComponent。 - Add Input Context:给本地玩家加
IMC_Rifle,包含开火、换弹、瞄准。 - Add Widget:在 HUD 武器槽显示
WBP_RifleAmmoPanel。 - Add Abilities:授予
GA_RifleFire、GA_Reload,并加AS_RifleRuntime属性集。
停用顺序建议反过来:先禁输入,再移除 UI,再移除 Ability,最后让组件销毁。这样玩家不会在组件半拆时还能按开火键。
架构分析
Game Feature 不应该知道具体关卡里有几个玩家,也不应该直接访问全局单例找第一个 Controller。更稳的做法是:激活时注册“对某类 Actor 的扩展处理器”,Actor ready 时回调,停用时释放处理器和已授予对象。Lyra 的 Action 大多都有 FPerContextData,里面保存 ActiveData 和 ComponentRequestHandle,这个模式非常值得学。
常见坑
- 只给当前已存在玩家加输入,新加入本地玩家没有 IMC。
- 停用时移除所有同类 IMC,误删别的玩法包加的输入。
- UI Widget 创建后没保存句柄,Deactivate 找不到它。
- AbilitySet 授予后只按 AbilityClass 移除,误删其它来源授予的 Ability。
- 把 Lyra 的 Slot Tag、HUD 类型硬搬到自己的项目,编译过了但运行时没有对应系统。
源码依据
Lyra 的 GameFeatureAction_AddInputContextMapping 监听 GameInstance 和 LocalPlayer,给 Enhanced Input LocalPlayerSubsystem 添加/移除 Mapping Context。GameFeatureAction_AddWidget 使用 Actor Extension 和 per-context 数据记录 Widget。GameFeatureAction_AddAbilities 用 ActiveExtensions 保存 Ability、Attribute、AbilitySet handles,并在 Deactivate 重置。
源码路径索引
Samples/Games/Lyra/Source/LyraGame/GameFeatures/GameFeatureAction_AddInputContextMapping.hSamples/Games/Lyra/Source/LyraGame/GameFeatures/GameFeatureAction_AddInputBinding.hSamples/Games/Lyra/Source/LyraGame/GameFeatures/GameFeatureAction_AddWidget.hSamples/Games/Lyra/Source/LyraGame/GameFeatures/GameFeatureAction_AddAbilities.hSamples/Games/Lyra/Source/LyraGame/GameFeatures/LyraGameFeaturePolicy.h