【UE5】GameplayAbilitySystem実践編【GAS】

この記事はUnreal Engine (UE) Advent Calendar 2023の13日目の記事です。

https://qiita.com/advent-calendar/2023/ue

# 概要
前回からの続きです。

https://qiita.com/atermwd/items/14b5c51e3e4a8a3f9ccc

前回の記事で入門したので実際に使う場合を想定してコンボ攻撃や
スタミナ消費攻撃などを実装していきます。

# 単発の攻撃を実装する
まずは攻撃用のアニメーションとMontageを用意します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/e5d49af4-66fe-c663-53bc-6bce830a765f.png)

### Ability内でMontageの再生を行う
新しく「GA_Attack」というAbilityを作成し、[PlayMontageAndWait]ノードでMontageを再生します。
また、前回は端折りましたが、ActivateAbilityの後に[CommitAbility]ノードを追加します。
[CommitAbility]ノードと[EndAbility]ノードはセットのようなものなので基本的には呼び出してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/fb27b036-8578-0d38-40d7-b4ff6fcb4c0a.png)

これを実行することでアニメーションが再生されることが確認できると思います。

このままの場合、連打すると攻撃を振り終わる前に次の攻撃を振ったりして良くないので
AbilityTagによって自分の攻撃中は同じ攻撃が出せない様にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/360a3cde-f786-58f6-be09-756c348327ad.png)
前回のものから引き続き実装している場合、起動用の[AbilityTag]が変わっているためこちらも合わせるようにしましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/5b0edd8f-6a9d-b840-11b5-c25c385cb04b.png)


### 攻撃判定を発生させる通知を作る
攻撃判定を発生させるにあたり、Montageの任意のタイミングをAbilityに通知する必要があります。
まずはAbility内に通知を受け取るための[WaitGameplayEvent]ノードをMontageと同じタイミングで実行します。
このノードはどこかからEventTagに設定されているタグを指定して対となる[SendGameplayEventtoActor]が呼び出された場合に、EventReceivedが起動するというものになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/e8c8bc32-dd49-a746-a8d5-457574f1d9bf.png)

ではMontage内で通知を送るためにAnimNotifyクラスのBPを作成し（名前はAN_SpawnDamageとしています）
ReceiveNotify関数をオーバーライドし、こんな感じに組みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/d9dfe8f1-fea9-ae92-5651-4c1c617ee6f1.png)
Payloadという引数には自由な情報を書き込むことができ、
特にOptionalObjectの2枠はObject型…
つまりほとんどのクラスを引数として渡せるワイルドカードとして扱うことができるため、
もしアニメーションで設定した数値をAbility側で反映させたいといった事があればここを利用するといいでしょう。

AnimNotifyができたらMontageに設定して…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/25163e56-4953-255a-332a-f86f5cc56c0c.png)

Ability側で通知を受け取った後の挙動を追記すれば…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/35ade15f-74ca-6d9a-15e9-e0371abe283a.png)

攻撃モーションを行い、任意のタイミングで処理が実行されることが確認できると思います。
攻撃判定は各自お好みで実装してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/6536a3af-49a9-d153-e065-1d3680b69c92.png)


# コスト消費攻撃を実装する
ひとまず攻撃を実行できるようになったので、スタミナを消費して攻撃するようにしてみます。
今回はスタミナが足りない場合は攻撃が行えない。という仕様とします。

まずは[ThirdPeasonCharacter]にスタミナ用の変数を作成しておきます。
（デフォルト値を100としています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/d864391b-a061-224d-dce2-7430f1659bed.png)
キャラクター側の処理はこれだけ。

### Abilityにコストの設定を行う

次はAbility[GA_Attack]にコスト用の変数を準備します。
（今回はコストのデフォルト値を15としています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/188023cd-9d26-6e3b-fa8f-68cf6ccfeb01.png)

それが済んだらAbilityの関数[CanActiveteAbility]をオーバーライドします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/b151453b-569d-17aa-c32c-00d771f97677.png)

この関数は戻り値のBool型[ReturnValue]がTrueの場合のみAbilityの起動を許可するフラグのような関数です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/ca854c30-f9eb-1c16-3da8-0dfdd4135aea.png)
その関数を以下のように
「スタミナが足りていれば攻撃を実行。足りなければ攻撃は失敗とする」
そういった処理を実装します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/a27f0919-88b2-9f30-6057-59d7a82984f2.png)

同じように[CommitExecute]関数もオーバーライドし、スタミナを消費する処理を実装します。
この関数は[CommitAbility]ノードを呼び出した際に実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/d97e72fa-df8f-9453-29fb-a7f40bf4dc28.png)

### 結果
攻撃を実行時にコストとして設定した値が消費されました。
また、何度も攻撃することでいずれコスト不足になり、攻撃が行えなくなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/db0c4c2e-1608-2481-7f26-ca645e618eb1.png)

# コンボ攻撃を実装する
ここまでで単発の攻撃の実装ができたので、コンボ攻撃の作り方の紹介をします。

大まかな内容はこんな感じで、Montageに[AnimNotifyState]を追加して
コンボ攻撃1を実行中にコンボ攻撃2の準備をしておき、コンボ攻撃1を終了していいタイミングでコンボ攻撃2のMontage等の処理を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/ce43534c-9795-40ae-89e1-39a0f688e601.png)

### コンボ攻撃2を作成する

まずは最初に作ったMontageに2つ目のコンボを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/6424d0f4-781b-620c-6bcc-46ac5da9ba4e.png)

