UE5 Gameplay Ability System(GAS) 简单记录

这里使用的UE版本为 5.3.2

至第一次接触 UE 已有十几天,对 GAS 甚至 UE 引擎的理解都极为浅薄,所以这里的内容随时可能会发生变化

 

其中的蓝图逻辑和代码逻辑不一定能够成功运行,因为我想描述的内容与我实际工程是不同的。为了更加精简,这里更注重重要函数的使用方式。

 

0. 环境配置

首先需要开启 Gameplay Ability 插件,位于

  

  寻找 Gameplay Ability 插件,启用它,然后重启编辑器就可以了

 

若想要发挥 GAS 的全部实力,我们暂时不得不使用 C++ 构建部分代码,这需要生成 C++项目的解决方案

找到你的UE工程目录,右键工程文件,生成C++解决方案。或者点击编辑器的 Tools - New C++ Class 随便增加一个C++类,它也会生成 C++工程

 

   然后找到以你项目名开头的 *.Build.cs 文件,在 PublicDependencyModuleNames.AddRange(new string[] { ... }  代码块中加入模块名:"GameplayAbilities", "GameplayTags", "GameplayTasks"

,这可以在C++工程中启用插件,别忘了保存并编译。若使用 Visual Studio 编译,需要关闭 UE 编辑器,否则编译失败

 

至此,配置完成,相关内容可查看官方文档 Gameplay技能系统 

 

1. Gameplay Ability Component (Gameplay 技能组件)

若令一个 Character 能够释放技能,需要在它身上挂载 AbilitySystemComponent;

这里不要在蓝图中添加组件,因为AbilitySystemComponent的有些函数在蓝图中并未暴露,所以,这里用继承的方式;

新增一个 Character 类,可令类名为  AAbilityCharacterBase ,它继承于  ACharacter  与  IAbilitySystemInterface ,后者位于头文件 AbilitySystemInterface.h 中;

增加  UAbilitySystemComponent 指针类型的成员,它就是挂载在 Character 身上的技能组件。这里只是指针,需要在构造函数中创建它的实例;

覆盖 IAbilitySystemInterface 接口的函数  UAbilitySystemComponent* GetAbilitySystemComponent() const ,返回的就是刚刚创建的组件指针,这里是必须的。

[备注] 当然,也可以将 AbilitySystemComponent 放在其它地方,比如 PlayerState,随需求和实现方式而定,GetAbilitySystemComponent 函数的实现也随之更改。

[备注] 显而易见,任何受 GAS 影响的单位(比如敌人),无论它是否拥有能够释放的技能,只要可被 GAS 更改属性(伤害,Buff,Debuff...),都需使用 AbilitySystemComponent,除非额外编写与GAS无关的逻辑。可以使用其它 Actor 的 ASC.

 

 

 

 

 1 #pragma once
 2 
 3 #include "CoreMinimal.h"
 4 #include "GameFramework/Character.h"
 5 #include "AbilitySystemInterface.h"
 6 #include "AbilitySystemComponent.h"
 7 
 8 #include "AbilityCharacterBase.generated.h"
 9 
10 UCLASS()
11 class ABILITYTEST_API AAbilityCharacterBase 
12     : public ACharacter
13     , public IAbilitySystemInterface
14 {
15     GENERATED_BODY()
16 
17 public:
18     // Sets default values for this character's properties
19     AAbilityCharacterBase();
20 
21 protected:
22     // Called when the game starts or when spawned
23     virtual void BeginPlay() override;
24 
25 public:    
26     // Called every frame
27     virtual void Tick(float DeltaTime) override;
28 
29     // Called to bind functionality to input
30     virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
31 
32 public:
33     class UAbilitySystemComponent* GetAbilitySystemComponent() const override 
34     {
35         return AbilitySystemComponent.Get();
36     }
37 
38     UPROPERTY(BlueprintReadOnly, Category = "Ability")
39     TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
40 };
 1 #include "AbilityCharacterBase.h"
 2 
 3 // Sets default values
 4 AAbilityCharacterBase::AAbilityCharacterBase()
 5 {
 6      // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
 7     PrimaryActorTick.bCanEverTick = true;
 8 
 9     AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
10     AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Full);
11 }

 

