【UE5】GameplayAbilitySystem超入門編【GAS】

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

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

# 概要
GameplayAbilitySystem(以後GASと表記)をC++に頼らずに
BPだけで攻撃アクションを実行できるようにします。

ゲームを１回作ってみて、スキルなどの実装がごちゃごちゃしてしまった…。
という人に刺さるやもしれないシステムです。

# GASとは？
公式ドキュメント

https://docs.unrealengine.com/4.27/ja/InteractiveExperiences/GameplayAbilitySystem/

簡単に言うと「タグ」を付けたり外したりすることで
キャラクター側が意識せずにスキルなどのアクションを実行できるという便利なものです。

～場合この攻撃で、～の場合でなければこの攻撃は出なくて…といった複雑なフラグのような条件を
タグという形で一括管理させることで、記述をシンプルにできるのが強みとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/e9bc1e95-046a-48b7-8b61-e32ed038e9e9.png)

:::note warn
注意点として、タグをうまく管理できないと非常に厄介な事になるため、
事前にどういった行動パターンがあるかなどの構想を立てたうえで実装しましょう。
::: 

# セットアップ
UE5のエディタの[編集]→[プラグイン]からプラグインのウィンドウを開き、
検索で[GamePlayAbility]と入力するなどして[GameplayAbilities]プラグインにチェックを入れエディタを再起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/5ec63e5e-6d91-9916-e40d-ae17f4236b14.png)

これでGASを使える準備が完了しました。簡単だね。

# まずは最低限動かしてみる

今回はThirdPersonTemplateで説明していきます。
(このバージョンはUE5.1ですが、他のバージョンでも大差ないはずです)


### PrintStringで文字列を表示する簡単なAbilityを作ってみる

まずコンテンツブラウザを右クリックしてGameplayAbilityブループリントを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/d0a4d55b-900b-daf8-c029-42e6815ada15.png)
なぜか最初から複数ありますが、GameplayAbilityを選択して作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/6839ebda-f73a-9eb5-41db-e4b724b88e37.png)
というわけでGA_PrintStringという、PrintStringを表示するためだけのAbilityをこれから実装します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/e5ef355b-dfb0-fcb5-9db3-a81ed9e58732.png)
BPを開いたら最初から[ActivateAbility]と[OnEndAbility]の二つのイベントがあるので、以下のように繋ぎます。
[EndAbility]は終わり次第とりあえず呼んでおきましょう。お約束みたいなものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/db2e2d90-a524-eca0-142d-12c0d7412569.png)
それが終わったら[クラスのデフォルト]を選択します。
詳細ウィンドウにいっぱい出てきますが、[AbilityTags]の右にある[編集...]を選択します。
出てきたウィンドウの[新しいゲームプレイタグを追加]を開くと名前を入力できるので
[Action.Test.PrintString]と入力して新しいタグを追加してください。
ドット[.]で区切る事である程度分別ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/0ca4bd3a-21ce-4f0a-9875-52b698a3b106.png)
こんな感じになればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/5f29204e-e888-1a5f-5224-39b311520770.png)

この設定項目に関する事については後述します。

### プレイヤーにAbilityを使う準備をする
まずはThirdPersonCharacterの左上にあるコンポーネントの[+追加]を押して
AbilitySystemコンポーネントを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/8f6d54d7-e1a6-6c99-7056-042095086c68.png)
追加したらAbilitySystemコンポーネントからGiveAbilityを呼び出して、先ほど作ったGA_PrintStringをAbilityClassに設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/2798cdc6-e164-6d14-bfed-97bdbe80b3f6.png)
それが終わったら任意のタイミングで同じく[AbilitySystemコンポーネント]から[TryActivateAbilityiesByTag]を呼び出し、[MakeGameplayTagContainerFromTag]ノードを以下のようにして設定してください。
このノードが基本的なAbilityを実行するノードとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/fe8bcbb9-c764-384d-656f-44b14ffe3a25.png)
実行して動けば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/d95cf15b-c4c4-b8d5-7d90-55f670a27194.png)

意外と簡単でしょう？

# 小休憩
AbilitySystemを使ってPrintStringを表示することができましたが
「PrintStringを表示するだけでこんなに手間かける意味とは…？」
と思われるのではないでしょうか？

このシステムの本番は複数のAbilityを扱うようになってからなので、そう思うのは当然です。
というわけで、便利さの一つを体験してもらってこの記事の本文は終わりとさせてもらいます。

