第21回UE5ぷちコンで制作した作品「子連れグレイマン侍WOLF」ではプレイヤーの状態管理にGAS(GameplayAbilitySystem)を使用しました。その中で「GAS便利!」ってなった部分について解説していければと思います。

プレイヤーの状態管理とは?

プレイヤーの状態管理とは、「攻撃アクション中は移動できない」とか「ジャンプ中に攻撃は出せない」等の管理の事を指しています。これだけ聞くと俗に言う「フラグ管理」で管理したくなりそうな要件ですよね。(攻撃中か判断するフラグ・ジャンプ中か判断するフラグを定義し、ブランチで切り分ければ実装できそう)

しかし、それではアクションが増える毎にフラグが増えていき、最終的には網の目のようなフラグ管理が必要となってしまい、バグの温床となってしまいます。また、プレイヤーのBPも条件分岐まみれになってしまいコードの可読性が落ちてしまいます。

GASを使えば俯瞰的な視点からフラグ管理ができる!

GASには”Activation Owned Tags”と”Activation Required Tags”と”Actibation Blocked Tags”というタグ管理機能があります。ここに適切なタグを設定していくことで、上述のフラグ管理のような切り分けを分かりやすく、正確に実装することができるようになっています。

Activation Owned TagsこのAbilityが開始(ActivateAbility)してから終了(EndAbility)するまで保持しておくGameplayTag
Activation Required TagsこのAbilityを発動させる為に保持している必要があるGameplayTag
Actibation Blocked TagsこのAbilityを発動させる為に保持していたらダメなGameplayTag

とりあえず、この3つだけは頭に叩き込んでおいてください。

子連れグレイマン侍WOLFにおけるプレイヤーの状態

本作ではプレイヤーがベビーカーを離している状態では、以下のような状態遷移があります。(ベビーカーを掴んでいる状態の話しは一旦置いておきます)

「納刀」時でも「抜刀」時でも「移動」「ライン移動」は可能ですが、「ジャンプ」は納刀時のみ、「攻撃」「防御」は抜刀時のみ、更に「攻撃」時にタイミングボタンを押せば「コンボ攻撃」が発動するという感じです。

そして、各アクションによるアニメーションの再生中は、別のアクションのボタンを押しても無効にする必要があります。(「抜刀」しながら「移動」はできない等)

さらに、コンボ攻撃と途中で敵から攻撃を受けた場合には、コンボ攻撃を解除する必要もあります。

これを「フラグ管理」で実装できますか??

そこでGAS(GameplayAbilitySystem)の登場!

本来ならGameplayTagの一覧からお見せすべきですが、直感的に理解して頂く為に「ジャンプ」のGameplayAbilityのタグ定義を先に見てみましょう。

(サードパーソンテンプレートから入門すると「ジャンプ」自体がGameplayAbilityで定義されている事に違和感を抱くかもしれませんが、「ジャンプ」も「攻撃」もアクション(プレイヤーの動作)として考えて頂けると腑に落ちるかと)

種類設定したGameplayTag設定したGameplayTagの理由
Activation Owned TagsCharacter.Action
Character.Action.Jump
ジャンプ中は「何らかのアクションを実行中」であることを示す”Character.Action”と、「それが具体的に何か」を示す為の”Character.Action.Jump”の2つのGameplayTagを保持する
Activation Required TagsCharacter.Mode.Moveジャンプは「納刀時」(CharacterのModeがMove)の時のみ実行可能
Activation Blocked TagsCharacter.Action「何らかのアクションを実行中」(“Character.Action”)にジャンプはできない

先ほど頭に叩き込んで頂いた情報と突き合わせると、なんとなく理屈が分かってくるかと思います。

解説すると”Character.Action”は同時に実行できないアクションのグループとして定義してあるイメージです。”Character.Action”の配下には”Character.Action.Jump”(ジャンプ)以外にも”Character.Action.Attack”(攻撃)や、”Character.Action.Guard”(防御)等があります。

ジャンプしている間は攻撃や防御をできないように制御する必要がある為、「Activation Owned Tags」には”Character.Action”を保持させています。また、現在のアクション(ジャンプ)という情報も取れたら便利なため、”Character.Action.Jump”も保持させています。

「Activation Required Tags」に定義している”Character.Mode.Move”とは、納刀状態を表しているGameplayTagとなります。”Character.Mode”系のGameplayTagは”Character.Mode.Battle”(抜刀)や、”Character.Mode.Push”(ベビーカーを押している)が定義されています。つまり、ここにはジャンプ可能なプレイヤーの状態を格納するイメージです。

最後に「Activation Blocked Tags」には”Character.Action”が定義してあります。これは、「Activation Owned Tags」の解説でも触れましたが、ジャンプ中には攻撃や防御はできないように制御したい為、同時実行不可なアクションのグループとして定義した”Character.Action”を設定しています。つまり、ジャンプ中はジャンプと同じグループのアクションは一切行えないという制御になります。