2. Gameplay Ability(GA, 技能)

2.1 技能激活

Character如果能够释放技能,首先需要拥有技能。调用 Give Ability 函数,赋予Character一个技能(下例位于继承 AAbilityCharacterBase 类的蓝图类中,代表可被控制的玩家)

   此处至少需要将Ability Class参数赋值,它表示要释放的是哪个技能。现在先将它放在这里,之后,需要创建一个技能的蓝图

注意,这里的返回值是一个Gameplay Ability Spec Handle类型的结构体,后面解释它是什么

 

对于一个最简单的需求来说:按键后释放一个技能,那就需要调用 AbilitySystemComponent 的函数(下例位于继承AAbilityCharacterBase 类的蓝图类中,代表可被控制的玩家)

 或者

  在蓝图中激活技能时,一般使用后者。

如果使用前者(Try Active Ability),需要在调用 Give Ability 时,记录它的返回值 Gameplay Ability Spec Handle,然后应用于 Try Active Ability 的参数中;

如果使用后者(Try Active Ability By Class),可以新建一个继承于 Gameplay Ability 类的蓝图类,然后在 In Ability to Activate 中就可以添加刚刚新创建的蓝图类,然后就能释放技能了,这是最为简单的使用方式;

  在C++项目中,在技能的拥有者身上记录拥有的技能列表是比较常见的情况,所以,可直接在C++中调用 TryActiveAbility 函数。实际上,TryActivateAbilityByClass 的实现中,也是调用了 TryActiveAbility,况且 GetDefaultObject 也有一定的消耗(存在Object Create,当然一般情况也不在乎这点消耗)

 

2.1.1 Server Ability RPC Batch

    在  CallServerTryActivateAbility  函数中,可以看到其中使用了  FServerAbilityRPCBatch :

     当 Client -> Server 调用释放技能时,可以实例化临时的结构体对象  FScopedServerAbilityRPCBatcher ,它会在作用域结束时,将 TryActivateAbility、SetReplicatedTargetData、EndAbility 这三个逻辑在一起一同执行。

    区别于 TryActivateAbility、SetReplicatedTargetData、EndAbility 这三个操作, FScopedServerAbilityRPCBatcher 仅通过一个RPC函数完成。另外,若令 FScopedServerAbilityRPCBatcher 能够正常工作,必须重写 UAbilitySystemComponent::ShouldDoServerAbilityRPCBatch 函数,并将返回值设置为 true.

    在 GASDocumentation 中,有更详细的相关介绍。

 

2.1.2 技能激活预测

    客户端在激活一个技能时,会生成一个新的 Prediction Key,它本质是一个整型的 ID,并拥有两个委托:Rejected Delegate,CaughtUp Delegate。

      Rejected Delegate:服务器发现客户端的预测失败时,客户端会调用此委托;

      CaughtUp Delegate:服务器对客户端的预测执行时(不在乎预测的正确与否),客户端会调用此委托;

    一般情况下,客户端产生一个新的 Prediction Key,或者服务器接收客户端发来的 Key,都是通过 FScopedPredictionWindow 这个结构体来完成的。

     

    它的套路和之前提到的 FScopedServerAbilityRPCBatcher  是一样的,通过 RAII(Resource Acquisition Is Initialization)的方式管理 Prediction Key:构造时记录,析构时向客户端同步,并调用 CaughtUp Delegate。

    此过程在 Server 时调用。也就是说,在 Server 正在执行 Client 预测的内容时(FScopedPredictionWindow 生命周期内),客户端收到属性同步过来的 Prediction Key,调用 CaughtUp Delegate。

 

    当客户端产生一个新的 Prediction Key

     通过 RPC 的方式传递给 Server

 

    技能激活时,Client 暂时不顾 Server 是否能够成功,径直执行逻辑,这就是所谓的“预测”。直到 Server 发现技能不能够成功激活,此时通知 Client,此技能激活失败。

     这时可以发现,Prediction Key 像是一个“记录点”的作用,通过 FScopedPredictionWindow 生成记录点,在失败时再根据 Prediction Key 执行相应的 Rejected Delegate 执行回退客户端的预测逻辑。

    然而,这个“记录点”是可以嵌套的。正如 GameplayPrediction.h 文件中的注释所提到的,当一个 FScopedPredictionWindow 的生命周期尚未完成时,再次生成一个 Prediction Key,然后再生成一个 Prediction Key,形成 X->Y->Z 的形式,Y 预测失败会同时将 Y、Z 两个预测操作拒绝/回退。

 

 

