[UE] Gameplay Ability System ※ メモ

# プラグインの有効化
![キャプチャ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277769/fa3d908a-e2f1-5e93-1060-32ff7d9be774.jpeg)

# タグの追加
![キャプチャ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277769/b4e77663-bc66-379c-f0e9-fab46623d692.jpeg)

# モジュールの追加
```cs:SampleProject.build.cs
PublicDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTags", "GameplayTasks" });
```

# スタートアップ処理
```c++:SampleProject.cpp
#include "Interfaces/IPluginManager.h"
#include "Modules/ModuleManager.h"
#include "AbilitySystemGlobals.h"
#include "GameplayTagsManager.h"

class FSampleGameModule : public FDefaultGameModuleImpl
{
public:
    virtual void StartupModule() override
    {
        check(LoadTagIni("Config/Tags"));
        
        auto& AbilitySystem = UAbilitySystemGlobals::Get();
        if ( ! AbilitySystem.IsAbilitySystemGlobalsInitialized())
        {
            /* システムの初期化 */
            AbilitySystem.InitGlobalData();
        }
    }

private:
    bool LoadTagIni(const FString& Directory)
    {
        if (const auto ThisPlugin = IPluginManager::Get().FindPlugin(TEXT("CommonConversation")))
        {
            /* GameplayTag 用の .ini ファイル配置ディレクトリパスを登録する */
            UGameplayTagsManager::Get().AddTagIniSearchPath(ThisPlugin->GetBaseDir() / *Directory);
            return true;
        }
        return false;
    }
};
IMPLEMENT_PRIMARY_GAME_MODULE(FSampleGameModule, SampleProject, "SampleProject");
```

# AttributeSet の継承クラス
```c++:SampleAttributeSet.h
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "Net/UnrealNetwork.h"
#include "GameplayEffectAggregatorLibrary.h"

/* Attribute の Getter & Setter 関数の自動生成マクロ */
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
        GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
        GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
        GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
        GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

UCLASS()
class USampleAttributeSet : public UAttributeSet
{
    GENERATED_BODY()

public:
    UFUNCTION()
    virtual void OnRep_SampleValue(const FGameplayAttributeData& OldValue)
    {
        GAMEPLAYATTRIBUTE_REPNOTIFY(USampleAttributeSet, SampleValue, OldValue);
    }

public: /* UObject interface */
    /* 生存中に複製されるプロパティを返す */
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override
    {
        Super::GetLifetimeReplicatedProps(OutLifetimeProps);
        DOREPLIFETIME_CONDITION_NOTIFY(USampleAttributeSet, SampleValue, COND_None, REPNOTIFY_Always);
    }
    
public: /* UAttributeSet interface */
    /* CurrentValue の変更が行われる直前に呼ばれるクランプ処理などを行う関数 */
    virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override
    {
        if (Attribute == GetSampleValueAttribute())
        {
	        NewValue = FMath::Clamp(NewValue, 0.0f, 100.0f);
        }
    }
    
    /* GameplayEffect から BaseValue の変更が行われた際に呼ばれる */
    virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override
    {
        Super::PostGameplayEffectExecute(Data);
    }
    
    /* Aggregator 生成時に呼ばれる */
    virtual void OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const override
    {
        if (NewAggregator != nullptr)
        {
            if (Attribute == GetSampleValueAttribute())
            {
                /* 最も否定的な Modifier とすべての肯定的な Modifier のみを許可する */
                NewAggregator->EvaluationMetaData = &FAggregatorEvaluateMetaDataLibrary::MostNegativeMod_AllPositiveMods;
            }
        }
    }

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Sample", ReplicatedUsing = OnRep_SampleValue)
    FGameplayAttributeData SampleValue{ 100.0f };
    ATTRIBUTE_ACCESSORS(USampleAttributeSet, SampleValue) /* 初期化関数の自動生成マクロ */
};
```