如何でしょう?なんとなく簡単に思えてきませんか?

続いて、「攻撃」のGameplayAbilityのタグを見て法則性を見つけてみましょう。

攻撃にはコンボの概念が存在するので、コンボ系の定義が入っていますが、それを除けばジャンプと全く同じ構成に見えませんか??(Combo系のタグを上記から消し去ってみてください)

具体的なジャンプとの違いは「Activation Owned Tags」に定義された”Character.Action.Attack”と、「Activation Required Tags」に定義された”Character.Mode.Battle”だけとなります。

ここで重要なのは、「Activation Required Tags」に設定された”Character.Mode.Battle”で、抜刀状態でないと攻撃できないという仕様の為に、このような定義となっています。

はい、結局は「ジャンプ」も「攻撃」も似たような定義であることが分かりました。

最後におさらいとして「ライン移動」のGameplayAbilityのタグ定義を見てみましょう。

ほら、まったく同じでしょ?「Activation Required Tags」が空なのは納刀状態でも、抜刀状態でも「ライン移動」ができるからです。もう、簡単に見えてきたでしょ?

納刀状態・抜刀状態

さて、今までは「Activation Owned Tags」にGameplayTagを定義することで、アクションの同時実行を防いできましたが、ここに定義したGameplayTagはActivateAbilityノードからEndAbilityノードの間だけ保持されるものでしたよね。言い換えれば「ジャンプ」「攻撃」「ライン移動」は全て一瞬で終わってしまうアクションな為「Activation Owned Tags」で管理するのが望ましいのですが、「納刀」を表す”Character.Mode.Move”や、「抜刀」を表す”Character.Mode.Battle”というGameplayTagの保持については、長期的にタグを保持する必要があります。(納刀状態のまま歩き回ることもある為です)

となると、「Activation Owned Tags」を使って”Character.Mode.Move”を保持させることは出来ない事が分かるかと思います。

さて、どう実装すべきでしょうか?それは、「抜刀」のGameplayAbilityのBPを見てみると解決しそうです。

前半部分の解説は省きますが、「モンタージュを再生」ノードで「抜刀」のアニメーションを再生しています。再生が終わると、Add Loose Gameplay Tagsノードを使ってオーナーアクタに対して”Character.Move.Battle”を渡しています。(ここ重要)

そうです、このロジックが永続的にGameplayTagを保持させる方法なのです。

更に画面右側のタグ定義を見てもらえれば分かりますが、”Character.Action”タグが保持されている間は「抜刀」することもできないことが分かるかと思います。(これは復習ですね)

全てのアクション時には移動すら禁止(ただし、ジャンプは別)

本作では「抜刀」「納刀」アニメーション時や、「攻撃」「防御」「ライン移動」など様々なアクション時においてコントローラーからの移動を受け付けないようになっています。(例えば抜刀のアニメーションは足が停止している為、抜刀しながら歩けてしまうとアニメーション的に不自然になってしまいます)

ただし、移動のロジックはCharacterMovementコンポーネントを使用し、AddMovementInputノードを使って移動している為、GameplayAbilityで移動処理を書くとロジックが煩雑になってしまう可能性がありました。

また、ジャンプしながらの移動はCharacterMovementコンポーネントに設定された値で動いて欲しい為、「全てのアクション時には移動はさせたくないけど、ジャンプ中だけは移動OK」というロジックをプレイヤーのBPで実装する必要がありました。

それを解決しているロジックが以下の通りです。

コントローラーから移動情報を受け取った際に、AbilitySystemコンポーネントが”Character.Action”タグを保持していたらAddMovementInputノードまで処理が届かないようになっています。

ただし、AbilitySystemコンポーネントが”Character.Action.Jump”を持っている場合は上記の条件は無効となります。

(“Character.Action”を配列で持っている理由は、後々他にも条件が増えるのかな?と思って配列で定義してしまいましたが、配列で定義する必要はありませんでした)

上記の解説で「GASを使えば条件分岐なんて不要だぜ!」って息巻いていた結果がコレです。もしかしたら、もっと良い実装方法があるかもしれませんが、本作ではこのようなロジックも混じっています。

本作のGameplayTag一覧

最初にお見せしなかった本作でのGameplayTag一覧は以下の通りです。必要に応じて上記の説明と併せて閲覧して頂ければと思います。

まとめ

フラグ管理を使わないでも、GASを使うことでアクションの同時実行を防ぐ仕組みを取り入れることができました。更にフラグ管理のような分岐ロジックを最小限に抑えることができました。

今回の説明ではGameplayAbilityのメインロジックには一切触れていないので、なんとなく抽象的な解説になっているかと思いますが、まずは抽象的にでも「GASって思ったよりも怖くないな」と思って頂きたいと思い、このような解説となっております。

次回は、本作のGameplayAbilityのロジックにも触れて、もっとディープな世界に連れていければと思います。

No responses yet

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です