2.2 技能内容实现

  激活技能的本质顶层逻辑大致如此,现在缺少一个技能:需要释放一个什么样的技能?

  GA是技能执行的具体逻辑。现在,创建一个技能蓝图

   在习惯上,命名以GA_开头,表示它是一个Gameplay Ability

  在GA蓝图中,存在两个事件函数:

    Event ActivateAbility: 技能激活需要执行的逻辑

    Event OnEndAbility: 技能执行结束

   技能的表现可以是输出一行String,发射一个火球,或者其它等等。但执行完逻辑后,需要调用 End Ability 函数,表示技能执行完成,否则,On End Ability 不会被调用,一些技能的事件和 Tag 等等都不能被正确更新。End Ability是必须的,并且要注意调用时机。一旦调用了 End Ability,此技能就已经完成,之后尽量不要在其它对象中引用这个技能实例,或者在这个技能实例中执行额外内容,尤其是有关GAS

 

  Commit Ability 是检查并应用技能的冷却(Cooldown)和消耗(Cost)的函数,如有这两者,此函数应被调用。冷却和消耗的实现是使用 Gameplay Effect,之后会设置它们。

  Commit Ability 中调用了 ApplyCooldown 与 ApplyCost 两个函数。

   

  2.3 在 C++ 中激活技能,技能激活与 Enhanced Input 联动

  在实现上,我更倾向于在蓝图中实现一些拓展性、频繁被更改的内容,C++中实现更加系统、通用的内容。

  类似于上述的“按键激活技能”,如果一个 Character 拥有10个技能,那么就需要绑定10个按键事件,对每个按键事件调用 Try Activate Ability By Class函数。

  现在,我不想要实现每个技能都必须在蓝图中为新技能绑定事件,而是通过技能“给予”的方式——只要我给予了这个 Character 一个技能,那么他就可以释放这个技能。至于按什么按键释放,通过配置 Class Defaults 的方式决定。

  回到一开始,要在C++中使用 Enhanced Input,找到以项目名开头的 *.Build.cs 文件,在  PublicDependencyModuleNames.AddRange(new string[] { ... }   代码块中加入模块名:"EnhancedInput"

  在 AAbilityCharacterBase 类中加入UInputMappingContext 类型的成员

1     UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput")
2     TObjectPtr<class UInputMappingContext> AbilityMappingContext;

  我要让 AbilityMappingContext 在蓝图中配置,而不是通过蓝图的 BeginPlay 中绑定

   (上图中的 IM_AbilitesInputMap 是新创建的)

 

  既然不通过蓝图的 BeginPlay,那就要在C++中的 BeginPlay 增加逻辑,使得 AbilityMappingContext 加入到 InputSystem 系统中

 1 // Called when the game starts or when spawned
 2 void AAbilityCharacterBase::BeginPlay()
 3 {
 4     Super::BeginPlay();
 5 
 6     if (AbilityMappingContext)
 7     {
 8         APlayerController* PlayerController = Cast<APlayerController>(GetController());
 9         if (!PlayerController)
10             return;
11 
12         const ULocalPlayer* LocalPlayer = PlayerController->GetLocalPlayer();
13         if (LocalPlayer)
14         {
15             UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
16             if (InputSystem)
17             {
18                 const int32 Priority = 1;
19                 InputSystem->AddMappingContext(AbilityMappingContext, Priority);
20             }
21         }
22     }
23 }

  也可以将上述逻辑放在 SetupPlayerInputComponent 函数中实现。

 

  技能的响应实际上是依据 InputAction 事件,接下来要做到的就是在GA蓝图中,能够配置对应的 InputAction。

  既然原生的 GameplayAbility 不可以配置 Enhanced Input,那么就自己新建一个 GameplayAbility 类

 1 //新建头文件: AbilityOwned.h
 2 #pragma once
 3 
 4 #include "CoreMinimal.h"
 5 #include "AbilitySystemComponent.h"
 6 #include "EnhancedInputSubsystems.h"
 7 #include "AbilityOwned.generated.h"
 8 
 9 class AAbilityCharacterBase;
10 
11 UCLASS(BlueprintType, Blueprintable)
12 class ABILITYTEST_API UAbilityOwned
13     : public UGameplayAbility
14 {
15     GENERATED_BODY()
16 
17 public:
18     UAbilityOwned();
19     UAbilityOwned(AAbilityCharacterBase* TheOwner);
20     ~UAbilityOwned();
21 
22 public:
23         //技能激活函数,无参,无返回值
24     void Activate();
25 
26         //获得技能的拥有者
27     UFUNCTION(BlueprintCallable, Category = "Ability")
28     AAbilityCharacterBase* GetOwningCharacter() const;
29 
30 private:
31     UAbilitySystemComponent* GetOwnerAbilitySystemComponent() const;
32 
33 public:
34         //此技能激活需要的 InputAction
35     UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "EnhancedInput")
36     TObjectPtr<UInputAction> Action;
37 
38         //技能拥有者
39     AAbilityCharacterBase* Owner;
40 
41         //此变量是GiveAbility返回的值,需要在技能激活时使用。如果它不存在,那么就需要调用 TryActivateAbilityByClass激活技能,而不是 TryActivateAbility
42     FGameplayAbilitySpecHandle Handle;
43 };
 1 //AbilityOwned.cpp
 2 #include "AbilityOwned.h"
 3 
 4 #include "AbilityCharacterBase.h"
 5 #include "Kismet/KismetSystemLibrary.h"
 6 
 7 UAbilityOwned::UAbilityOwned()
 8     : Owner(nullptr)
 9 {
10 }
11 
12 UAbilityOwned::UAbilityOwned(AAbilityCharacterBase* TheOwner)
13     : Owner(TheOwner)
14 {
15 }
16 
17 UAbilityOwned::~UAbilityOwned()
18 {
19 }
20 
21 UAbilitySystemComponent* UAbilityOwned::GetOwnerAbilitySystemComponent() const
22 {
23     if (!Owner)
24         return nullptr;
25 
26     return Owner->GetAbilitySystemComponent();
27 }
28 
29 void UAbilityOwned::Activate()
30 {
31     UAbilitySystemComponent* ASC = GetOwnerAbilitySystemComponent();
32     if (!ASC)
33         return;
34     
35     ASC->TryActivateAbility(Handle);
36 }
37 
38 AAbilityCharacterBase* UAbilityOwned::GetOwningCharacter() const
39 {
40     return Cast<AAbilityCharacterBase>(this->GetOwningActorFromActorInfo());
41 }

  此处的Activate()函数必须是无参无返回值的函数,因为 Enhanced Input Component 的输入绑定的回调函数必须是无参无返回值的

 1 //AbilityCharacterBase.cpp 
 2 //以下两函数为新增的
 3 
 4 //为 Character 添加技能
 5 FGameplayAbilitySpecHandle AAbilityCharacterBase::AddAbilityOwn(TSubclassOf<UAbilityOwned> AbilityClass)
 6 {
 7     if (!AbilitySystemComponent)
 8         return FGameplayAbilitySpecHandle();
 9 
10     //Add Ability 
11     FGameplayAbilitySpec Spec(AbilityClass, 1, INDEX_NONE, this);
12     FGameplayAbilitySpecHandle Handle = AbilitySystemComponent->GiveAbility(Spec);
13     
14     TObjectPtr<UAbilityOwned> NewAbilityCDO = NewObject<UAbilityOwned>(this, AbilityClass.Get(), FName(TEXT("AbilityCDO")), EObjectFlags::RF_ClassDefaultObject);
15     NewAbilityCDO->Owner = this;
16     NewAbilityCDO->Handle = Handle;
17 
18     AbilitesOwned.Add(NewAbilityCDO);
19     
20     //Set Input
21     BindAbilityToAction(NewAbilityCDO);
22     return Handle;
23 }
24 
25 //为技能绑定按键事件
26 void AAbilityCharacterBase::BindAbilityToAction(UAbilityOwned* AbilityCDO)
27 {
28     if (!AbilityCDO)
29         return;
30 
31     APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
32     if (!PlayerController)
33         return;
34 
35     UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerController->InputComponent);
36     if (!EnhancedInputComponent)
37         return;
38     
39     EnhancedInputComponent->BindAction(AbilityCDO->Action, ETriggerEvent::Triggered, AbilityCDO, &UAbilityOwned::Activate);
40 }

  由于 EnhancedInputComponent->BindAction  函数只能绑定无参函数,所以,若想调用类似于 ActivateAbilityByIndex(int32 Index) 或者 ActivateAbilityByClass(TSubclassOf<UAbilityOwned> AbilityClass) 的函数就成了问题。这样,Character 所拥有的技能在类的成员变量表示就不能是 TArray<TSubclassOf<UAbilityOwned>> AbilitesOwned; (不能是 UClass),而是 TArray<TObjectPtr<UAbilityOwned>> AbilitesOwned; (UObject 的形式),通过为 Object 赋值成员变量来做到函数参数的传递。就像上述代码中的

