当前位置: 首页 > news >正文

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~(∠・ω< )⌒★

http://www.jsqmd.com/news/1078283/

相关文章:

  • RPA引擎源码解析:Python状态机与规则引擎设计
  • 动图魔方技术拆解 09:FrameProcessor 如何统一裁剪、滤镜、字幕和输出参数
  • 遗传算法第二部分:选择压力、交叉算子与自适应变异机制解析
  • 容器云入门学习心得:基于 Docker 实现 Web 应用容器化部署实践
  • Appium跨界Windows桌面自动化测试:统一技术栈实战指南
  • 5分钟搞定FanControl中文设置:Windows风扇控制彻底汉化指南
  • 【2026免费喝奶茶攻略】【领千问8元无门槛券】
  • software framwork 2026.06.25
  • Qwen-VL-2512+Gradio三分钟搭建AI海报工坊
  • 2026深度实测|Cursor高性价比平替实测!中文Vibe Coding迭代能力全对比
  • 空间计算驱动的企业GEO实践:佛山园区与中山制造案例的技术路径分析
  • 01_visual_studio环境配置及C++基本概念入门
  • GPT-4o实战指南:参数调优、多模态落地与企业级避坑手册
  • 当下即是:当手机成为此刻
  • Docker第3天:Dockerfile、Compose、Swarm、Machine学习整理
  • 2026软考零基础保姆级备考规划!上班族高效上岸攻略
  • 9 款通信 FPGA / 交换芯片参数价格对比
  • Xinference模型部署实战:零配置启动、OpenAI兼容与GGUF优化
  • 为xv6实现符号链接:从概念到内核实践
  • 机器学习新手生存指南:从环境配置到模型部署的实操路径
  • 人民大学、上海AI实验室等联合打造的“全能生物AI“
  • 深度评测:企业采购Token服务商,一张表打满5个维度
  • 豆包AI视频三招实操:文生视频、图片动起来、数字分身全解析
  • 鸿蒙 ArkTS 实战:Lost Found Board 从状态建模到交互闭环完整解析
  • 导师推荐!2026年首选推荐的专业降AI率工具
  • Qwen2.5-VL本地部署实战:边缘多模态推理全链路指南
  • 2026旅游小程序和普通商城的区别,关键在这里
  • 用9B参数的小模型打败32B的“巨人“
  • DolphinDB工业数据质量:完整性检查与修复
  • P89LPC9321单片机引脚、时钟与SFR配置实战指南