# Ability System を保持するクラス
```c++:SampleCharacter.h
#include "Abilities/GameplayAbility.h"
#include "AbilitySystemInterface.h"
#include "AbilitySystemComponent.h"
#include "GameplayTags.h"
#include "GameplayEffectTypes.h"

UCLASS()
class ASampleCharacter : public ACharacter,
                       , public IAbilitySystemInterface
                       , public IGameplayTagAssetInterface
{
    GENERATED_BODY()
    
public:
    ASampleCharacter(const FObjectInitializer& ObjectInitializer)
    {
        AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("Ability System"));
        AbilitySystemComponent->SetIsReplicated(true);
        AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Full);
        AbilitySystemComponent->RegisterGenericGameplayTagEvent().AddUObject(this, &ASampleCharacter::OnAbilitySystemGenericGameplayTagEvent);
        
        /* UAbilitySystemComponent に自動登録 */
        SampleAttributeSet = CreateDefaultSubobject<USampleAttributeSet>(TEXT("AbilitySet"));
        
        GameplayTagCountContainer.RegisterGenericGameplayEvent().AddUObject(this, &ASampleCharacter::OnGameplayTagCounterGenericGameplayTagEvent);
    }

public: /* UObject */
    virtual void BeginPlay() override
    {
        Super::BeginPlay();

        if (auto ThisComponent = GetAbilitySystemComponent())
        {
            int32 Index = 0;
            for (auto& GameplayAbiliity : StartupGameplayAbiliities)
            {
                if (IsValid(GameplayAbiliity.GetGCPtr()))
                {
                    ThisComponent->GiveAbility(FGameplayAbilitySpec(GameplayAbiliity, 1, Index++));
                }
            }
	        
            ThisComponent->InitAbilityActorInfo(this, this);
        }
    }

public: /* APawn interface */
    /* 所有された際に呼ばれる */
    virtual void PossessedBy(AController* NewController) override
    {
        Super::PossessedBy(NewController);

        if (auto ThisComponent = GetAbilitySystemComponent())
        {
	        ThisComponent->RefreshAbilityActorInfo();

	        if (ThisComponent->ReplicationMode == EGameplayEffectReplicationMode::Mixed)
	        {
		        SetOwner(NewController);
	        }
        }
    }
    
public: /* IAbilitySystemInterface */
    /* UAbilitySystemComponent の取得 */
    virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override
    {
        return AbilitySystemComponent;
    }
    
public: /* IGameplayTagAssetInterface */
    /* 所有している GameplayTag の取得 */
    virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override
    {
        for (const auto& GameplayTag : GameplayTagContainer.GetGameplayTagArray())
        {
	        if ( ! TagContainer.HasTag(GameplayTag))
	        {
		        TagContainer.AddTag(GameplayTag);
	        }
        }
    }

public:
    /* UAttributeSet 追加 */
    bool AddAttributeSet(UAttributeSet* Attribute)
    {
        if (IsValid(Attribute))
        {
            if (auto ThisComponent = GetAbilitySystemComponent())
            {
                ThisComponent->AddSpawnedAttribute(Attribute);
                ThisComponent->ForceReplication();
                return true;
            }
        }
        return false;
    }
    
    /* UAttributeSet 削除 */
    void RemoveAttributeSet(UAttributeSet* Attribute)
    {
        if (IsValid(Attribute))
        {
            if (auto ThisComponent = GetAbilitySystemComponent())
            {
                ThisComponent->RemoveSpawnedAttribute(Attribute);
                ThisComponent->ForceReplication();
            }
        }
    }
    
    /* サンプル値の設定 */
    bool SetAttributeSampleValue(const float& Value)
    {
        if (IsValid(SampleAttributeSet))
        {
            if (auto ThisComponent = GetAbilitySystemComponent())
            {
                ThisComponent->SetNumericAttributeBase(USampleAttributeSet::GetSampleValueAttribute(), Value);
                return true;
            }
        }
        return false;
    }
    
    /* サンプル値の取得 */
    float GetAttributeSampleValue() const
    {
        if (IsValid(SampleAttributeSet))
        {
            if (auto ThisComponent = GetAbilitySystemComponent())
            {
                return ThisComponent->GetNumericAttribute(USampleAttributeSet::GetSampleValueAttribute());
            }
        }
        return 0.0f;
    }

private:
    /* GameplayTag 参照カウントが更新された際に呼ばれる */
    void OnAbilitySystemGenericGameplayTagEvent(const FGameplayTag GameplayTag, int32 NewCount)
    {
        if ( ! GameplayTagContainer.HasTag(GameplayTag))
        {
	        GameplayTagContainer.AddTag(GameplayTag);
        }
        
        if ( ! GameplayTagCountContainer.HasMatchingGameplayTag(GameplayTag))
        {
	        GameplayTagCountContainer.SetTagCount(GameplayTag, NewCount);
        }
        else
        {
	        static_cast<void>(GameplayTagCountContainer.UpdateTagCount(GameplayTag, (NewCount - GameplayTagCountContainer.GetTagCount(GameplayTag))));
        }
    }
    
    /* GameplayTag のカウントが 0 になった際に呼ばれる */
    void OnGameplayTagCounterGenericGameplayTagEvent(const FGameplayTag GameplayTag, int32 NewCount)
    {
        if (NewCount <= 0)
        {
	        GameplayTagContainer.RemoveTag(GameplayTag);
        }
    }
    
public:

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Abilities)
	TArray<TSubclassOf<UGameplayAbility>> StartupGameplayAbiliities;
    
private:
    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Sample", meta = (AllowPrivateAccess = "true"))
    TObjectPtr<USampleAttributeSet> SampleAttributeSet{ nullptr };
    
    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = Abilities, meta = (AllowPrivateAccess = "true"))
    TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent{ nullptr };
    
    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Sample", meta = (AllowPrivateAccess = "true", Categories = "GameplayCue"))
    FGameplayTagContainer GameplayTagContainer;
    
    FGameplayTagCountContainer GameplayTagCountContainer;
};
```

# アニメーションモンタージュの作成
![キャプチャ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277769/6da67c95-d43b-3676-fdf3-1e1ba8a6fa62.jpeg)

# Gameplay Effect BP の作成
![キャプチャ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277769/fb405a89-bfb8-53b8-738d-c7abbd4425eb.jpeg)
※ BaseValue、CurrentValue ともに影響する。

# Gameplay Ability BP の作成
![キャプチャ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277769/c699e2a2-0b1d-f496-c3ac-bb6b61c66509.jpeg)
※ 実行すると AbilityTag を付与する。

# Gameplay Ability の実行
![キャプチャ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277769/3670102d-a01b-98bf-a17c-2a554a1a5ad9.jpeg)

# 参考資料
- [GASDocumentation](https://github.com/tranek/GASDocumentation#concepts-a)
- [Launching Grenades in Unreal with Gameplay Ability System](https://denismakesgames.medium.com/launching-grenades-in-unreal-with-gameplay-ability-system-part-1-3179f38f3730)
- [おかわりはくまいのアンリアルなメモ](https://okawari-hakumai.hatenablog.com/archive/category/GameplayAbilities)
- [historia ブログ](https://historia.co.jp/archives/15422/)
- [pto8913のメモ帳](https://pto8913.hatenablog.com/entry/2020/05/27/135109)
- [Hatena Blog](https://wvigler.hatenablog.com/entry/2021/05/10/103406)