1     TObjectPtr<UAbilityOwned> NewAbilityCDO = NewObject<UAbilityOwned>(this, AbilityClass.Get(), FName(TEXT("AbilityCDO")), EObjectFlags::RF_ClassDefaultObject);
2     NewAbilityCDO->Owner = this;  //技能拥有者
3     NewAbilityCDO->Handle = Handle;  //Ability Spec Handle,技能激活时使用

  这时创建GA蓝图,就能够配置 Input Action,并且之前 GiveAbility 函数调用与按键事件函数也不需要在蓝图中存在了

 

2.4 GAS 原生的 GA 与 Input 事件联动 [此节未完成]

  [此节未完成,待补充]

 

3. Gameplay Effect 技能效果(GE)

  GE是技能执行产生的效果,比如属性值影响,增加 Buff 等等,技能的冷却和消耗也属于GE。

  对一个Actor施加一个GE,有8个函数可调用,但一般情况只用其中的2-4个

这些函数一半是 GameplayAbility 的成员,另一部分是 AbilitySystemComponent 的成员;一些是为目标施加,一部分是为自己施加,需要按情况使用对应的函数。

回到之前新创建的GA蓝图,想要发射一颗火球,那么首先要有一颗火球。

新建Actor蓝图,用来来表示一颗火球

 

   设置碰撞事件

 