# GASの便利さを体験しよう
ではGA_PrintStringに戻ってPrintStringの後に３秒間のDelayを挟み込みます。
そして[ActivationOwnedTags]に[Action.Test.PrintString]を設定します。
これでこのAbilityは３秒間持続したのちに終了するという挙動になります。
ここで言う「Abilityが持続する」というのは、この[ActivationOwnedTags]に設定されたタグである[Action.Test.PrintString]をキャラクターが所持している状態の事を言います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/6f7373d5-d9ff-477c-f644-f755a5da61e2.png)

### 新しいAbilityを作成する
ここで新しいAbilityを作成します。
作り方は最初に作ったものと大体同じですが、[AbilityTags]と[ActivationBlockedTags]に少し手を加えてあります。
[AbilityTags]には新しく作ったタグ（任意の名前でOK)を
[ActivationBlockedTags]には最初に作ったAbilityで使っていた[ActivationOwnedTags]のタグを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/fcfa4aee-7ee0-32d6-472e-e55b3a47415e.png)
そして最初にやったのと同じようにキャラクターにAbilityを設定、実行できるようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/e41dae98-8fd0-60d6-fbee-c5084891e42e.png)

### 実行してみる
まず新しく作ったAbilityを実行する…普通に動きます。
古いほうのAbilityを実行する…普通に動きます。
しかし、古いほうのAbilityを実行してから３秒間の間、新しいAbilityが動いていない事がわかります。

これがAbilitySystemの良いところの一つで
「古いAbilityが実行中は新しいAbilityの実行を禁止する」という挙動を
AbilitySystem内だけで実装できたということです。

キャラクターで実装したのは
・Abilityを覚えさせる
・Abilityを実行させる
の２つだけであり、フラグもタイマーも作っていないのです。

ちなみに、キャラクターが現在所持しているタグは以下のコンソールコマンドによって可視化できます。
`showdebug abilitysystem`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582942/f40c2c1c-d798-6a83-0d4d-c7b3d776c5a6.png)


# 具体的には何に使える？
アクションゲームで攻撃Abilityを作成したとします。
その攻撃Abilityはアイテム使用中には実行できない仕様でした。
アイテム使用Abilityの[ActivationOwnedTags]に[Action.Use.Item]と設定しておけば
攻撃Abilityの[ActivationBlockedTags]に設定することで、アイテム使用Abilityが終了するまで攻撃が実行できなくなります。
そしてその動作をプレイヤー側の実装を変えずに可能となります。

とても単純な言い方をすれば、キャラクターの状態に関するフラグをすべてタグに集約することで
キャラクターの中に大量の管理用変数ができる事を防止し
プレイヤーを敵が同じ基礎挙動(ジャンプや仰け反り、死亡など）を持っていれば同じタグを使いまわしたりもできます。

そしてタグの名前でAbilityを実行するため、攻撃そのものが差し変わってもキャラクターの実装には影響せず
キャラクターはただ「攻撃したい」と求めれば「今持っているAbilityの中で実行可能なもの」を行うという
非常にスッキリした実装が可能となります。

# Abilityの設定タグの大まかな説明リスト
以下にざっくりとしたAbilityのタグの挙動を置いておきます。
* Ability Tags
ここに設定したタグをTryActivateAbilityiesByTagで呼び出すと動く
複数設定すると、いずれかを呼び出せば動く
呼び出し用の名前って事
* Cancel Abilites With Tag
ここに設定したタグを持つアビリティをキャンセルする
必殺技による割り込み、被ダメージでのダウン等で活躍かな
* Block Abilities With Tag
このアビリティ実行中は該当タグ(AbilityTags)を持つアビリティの実行を禁止する。
後述のActivationBlockedTagsと同じ実行禁止効果だが、受動と能動という関係。
ダウン中は他の行動を禁止したいって場合はこっちを使ったほうがミスがない。
* Activation Owned Tags
アビリティ実行時に設定したタグをオーナーに付与する
今このアクション実行中だよ！ということを明示するために必要
別になくても動くっちゃ動くけど、普通は設定する
* Activation Required Tags
ここで設定したタグを持っていないと実行しなくなる。
例えばダッシュ中のタグが付いていないとダッシュ攻撃は出せない。みたいな。
ダッシュ攻撃作るなら、通常攻撃はダッシュ中発動できない制限がいるのかな？
変身中なんかの行動変化はこれで切り分けできそう
* Activation Blocked Tags
これに設定されたタグを持っている間はアビリティが実行できなくなる
↑のダッシュ中は通常攻撃が出ない。とか、攻撃中は攻撃できないとか。
これちゃんと設定しないと、攻撃を攻撃でキャンセルとかいうおかしな挙動ができちゃう
* Target Required Tags
Activation Required Tagsと参照する対象が違うみたいだけど、違いがわからん…
どっち使えばええの…？
* Target Blocked Tags
Activation Blocked Tagsと参照する対象が違うみたいだけど、違いがわからん…
どっち使えばええの…？