UE5 GameplayAbilitySystemのブロックタグについてのメモ

# 概要
UnrealEngine5 の GameplayAbilitySystem(GAS) での BlockTagについてのメモ。

`Block Abilities with Tag`を使うと指定タグを持つアビリティの発動をブロックできますが、アビリティ動作途中までブロックしたいようなケースがあったのでメモ書きです。


## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2023/12/07| 初版 |
|2024/07/09| アビリティ失敗時のタグ情報について追記 |
|2024/12/25| UE5.5 でのブロックタグ処理の変更について追記 |


## 参考
以下の記事を参考にいたしました、ありがとうございます。
[UE5公式：ゲームプレイアビリティシステム](https://docs.unrealengine.com/5.0/ja/gameplay-ability-system-for-unreal-engine/)
[UE公式：UAbilitySystemComponent::BlockAbilitiesWithTags](https://docs.unrealengine.com/5.3/en-US/API/Plugins/GameplayAbilities/UAbilitySystemComponent/BlockAbilitiesWithTags/)
[【UE5】GASと対話する【備忘録】](https://lunanelis.hatenablog.com/entry/2022/05/10/035841)


### 関連する過去記事
[UE4 GameplayAbility Pluginについてのメモ](https://qiita.com/unknown_ds/items/6d4438646827bbe08f4a)


## 環境
Windows10
Visual Studio 2022
UnrealEngine 5.2.1, 5.5.1

## 関連ソース
"Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Public\AbilitySystemComponent.h"
"Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Private\AbilitySystemComponent_Abilities.cpp"


# GASのブロックタグについて

## Block Abilities with Tag の動作
アビリティクラスの設定の `Block Abilities with Tag` にタグを設定すると、その設定されたタグを持つアビリティの起動をブロックすることができます。

![AbilityTag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/858eb377-6ff6-9c63-94dc-7f9fa025f7c3.png)

上記の設定だと、`Ability.Skill.Test1` を持つアビリティが動作している間は `Ability.Skill.Test2` が発動しないようにブロックしています。


## Activation Block Tags の動作
こちらも設定したタグを持つアビリティの起動をブロックします。

![ActivateBlockTags.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/94d78535-1e18-41f0-3234-a1914cca6776.png)

`Block Abilities with Tag` の違いは該当のタグを持つアビリティをブロックする以外にもアクターが持っている場合にブロックされるようです、あとは失敗時のコールバック時に失敗理由判定タグがないなど細かい違いがあるようです。

```cpp:アビリティ失敗時コールバック
// アビリティ発動失敗時コールバック
void AMyCharacter::OnAbilityFailed(const UGameplayAbility* _Ability, const FGameplayTagContainer& _GameplayTags)
{
	//＃ アビリティ発動失敗時コールバックに原因のゲームタグを得るには
	//＃ DefaultGame.ini に以下の登録と、UAbilitySystemGlobals::Get().InitGlobalData(); が必要（アセットマネージャ初期化でやるのがおすすめ）
	//＃ 
	//＃ [/Script/GameplayAbilities.AbilitySystemGlobals]
	//＃ ActivateFailCostName="Ability.Fail.Cost"
	//＃ ActivateFailCooldownName="Ability.Fail.Cooldown"
	//＃ ActivateFailTagsBlockedName="Ability.Fail.Block"
	//＃ ActivateFailTagsMissingName="Ability.Fail.RequiredTags"

	if( _GameplayTags.HasTag( FGameplayTag::RequestGameplayTag(FName("Ability.Fail.Cooldown"))) ){
		// クールダウン中
	}
	if( _GameplayTags.HasTag( FGameplayTag::RequestGameplayTag(FName("Ability.Fail.Cost"))) ){
		// コスト不足
	}
	if( _GameplayTags.HasTag( FGameplayTag::RequestGameplayTag(FName("Ability.Fail.Block"))) ){
		// 他のアビリティにブロックされた
  		// `Block Abilities with Tag`タグでブロックされた場合
	}
	if( _GameplayTags.HasTag( FGameplayTag::RequestGameplayTag(FName("Ability.Fail.RequiredTags"))) ){
		// 発動に必要なタグがない
	}
}
```


# アビリティ実行途中でブロックをやめる
上記ブロックタグを使うと該当アビリティ動作中は指定のアビリティをブロックすることができますが、途中までブロックして解除したいというようなケースもあり得ます。
このような場合、GASのプロパティで `Block Abilities with Tag` を設定した上で `AbilitySystemComponent`クラスの `UnBlockAbilitiesWithTags` で解除。もしくはブロックしたいタイミングで `BlockAbilitiesWithTags` でブロックということもできるようです。

:::note warn
内部的には`BlockedAbilityTags` でカウントしているのでブロック/アンブロックの呼び出し回数に注意が必要です。GASのインスタンスポリシーが`EGameplayAbilityInstancingPolicy::InstancedPerActor` の場合、アビリティが終了しても呼び出しカウンタが継続するため不具合となることが考えられます。
:::

```cpp:AbilitySystemComponent.h
	/** 特定のアビリティタグのブロックまたはブロックのキャンセル */
	void BlockAbilitiesWithTags(const FGameplayTagContainer& Tags);
	void UnBlockAbilitiesWithTags(const FGameplayTagContainer& Tags);

protected:
	/** これらのタグが付いているアビリティは発動できません */
	FGameplayTagCountContainer BlockedAbilityTags;
```

`BlockAbilitiesWithTags` `UnBlockAbilitiesWithTags` は以下のように実装されています。カウント数を取得/設定するには `BlockedAbilityTags`に対し、`GetTagCount()` `SetTagCount()` で可能です。

```cpp:AbilitySystemComponent.cpp
void UAbilitySystemComponent::BlockAbilitiesWithTags(const FGameplayTagContainer& Tags)
{
	BlockedAbilityTags.UpdateTagCount(Tags, 1);
}

void UAbilitySystemComponent::UnBlockAbilitiesWithTags(const FGameplayTagContainer& Tags)
{
	BlockedAbilityTags.UpdateTagCount(Tags, -1);
}
```

## 適用例
モンタージュに任意のイベントタグを入れて `Play Montage Wait for Event`等で受け取って切り替える方法があります。

:::note warn
`Play Montage Wait for Event` はデフォルトのプラグインには付属していません。
https://github.com/tranek/GASDocumentation を参考にしました。
:::

![Montage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0393400b-83eb-dd63-2191-28b80c38d69b.png)

![MontageEvent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/8747ef73-5bab-921e-7016-a1ebe985ac4e.png)

アニメーション通知の処理は以下のような感じになるかと思います。

```cpp:AnimNotify_EventTag.cpp
void UAnimNotify_EventTag::Notify(
	USkeletalMeshComponent* MeshComp,
	UAnimSequenceBase* Animation,
	const FAnimNotifyEventReference& EventReference
	)
{
	AActor*			_Actor = MeshComp->GetOwner();
	ACharacter*		_Chara = Cast<ACharacter>(_Actor);
	
	if(_Chara){
		// イベント呼び出し
		FGameplayEventData _Payload;
		UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(_Actor, EventTag, _Payload);
	}

}
```

# その他
## CanActivateAbility()で実行ブロック時の失敗情報付与
`UGameplayAbility::CanActivateAbility()` で独自にアビリティを実行失敗にした場合、`OptionalRelevantTags` に失敗原因タグをつけることでコールバックメソッドで受け取ることができます。
以下サンプルコード。

```cpp:MyAbility.cpp
bool UMyGameplayAbility::CanActivateAbility(
	const FGameplayAbilitySpecHandle Handle,
	const FGameplayAbilityActorInfo* ActorInfo,
	const FGameplayTagContainer* SourceTags,
	const FGameplayTagContainer* TargetTags,
	OUT FGameplayTagContainer* OptionalRelevantTags
	) const
{
	if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags))
	{
		// 失敗コールバック用にタグ情報を付与する
		OptionalRelevantTags->AddTag(FGameplayTag::RequestGameplayTag(FName("Ability.Fail.Other")));
		
		return(false);
	}

	return(true);
}
```

```cpp:AMyCharacter.cpp
// アビリティ発動失敗時コールバック
void AMyCharacter::OnAbilityFailed(const UGameplayAbility* _Ability, const FGameplayTagContainer& _GameplayTags)
{
	if( _GameplayTags.HasTag( FGameplayTag::RequestGameplayTag(FName("Ability.Fail.Other"))) ){
		// CanActivateAbility()でfalseが返ってきた
	}
	
}

```

## UE5.5 でのブロックタグの処理の変更について
UE5.5から `UGameplayAbility::DoesAbilitySatisfyTagRequirements` のコードが変わった影響で、ブロックタグの処理が少し変わったようです。

<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/d8229b76-7a72-932c-08d8-1b6cd583949a.png">

上記ゲームプレイタグがあったとして、任意のアビリティが `Ability.Skill.Test1` や `Ability.Skill.Test2` というアセットタグを持ったアビリティの実行をブロックするには`Block Abilities with Tag`を設定することでできます。
UE5.3では親タグの `Ability.Skill` を指定するだけで子タグの2つのタグを持つアビリティをブロックできていたのですが、UE5.5ではこれが効かず、`Ability.Skill.Test1` と `Ability.Skill.Test2` などと指定しないとダメみたいです。



# まとめ
`BlockAbilitiesWithTags` `UnBlockAbilitiesWithTags` はC++のみになっているため、BPで制御するにはアビリティコンポーネントを継承して使わないとならないようです。