对目标应用 GE 

     (获得 Target Actor 的 Ability System Component 失败时也应该 DestoryActor )

 

  回到GA蓝图,设置 Spawn Actor 函数的参数 Class,设置为刚刚创建的Actor蓝图

 

  新建 GE 蓝图类,定义技能效果

 

  GE 蓝图是一个纯数据蓝图,在这里尽量解释其中的每个成员,它们是 GAS 工作的核心。

  3.1 Duration

Duration Policy 存在三个选项,表示技能效果的持续存在的方式:

  Instant: 立刻生效,瞬时效果,无持续时间,无周期

  Infinite: 无限持续,GE被移除时消失

  Has Duration: 持续一段时间

Infinite 与 Has Duration 可设置 Period,表示每一段时间应用一次 Modifier 与 Execution 中设置的内容。

 

3.2 Gameplay Effect

    Gameplay Effect 子项是应用 GE 时的实际效果:增减 Tag,修改属性等等。

    Gameplay Effect 子项存在三个数组类型成员:Modifiers,Executions,Components:

    3.2.1 Modifiers 

      Modifiers 可用于修改目标的属性值,可定义被修改的属性(Attribute),计算符号(Modifier Op),计算类型(Modifier Magnitude),对技能释放者和目标的 Tag 要求(Source Tags & Target Tags)。

      对于计算类型,拥有4种:

        Scalable Float: 硬编码设置,直接在蓝图中设置值

        Attribute Based: 基于 Source 或者 Target 的属性计算值,公式为:Y = Coeffcient * (PreMultiplyAdditiveValue + X) + PostMultiplyAdditiveValue。

        Custom Calculation Class: 根据 Gameplay Mod Magnitude Calculation 类计算出一个 float 值

        Set by Caller: SetByCaller 是运行时由 Ability 或 GameplayEffectSpec 的创建者于 GameplayEffect 之外设置的值。在 Modifiers 中,根据 Data Name 选择 caller 的方式不可选。

 

        可根据 Source & Target 是否拥有某些 Tag 来决定是否生效每个 Modifier 。注意,如果 GE 设置了周期或者为无限, 这些 Tag 只会在首次应用 GE 时才会判断。

    3.2.2 Executions 

      当 Modifiers 中约定的规则不满足于需求时,Execution 可自定义计算逻辑来修改属性值。

