news 2026/6/26 1:19:18

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UE5.6 GAS学习笔记(2)-->GA篇 [1.触发流程]

前言

本文对GAS框架中的GameplayAbility(GA)进行深入探索。

正文

Gameplay Ability(GA)是GAS的核心组件,它定义了角色在游戏中可以执行的特定“行为”或“技能”的逻辑。一般来说,我们会创建一个继承自UGameplayAbility类的蓝图或直接定义C++类来实现。

如何激活一个GA?

在谈及GA的具体内容之前,我们需要先知道如何正确激活一个GA。

一、使用InputID来激活GA

(1)UE有一个UEnhancedInputLocalPlayerSubsystem ,它在LocalPlayer中获取,在DedicatedServer模式下,这个System只被各个客户端拥有,原因很简单:它的职责是用来处理玩家输入,本质上是对玩家的操作(eg:鼠标/键盘的按键触发)进行反馈,服务端没有玩家,也没有输入,当然不需要。

我们利用这个SubSystem提供的接口来对IMC(Input Mapping Context)进行增删,IMC中Mappings项存储了一个个IA(InputAction,决定了输入类型)与具体的按键映射(鼠标/键盘)。

UEnhancedInputLocalPlayerSubsystem* InputSubsystem=OwningPlayerController->GetLocalPlayer()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(); InputSubsystem->RemoveMappingContext(GameplayInputMappingContext); InputSubsystem->AddMappingContext(GameplayInputMappingContext,0);

在Browser中直接右键,在Input栏找到IMC和IA

(2)在角色类中,我们定义一个TMap<ECAbilityInputID, 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-GA

(4)至此,可以开始将这几部分都串联起来,在ASC中实现一个遍历Map并注册所有GA到ASC中的函数。

//这个函数为GA制作带有对应InputID的Spec,根据GA类型确定Level,然后注册到ASC中。 void UCAbilitySystemComponent::GiveInitialAbilities() { if (!GetOwner() || !GetOwner()->HasAuthority()) return ; //GA等级从0开始,可以升级 for (const TPair<ECAbilityInputID, TSubclassOf<UGameplayAbility>> AbilityPair: Abilities) { GiveAbility(FGameplayAbilitySpec(AbilityPair.Value,0,(int32)AbilityPair.Key,nullptr)); } //基础GA固定等级为1,不进行升级 for (const TPair<ECAbilityInputID, TSubclassOf<UGameplayAbility>> AbilityPair: BasicAbilities) { GiveAbility(FGameplayAbilitySpec(AbilityPair.Value,1,(int32)AbilityPair.Key,nullptr)); } if (!AbilitySystemGeneric) return; //被动GA不需要主动触发,ID为-1 for (const TSubclassOf<UGameplayAbility>& PassiveAbility :AbilitySystemGeneric->GetPassiveAbilities()) { GiveAbility(FGameplayAbilitySpec(PassiveAbility,1,-1,nullptr)); }

然后在角色类中处理输入,Character类中有一个只在客户端调用的专门处理输入的函数SetupPlayerInputComponent()

void ACPlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); UEnhancedInputComponent* EnhancedInputComp=Cast<UEnhancedInputComponent>(PlayerInputComponent); if (EnhancedInputComp) { for (const TPair<ECAbilityInputID,UInputAction*>& InputActionPair:GameplayAbilityInputAction) { EnhancedInputComp->BindAction(InputActionPair.Value,ETriggerEvent::Triggered, this,&ThisClass::HandleAbilityInput,InputActionPair.Key); } } } void ACPlayerCharacter::HandleAbilityInput(const FInputActionValue& InputActionValue, ECAbilityInputID InputID) { const bool bPressed=InputActionValue.Get<bool>(); 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=(TitleProperty="InputTag")) TArray<FWarriorInputActionConfig> NativeInputActions; //IA与GA_Tag结构体存储数组 UPROPERTY(EditDefaultsOnly,BlueprintReadOnly,meta=(TitleProperty="InputTag")) TArray<FWarriorInputActionConfig> AbilityInputActions; //遍历存储所有IA和对应Tag的NativeInputAction,如果找到输入的Tag,就返回其IA UInputAction* FindNativeInputActionByTag(const FGameplayTag& InInputTag) const ; };

每一个IA都配置一个对应的InputTag