新たにAbilityの[GA_Attack2]を作成し2つ目の攻撃アニメーションが再生されるようにします。
この時[WaitGameplayEvent]ノードを挟むことで、コンボ攻撃1が許可するまでコンボ攻撃2が実行されないようにします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/6dc4c7a7-7cf1-9a20-7891-9f8444e53893.png)

また、後述する理由でAbilityTagの設定は以下のようにしておきます。
コンボ攻撃1と同じタグで起動し、自身のAbility中は同じAbilityを実行せず
[Action.State.ComboReady]タグを所有している場合に起動可能という設定となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/fc883ea1-b155-f87b-ecad-6e878293ab9f.png)
もちろんAbilityを作成した後はプレイヤー側への追加は忘れないようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/ec881a11-77ac-a405-c369-f077040c3fa9.png)


ここでコンボ攻撃1に戻りますが、コンボ攻撃2を実行中に1が起動しては困るため
[ActivationBlockedTags]に[Action.Test.Attack2]を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/5cffb9df-0458-d2ed-c630-e2642f3df4e0.png)


# コンボ連携用のAnimNotifyStateを作成する
次に[AnimNotifyState]を作成します。
今回必要になる[AnimNotifyState]は2つで
* ボタン入力を受け付けて、入力された場合にコンボ攻撃2を待機状態にするもの
* コンボ攻撃2が待機状態の場合にコンボ攻撃2を実行するもの

### コンボ受付用のAnimNotifyStateを作成する
[AnimNotifyState]の開始～終了までの間、指定したタグを付与するという動作を実装します。
これにより、この区間でのコンボ攻撃2の発動条件を満たします。

まずは[AnimNotifyState]を作成します。（名前は[ANS_AttackReady]とします）
変数として[GameplayTag型]の変数を[スポーン時に公開]にチェックを入れて追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/a64ea4ca-bd45-fc65-e02f-204cc3194edc.png)

[Receive_NotifyBegin]関数及び[Receive_NotifyEnd]関数をオーバーライドし、
それぞれ以下のように組みます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/fb2a046a-b0bc-f5df-b9f8-9155e57c5137.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/408cc952-59cb-53d1-561c-f55e1ac0d287.png)
古い情報源だと[AddGameplayTag]や[RemoveGameplayTag]を使うと書かれているものもありますが
現在はこちらのノードが正しいため要注意です。





### 待機状態のコンボ攻撃2を実行するAnimNotifyStateを作成する
先ほどと同じく[AnimNotifyState]を作成し、（名前は[ANS_AttackBrunch]とします）
[ReceivedNotifyTick]をオーバーライドし以下のように組みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/b3a0a8a1-96da-6161-b1e9-16ab4b9bb861.png)
そのステート期間中は毎フレームこのタグで止めているAbilityを進行させます。

### Montageに設定する
上記2つの[AnimNotifyState]をMontageに設定していきます。
大体このような感じで配置しますが、[ANS_AttackReady]の終端が[ANS_AttackBrunch]の終端より手前で終わる様にしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/09d151eb-d464-3b06-fef1-b152b0f2b0a2.png)
また、[ANS_AttackReady]をクリックして出てくる詳細で[BeginAbilityTag]に[Action.State.ComboReady]を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/6e01dda6-77cf-f3c1-0366-0127224d0453.png)


ここまでの実装により、コンボ攻撃1を実行中に
[Action.State.ComboReady]というタグが一定時間付与されて
このタグが付与されている場合にのみ実行可能なコンボ攻撃2が起動し
[ANS_AttackBrunch]の[SendGameplayEventtoActor]により起動済みのコンボ攻撃2が進みアニメーションが再生される様になります。
![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/56badb9c-2131-f028-5a76-e413de6fdaa8.gif)


# まとめ
色々と準備するものが多く長くなりましたが
ここまで実装してきてプレイヤー側への追記はコンボ攻撃2のGiveAbilityのみであり、
攻撃実行の処理はコンボ攻撃1と2で共有している状態となります。
似たような形でコンボ攻撃3以降も作れるため煩雑なフラグ管理を減らせるのが強みとなります。
GamePlayAbilitySystemで巨大なSwitchや変数管理とはオサラバしましょう

# 小ネタ
[GiveAbility]時の戻り値である[AbilitySpecHandle]から[GetGameplayAbilityfromSpechandle]ノードを使うことで
Abilityの実体を取得できるので、何らかの理由でフィードバックが要る場合などに使えると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/d5abd5fe-9bcb-7fe4-d18a-f88494fef61d.png)


基本的にエディタ上でタグの削除はできません。
間違えて追加してしまったり、不要になったタグを消したい場合は
Config/DefaultGameplayTags.ini
の中にタグリストがあるためここから削除することで無かったことにできます。

※当然ですが使用中のタグだった場合動作しなくなるなどの不具合が起こるため重々注意してください。

### タグの削除について追記

Project設定の中のGameplayTags項目にある
GameplayTagList内の矢印から削除ができる事を教えてもらいました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/106823be-c945-2b52-b7b2-f2039413066e.png)

ただし、ここで削除できるのは「どこにも使われていない状態のタグ」だけなので、
参照を検索から使われている箇所を逐一消していく必要はあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/6c4fb60b-5ae9-f030-084f-a746e0e445bb.png)

間違えて作ったタグぐらいであれば未使用でしょうし、こちらで消したほうが手間はなさそうで良いですね。