UE5.6 GAS学习笔记(2)-->GA篇 [1.触发流程]

📅 2026/6/26 1:19:27
UE5.6 GAS学习笔记(2)-->GA篇 [1.触发流程]
前言本文对GAS框架中的GameplayAbility(GA)进行深入探索。正文Gameplay AbilityGA是GAS的核心组件它定义了角色在游戏中可以执行的特定“行为”或“技能”的逻辑。一般来说我们会创建一个继承自UGameplayAbility类的蓝图或直接定义C类来实现。如何激活一个GA在谈及GA的具体内容之前我们需要先知道如何正确激活一个GA。一、使用InputID来激活GA1UE有一个UEnhancedInputLocalPlayerSubsystem 它在LocalPlayer中获取在DedicatedServer模式下这个System只被各个客户端拥有原因很简单它的职责是用来处理玩家输入本质上是对玩家的操作eg鼠标/键盘的按键触发进行反馈服务端没有玩家也没有输入当然不需要。我们利用这个SubSystem提供的接口来对IMCInput Mapping Context进行增删IMC中Mappings项存储了一个个IAInputAction决定了输入类型与具体的按键映射鼠标/键盘。UEnhancedInputLocalPlayerSubsystem* InputSubsystemOwningPlayerController-GetLocalPlayer()-GetSubsystemUEnhancedInputLocalPlayerSubsystem(); InputSubsystem-RemoveMappingContext(GameplayInputMappingContext); InputSubsystem-AddMappingContext(GameplayInputMappingContext,0);在Browser中直接右键在Input栏找到IMC和IA2在角色类中我们定义一个TMapECAbilityInputID, UInputAction* GameplayAbilityInputAction;ECAbilityInputID是一个自定义枚举类因为Demo比较简单所以这边ID命名也比较简单也可以根据需要自定义拓展。我们需要在BP_角色类中找到这个Map将InputID与IA分别进行对应。BP_角色类中找到这个Map配置数据有了以上两步我们已经完成了激活GA的前置工作建立起Input-IA-ID触发链。接下来我们以ID为媒介将GA也加入此触发链3在ASC中实现这样的数据结构使用Map存储GA与ID的映射关系。代码中实现数据结构ASC中对数据结构进行配置在ASC依附的BP_角色类中找到配置InputID-GA4至此可以开始将这几部分都串联起来在ASC中实现一个遍历Map并注册所有GA到ASC中的函数。//这个函数为GA制作带有对应InputID的Spec根据GA类型确定Level然后注册到ASC中。 void UCAbilitySystemComponent::GiveInitialAbilities() { if (!GetOwner() || !GetOwner()-HasAuthority()) return ; //GA等级从0开始可以升级 for (const TPairECAbilityInputID, TSubclassOfUGameplayAbility AbilityPair: Abilities) { GiveAbility(FGameplayAbilitySpec(AbilityPair.Value,0,(int32)AbilityPair.Key,nullptr)); } //基础GA固定等级为1不进行升级 for (const TPairECAbilityInputID, TSubclassOfUGameplayAbility AbilityPair: BasicAbilities) { GiveAbility(FGameplayAbilitySpec(AbilityPair.Value,1,(int32)AbilityPair.Key,nullptr)); } if (!AbilitySystemGeneric) return; //被动GA不需要主动触发ID为-1 for (const TSubclassOfUGameplayAbility PassiveAbility :AbilitySystemGeneric-GetPassiveAbilities()) { GiveAbility(FGameplayAbilitySpec(PassiveAbility,1,-1,nullptr)); }然后在角色类中处理输入Character类中有一个只在客户端调用的专门处理输入的函数SetupPlayerInputComponent()。void ACPlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); UEnhancedInputComponent* EnhancedInputCompCastUEnhancedInputComponent(PlayerInputComponent); if (EnhancedInputComp) { for (const TPairECAbilityInputID,UInputAction* InputActionPair:GameplayAbilityInputAction) { EnhancedInputComp-BindAction(InputActionPair.Value,ETriggerEvent::Triggered, this,ThisClass::HandleAbilityInput,InputActionPair.Key); } } } void ACPlayerCharacter::HandleAbilityInput(const FInputActionValue InputActionValue, ECAbilityInputID InputID) { const bool bPressedInputActionValue.Getbool(); if (bPressed) { GetAbilitySystemComponent()-AbilityLocalInputPressed((int32)InputID); } else { GetAbilitySystemComponent()-AbilityLocalInputReleased((int32)InputID); } }遍历IA的Map确定TriggerEvent类型使用BindAction为各个IA绑定回调函数形参就是Map中IA对应的ID在回调函数中调用InputPressed/InputReleased从ASC的ActivatableAbilities数组中查找拥有此ID的Spec最终触发此GA。Input-IA-ID-GA,这就是利用InputID实现的GA激活流程。二、使用InputTag来激活GA除了利用Spec中的InputID参数作为GA的标识之外还可以用AbilitySpec.GetDynamicSpecSourceTags().AddTag(AbilitySet.InputTag);这个函数在一个GA的Spec上添加一个动态Tag即可以随时Add/Remove利用此Tag来作为GA的输入标识。下面来看看如何实现激活流程这是另一个Demo框架和上文不同只看代码即可1在角色类中定义如下DA类结构DA类UCLASS() class WARRIOR_API UDataAsset_InputConfig : public UDataAsset { GENERATED_BODY() public: //默认IMC UPROPERTY(EditDefaultsOnly,BlueprintReadOnly) UInputMappingContext* DefaultMappingContext; //IA与Naive_Tag结构体存储数组 UPROPERTY(EditDefaultsOnly,BlueprintReadOnly,meta(TitlePropertyInputTag)) TArrayFWarriorInputActionConfig NativeInputActions; //IA与GA_Tag结构体存储数组 UPROPERTY(EditDefaultsOnly,BlueprintReadOnly,meta(TitlePropertyInputTag)) TArrayFWarriorInputActionConfig AbilityInputActions; //遍历存储所有IA和对应Tag的NativeInputAction如果找到输入的Tag就返回其IA UInputAction* FindNativeInputActionByTag(const FGameplayTag InInputTag) const ; };每一个IA都配置一个对应的InputTag2在武器类中定义一个FWarriorHeroAbilitySet一个TagGA的结构体数组配置对应信息因为我这个Demo角色本身是没有技能的根据使用武器有不同技能组每一个GA也配置对应的Tag注册GA时将InputTag添加到GA的DynamicSpecSourceTags中void UWarriorAbilitySystemComponent::GrantHeroWeaponAbilities(const TArrayFWarriorHeroAbilitySet InDefaultWeaponAbilities,const TArrayFWarriorHeroSpecialAbilitySet InSpecialWeaponAbilities,int32 ApplyLevel,TArrayFGameplayAbilitySpecHandle OutGrantedAbilitySpecHandles) { for (const FWarriorHeroAbilitySet AbilitySet:InDefaultWeaponAbilities) { if (!AbilitySet.IsValid()) continue; FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant); AbilitySpec.SourceObjectGetAvatarActor(); AbilitySpec.Level ApplyLevel; AbilitySpec.GetDynamicSpecSourceTags().AddTag(AbilitySet.InputTag); OutGrantedAbilitySpecHandles.Add(GiveAbility(AbilitySpec)); } }(3)在角色类的SetupPlayerInputComponent函数中调用BindAbilityInputAction,其中包含了两个BindAction处理IA触发和结束两种状态的函数回调。1. //将IA与CallBackFunc进行绑定触发时机由ETriggerEven判定Func带有Tag参数封装了ASC以Tag触发GA的功能函数 template class UserObject, typename CallbackFunc void UWarriorInputComponent:: BindAbilityInputAction(const UDataAsset_InputConfig* InInputConfig, UserObject* ContextObject, CallbackFunc InputPressedFunc, CallbackFunc InputReleasedFunc) { for (const FWarriorInputActionConfig AbilityInputActionConfig:InInputConfig-AbilityInputActions) { if (!AbilityInputActionConfig.IsValid()) continue; BindAction(AbilityInputActionConfig.InputAction,ETriggerEvent::Started,ContextObject,InputPressedFunc, AbilityInputActionConfig.InputTag); BindAction(AbilityInputActionConfig.InputAction,ETriggerEvent::Completed,ContextObject,InputReleasedFunc, AbilityInputActionConfig.InputTag); } } 2.//在SetupPlayerInputComponent()函数中调用 WarriorInputComponent继承UEnhancedInputComponent WarriorInputComponent-BindAbilityInputAction (InputConfigDataAsset,this,ThisClass::Input_AbilityInputPressed,ThisClass::Input_AbilityInputReleased); 3.//找到Tag对应的GA void AWarriorHeroCharacter::Input_AbilityInputPressed(FGameplayTag InInputTag) { WarriorAbilitySystemComponent-OnAbilityInputPressed(InInputTag); } void AWarriorHeroCharacter::Input_AbilityInputReleased(FGameplayTag InInputTag) { WarriorAbilitySystemComponent-OnAbilityInputReleased(InInputTag); } 4. void UWarriorAbilitySystemComponent::OnAbilityInputPressed(const FGameplayTag InInputTag) { if (! InInputTag.IsValid()) { return ; } //对所有注册好的AbilitySpec进行遍历 for (const FGameplayAbilitySpec AbilitySpec : GetActivatableAbilities()) { //找Tag if (! AbilitySpec.GetDynamicSpecSourceTags().HasTagExact(InInputTag)) continue; else { TryActivateAbility(AbilitySpec.Handle); } } }IA-InputTag-GA这是使用Tag触发GA的流程这两种方案有什么区别哪一种更好InputID是UE4就存在的触发方式相比之下InputTag是如今更为现代的方案而且UE的Lyra实例项目就是使用第二种InputTag的方式来实现GA激活的这恰是对这个问题的回答第二种是更好的选择。虽然我们可以通过自定义InputID枚举类来为单纯的int32类型的InputID赋予字面意义从1,2,3,4这样没有语义的纯数字修改为向AttackAim这样的带语义的字符串但是InputTag在此基础上还额外提供了层级拓展的功能比如一个GA_BasicAttack基类派生了GA_BasicAttack_Light GA_BasicAttack_Heavy两个子类InputID需要对应添加不同的ID虽然它们有同为一个类型的关系在InputID中却体现不出来而InputTag可以用层级后缀体现InputTag.BasicAttack.Light/Heavy更加统一且方便管理后续还有拓展也无需增加枚举类直接在编辑器中的Tag管理器中找到对应的子层级添加即可。除此之外有些GA并不是通过输入来激活的也可以通过监听Tag来直接激活可以在GA的FAbilityTriggerData::TriggerSource中进行定义。GA蓝图类的Triggers项在这一项中我们可以决定这个GA通过Event、或者监听某个Tag的存在与否来进行激活即TriggerTag。如果我们将触发激活GA也通过Tag来实现似乎GA激活流程的整体框架也会更加统一。并且GAS明显对Tag更加兼容你会发现GA的各种条件判断触发前置逻辑限制等都对Tag有所依赖所以我想以InputTag作为GA触发方式取代传统的InputID也与此有关将整个生态环境都尽可能用Tag来进行操作使得整个体系更加统一。这样一来我们只需要一个Tag类来专门存储各种不同的Tag不同功能的Tag用不同的前缀表示例如Status、Input、Player、等等分别代表状态、输入、玩家等特别清晰,也无需额外维护一个InputID的枚举类。没想到一个触发流程就写了这么长GA篇后续可能会分多个小节来完成感谢各位支持Ciallo(∠・ω )⌒★