Execution 可提前捕获一些属性值用于计算。Snapshot 为属性快照,若设置为True,则在GE应用过程中,即使是目标变更了属性值,此处捕获的属性值依然是应用GE之前的值。

         注意,若使用 Execution 则需要在 C++ 中实现一些逻辑,无法在蓝图中执行全部的工作。

[未完成] 在 C++ 中执行 Gameplay Effect Execution Calculation,待补充

    3.2.3 Components 

      Components 中约定了一些 GE 的功能,简单描述一下它们。由于我没有逐个试验过它们,所以描述地未必会完全正确。

        Abilities Gameplay Effect Component:  当 GE 被应用时,为目标赋予一个技能。此技能可以是主动技能,但我认为被动技能或者令目标强制做某些事的时候更为适合。这里可定义赋予什么样的技能,以及技能等级,技能移除策略。

 

        Additional Effects Gameplay Effect Component: 当 GE 被应用时,之后,或提前移除,During 到期,再应用一些其它的 GE。

        Asset Tags Gameplay Effect Component: 表示 GE 自己所拥有的 Tags,不具备任何功能。

        Block Ability Tags Gameplay Effect Component: 阻止拥有一些Tag的技能激活。

        Chance to Apply Gameplay Effect Component: 有一定的概率应用此 GE。

        Custom Can Apply Gameplay Effect Component: 类似于概率应用 GE,这里可自定义在何种条件下应用 GE。需要 GameplayEffectCustomApplicationRequirement 蓝图类或 C++ 子类。

        Gameplay Effect UIData Text Only: GE 的 UI信息,仅有Text。

        Immunity Gameplay Effect Component: 根据配置内容免疫其它 GE。

        Remove Other Gameplay Effect Component: 根据配置内容移除其它 GE。

        Target Tag Requirements Gameplay Effect Component: 目标达到 Tag 的要求(有/没有/匹配)才可使 GE 被应用(Application) / 生效(OnGoing) / 移除(Removal)。

        Target Tags Gameplay Effect Component: 赋予或移除目标的Tag。

        以上内容都是一个个的组件类,一些功能可能需要继承它们并实现或覆盖一些函数。

 

  3.3 Stack

    可以理解为 Buff 堆叠数,对于一些特殊情况,比如“如果拥有了 Buff 时又被再次施加,则持续时间刷新”这类需求,可将 Stack 的 Limit Count 设置为 1.

    若使用 Stack,GE 的 During 不能是 Instant。

    其中,Stacking Type有两个:

      Aggregate by Source: 多个 Source Actor 为一个 Target 施加 GE 时,为每个 Source 都建立一个 Stack 实例。

      Aggregate by Target: 多个 Source Actor 为一个 Target 施加 GE 时,仅存在一个 Stack 实例。

        

  现在,创建一个最为简单的技能伤害的 GE: 扣减10点生命值

  当前没有Attribute,先放在这里,之后再创建它

 

   继续创建一个用于技能冷却的 GE,这里必须给一个用于冷却的 Tag

 

  回到 GA 蓝图,将新创建的冷却 GE 应用到技能上

 

   [备注] 直接调用 GetCooldownTimeRemaining 函数无法正确获得剩余的冷却时间 

 

 

 4. Gameplay Tags

  Tag本质是字符串,以字符 “ . ” 进行分割以表示 Tag 的父子级。它可用于描述、标记、过滤、触发、组织分类游戏中的实体、事件、行为等等。

  比如,已知一些Tag:

    Ability.Active.AbilityFireBall    表示一个主动技能,并且具体为“火球术”的技能

    Ability.Damage.Real    表示一个技能产生的伤害,并且是真实伤害

    Ability.Damage.Magic    表示一个技能产生的伤害,并且是魔法伤害

    Ability.Immunity.Damage.Magic    表示免疫技能伤害,并且免疫的是魔法伤害

    Entity.Character.Enemy.Faction.Ghost    表示一个敌人的实体,并且阵营是“鬼魂”

  现在,对一个 Target 施加 GE A,GE A 的效果是为 Target 增加一个 Ability.Immunity.Damage.Magic 的 Tag

   那么又一个 GE B 到来时,并配置了 Must Not Have Tag :Ability.Immunity.Damage.Magic,则 GE B 不能被应用

 

   回到 GA 蓝图,仍可以看到在 Class Defaults 中可配置一系列 Tag:

  Ability Tags:该 GA 拥有的 Tag,用于描述该技能。

  Cancel Abilities with Tag:该 GA 激活时,如果其它 GA 拥有 Cancel Abilities with Tag 中的 Tag,那么这些 GA 将被取消。

  Block Abilities with Tag:该 GA 激活时,如果其它 GA 拥有 Block Abilities with Tag 中的 Tag,那么这些 GA 将被阻塞。

  Activation Owned Tags:该 GA 激活时,这些 Tags 会赋予给技能的拥有者。

  Activation Required Tags:技能的拥有者身上存在这些 Tags 时,该 GA 才能被激活。

  Activation Blocked Tags:技能的拥有者身上存在这些 Tags 时,该 GA 不能被激活。

  Source Required Tags:技能的拥有者身上存在这些 Tags 时,该 GA 才能被激活。此条目只有由 Gameplay Event 触发 GE 时有作用。

 

  Source Blocked Tags:技能的拥有者身上存在这些 Tags 时,该 GA 不能被激活。此条目只有由 Gameplay Event 触发 GE 时有作用。

  Target Required Tags:技能的目标身上存在这些 Tags 时,该 GA 才能被激活。此条目只有由 Gameplay Event 触发 GE 时有作用。

  Target Blocked Tags:技能的目标身上存在这些 Tags 时,该 GA 不能被激活。此条目只有由 Gameplay Event 触发 GE 时有作用。

    