(2)在武器类中定义一个FWarriorHeroAbilitySet(一个Tag+GA的结构体)数组,配置对应信息(因为我这个Demo角色本身是没有技能的,根据使用武器有不同技能组)

每一个GA也配置对应的Tag

注册GA时,将InputTag添加到GA的DynamicSpecSourceTags中

void UWarriorAbilitySystemComponent::GrantHeroWeaponAbilities(const TArray<FWarriorHeroAbilitySet>& InDefaultWeaponAbilities,const TArray<FWarriorHeroSpecialAbilitySet>& InSpecialWeaponAbilities,int32 ApplyLevel,TArray<FGameplayAbilitySpecHandle>& OutGrantedAbilitySpecHandles) { for (const FWarriorHeroAbilitySet& AbilitySet:InDefaultWeaponAbilities) { if (!AbilitySet.IsValid()) continue; FGameplayAbilitySpec AbilitySpec(AbilitySet.AbilityToGrant); AbilitySpec.SourceObject=GetAvatarActor(); 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这样没有语义的纯数字修改为向Attack,Aim这样的带语义的字符串,但是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~(∠・ω< )⌒★

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 1:19:02

遗传算法第二部分:选择压力、交叉算子与自适应变异机制解析

1. 项目概述&#xff1a;为什么第二部分比第一部分更值得你花时间啃透“遗传算法入门——第二部分”这个标题乍看平平无奇&#xff0c;像是某本教材里被翻得卷了边的章节名。但如果你已经读过第一部分&#xff0c;或者刚用Python跑通了一个简单的“求函数最大值”的GA demo&…

作者头像 李华
网站建设 2026/6/26 1:18:18

ImHex二进制分析完全指南:从新手到专家的10个实用技巧

ImHex二进制分析完全指南&#xff1a;从新手到专家的10个实用技巧 【免费下载链接】ImHex &#x1f50d; A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM. 项目地址: https://gitcode.com/GitHub_Trending/im/Im…

作者头像 李华
网站建设 2026/6/26 1:16:10

YOLOv11涨点改进| ECCV 2026 |独家创新、主干改进篇| 全新PKINetV2主干让YOLOv11更加强大!增强遥感目标检测中的几何建模能力、尺度适应能力,助力目标检测、旋转目标检测涨点

一、本文介绍 ⭐本文介绍使用全新PKINetV2主干 改进YOLOv11的主干网络,主要作用是替换原有Backbone,增强模型对复杂目标形态和多尺度目标的特征提取能力。PKINet-v2通过条带卷积与方形卷积协同建模,既能适应桥梁、船舶、道路、杆状物等细长目标的几何结构,又能保持车辆、建…

作者头像 李华
网站建设 2026/6/26 1:15:01

中间件安全攻防实践:从K8s、Docker到Jetty、WebSphere的CVE复现与加固

1. 项目概述&#xff1a;从“攻防演练”到“日常运维”的中间件安全视角在安全圈里待久了&#xff0c;你会发现一个很有意思的现象&#xff1a;很多安全工程师谈起“服务攻防”和“CVE复现”时&#xff0c;眼睛会放光&#xff0c;仿佛那是充满挑战与荣耀的战场。但一提到日常的…

作者头像 李华
网站建设 2026/6/26 1:12:58

Docker第3天:Dockerfile、Compose、Swarm、Machine学习整理

Docker三件套&#xff1a;Dockerfile、Compose、Swarm及淘汰工具Machine完整学习笔记前言一、Dockerfile&#xff1a;自定义镜像标准构建脚本1 构建核心规则2 高频指令详解简易Nginx镜像实战Dockerfile二、Docker Compose&#xff1a;单机多容器一键编排核心定位标准使用三步流…

作者头像 李华
网站建设 2026/6/26 1:12:21

9 款通信 FPGA / 交换芯片参数价格对比

板上 9 颗芯片完整解析(按图中黄色数字编号) 这是一张高端通信 / 网络设备主板(Juniper 瞻博交换机 / 路由器拆机板),包含博通交换 SoC、Xilinx 赛灵思 FPGA、Intel (Altera) 阿尔特拉 FPGA三大类芯片,分 9 颗逐一说明: 第一行(上排 3 颗) 1 号:1550 — Broadcom …

作者头像 李华