5. Gameplay Attribute [此节未完成]

  Gameplay Attribute 用于表示角色的基础属性,HP,MP,移动速度,攻击力等等。

  Gameplay Attribute 在 C++ 中由  FGameplayAttributeData 结构体定义,其中包括 BaseValue 和 CurrentValue 两个值。不可将 BaseValue 视为如生命最大值的意义,它一般用于存储属性被更改前的值,比如被一个 During 的 GE 更改。
  
  现在,创建属性集
  
 1 //新建头文件:CharacterAttributes.h
 2 #pragma once
 3 
 4 #include "CoreMinimal.h"
 5 #include "AttributeSet.h"
 6 #include "CharacterAttributes.generated.h"
 7 
 8 UCLASS(Blueprintable)
 9 class UCharacterAttributes : public UAttributeSet
10 {
11     GENERATED_BODY()
12 
13 public:
14     UCharacterAttributes();
15     ~UCharacterAttributes();
16     
17     void InitAttributes(TObjectPtr<UAbilitySystemComponent>& AbilitySystemComponent);
18 
19 public:
20     UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Attribute")
21     FGameplayAttributeData Health;
22 
23     UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Attribute")
24     FGameplayAttributeData Mana;
25 };
 1 //CharacterAttributes.cpp
 2 
 3 #include "AbilitySystemComponent.h"
 4 #include "CharacterAttributes.h"
 5 
 6 #define __AddAttribute(AttributeData_, Value_) \
 7     { \
 8         AttributeData_.SetBaseValue(Value_); \
 9         AttributeData_.SetCurrentValue(Value_); \
10     }
11 
12 UCharacterAttributes::UCharacterAttributes()
13 {
14 }
15 
16 UCharacterAttributes::~UCharacterAttributes()
17 {
18 }
19 
20 void UCharacterAttributes::InitAttributes(TObjectPtr<UAbilitySystemComponent>& AbilitySystemComponent)
21 {
22     __AddAttribute(Health, 100);
23     __AddAttribute(Mana, 100);
24 
25     AbilitySystemComponent->AddSpawnedAttribute(this);
26 }

 

  为之前的 AAbilityCharacterBase 加入属性
 1 #pragma once
 2 
 3 #include "CoreMinimal.h"
 4 #include "GameFramework/Character.h"
 5 #include "AbilitySystemInterface.h"
 6 #include "AbilitySystemComponent.h"
 7 #include "CharacterAttributes.h"
 8 #include "AbilityCharacterBase.generated.h"
 9 
10 UCLASS()
11 class ABILITYTEST_API AAbilityCharacterBase 
12     : public ACharacter
13     , public IAbilitySystemInterface
14 {
15     GENERATED_BODY()
16 
17 public:
18     // Sets default values for this character's properties
19     AAbilityCharacterBase();
20 
21 protected:
22     // Called when the game starts or when spawned
23     virtual void BeginPlay() override;
24 
25 public:    
26     // Called every frame
27     virtual void Tick(float DeltaTime) override;
28 
29     // Called to bind functionality to input
30     virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
31 
32 public:
33     class UAbilitySystemComponent* GetAbilitySystemComponent() const override 
34     {
35         return AbilitySystemComponent.Get();
36     }
37 
38     class UCharacterAttributes* GetCharacterAttributes() const 
39     {
40         return CharacterAttributes.Get();
41     }
42     
43     UPROPERTY(BlueprintReadOnly, Category = "Ability")
44     TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
45 
46     UPROPERTY(BlueprintReadOnly, Category = "Attributes")
47     TObjectPtr<UCharacterAttributes> CharacterAttributes;
48 };

 

  再回到之前创建的GE,就可以在 Attribute 中设置被修改的属性值了。

 

[未完成] 属性的更改监听以及在什么情况下存在可预测性

 

附注:Spec 与 SpecHandle

  在查看 GAS源码时,经常遇见 Spec 结尾的结构体和 SpecHandle 结尾的结构体,比如:

    GameplayAbilitySpec,GameplayAbilitySpecHandle

    GameplayEffectSpec,GameplayEffectSpecHandle,GameplayEffectContext,GameplayEffectContextHandle,ActiveGameplayEffect,ActiveGameplayEffectHandle

    ......

 

  对于 Gameplay Ability 来说,一般不会使用它的实例,除非使用它的 CDO。更多情况下,只是使用它的类(UClass)。

  GameplayAbilitySpec 可看作是 Gameplay Ability 的一个实例,更倾向于“运行时”的数据;而 GameplayAbilitySpecHandle 可看作指向 GameplayAbilitySpec 的指针。

  同样对于 GameplayEffect,GameplayEffectContext 等有同样的规律。另外有,GameplayEffectContextHandle 表明一个 GameplayEffectSpec 的创建者。

 

6. Gameplay Task [此节未完成]

 [未完成] 待补充

 

7.  Gameplay Ability System 网络同步 [此节未完成]

 

 [未完成] 待补充

 

posted on 2024-01-16 17:46  __Even  阅读(1085)  评论(0编辑  收藏  举报

导航