GameplayAbilitiesの使い方(タスク編)

GameplayAbilitiesの使い方、最終回となります。

今回はタスク(GameplayTask)について説明します。

タスクはアビリティ実行中に、イベント(GameplayEvent)の発生やモンタージュの再生終了など外部でなんらかのトリガーが発生した場合にコールバックを受け取って処理をするためのシステムです。

このブログのGA_DanceやGA_Flipで使われている"PlayMontageAndWait"というノードも実はタスクの一種で、モンタージュを再生したあと、再生、中断などが行われるとコールバックを戻す機能を持ちます。GA_Dance,GA_FlipはMontageの再生が終わったらアビリティ終了としているので、コールバックはすべてEndAbilityにつないでいます。

EpicのサンプルのActionRPGではC++でGameplayTaskの派生クラスが作られていて、内部で各種コールバックをフックして処理をブループリントに返すノードが作られていますが、そこまで説明すると大量にソースの作成が必要になってしまうので、今回は簡単にありもののイベントをフックするノードを使い概要だけの紹介にしたいと思います。

実際には、ブループリントちゃんがフリップ中に攻撃の当たり判定を持たせ、敵にヒットしたらコールバックイベントを発生させ敵にダメージを与える前処理を行うところまでやってみます。

攻撃用 コリジョンの作成と設定

まずはブループリントちゃんに攻撃用当たり判定を作ります。

コンテンツブラウザからBluePrintChan→BluePrintを開いてBP_BPchanをダブルクリックで開きます。

f:id:nca03132:20180804114710p:plain

コンポーネントを追加を選んでCollisionのSphereCollisionを追加します。

f:id:nca03132:20180804115024p:plain

名前をAttackCollisionとしておきます。

f:id:nca03132:20180804115138p:plain

ビューポートにタブを移して標準のカプセルより一回り大きい感じにスケールしてください。大体2倍ぐらいでいいと思います。

f:id:nca03132:20180804115412p:plain

次はイベントグラフです。

BeginPlayで攻撃判定用のAttackCollisionのNoCollisionにしておきます。

f:id:nca03132:20180804120027p:plain

次に、攻撃開始タイミングで呼び出されるイベントとしてBeginAttack、攻撃終了タイミングで呼び出されるイベントとしてEndAttackをカスタムイベントで作ります。

BeginAttackではAttackCollisionを有効化(QueryOnly)、EndAttack時にAttackCollisionを無効化します。

 

f:id:nca03132:20180804141634p:plain

次に、AttackCollisionが敵に当たった時の処理を書きます。

コンポーネントのAttackCollisionを選択し、詳細の下の方にある"OnComponentBeginOverlap"を選択します。

f:id:nca03132:20180804141908p:plain

 

OnComponentBeginOverlapで、OtherActorが自分自身でなければSendGameplayEventToActorでGameplayEventを発行します。この時、イベントタグには"Event.Attack"を新規に作って設定しておいてください。また入力ピンのGameplayEventData構造体(Payload)の情報はイベント受信側でそのまま受け取れるようになっているので、今回は対象アクターであるTargetを設定しておきましょう。

送信先の設定であるActorは自分自身にして、のちほど設定するGameplayTaskでイベントを受けるようにします。

f:id:nca03132:20180804161748p:plain

 

AnimNofifyState作成

次に、アニメーションモンタージュに設定するための攻撃中通知を作成します。

コンテンツのAbilitiesフォルダで右クリックして"基本アセットを作成"の中の"ブループリントクラス"を選択。

親クラスを"AnimNotifyState"にします。

f:id:nca03132:20180804154948p:plain

アセット名はANS_Attackとしておきます

f:id:nca03132:20180804155042p:plain

ダブルクリックで開いて、関数のオーバーライドからReceivedNotifyBeginを指定します。

f:id:nca03132:20180804155209p:plain

この関数はNotifyStateの開始時に呼ばれるものです。ANS_Attackはアニメーションモンタージュ中で攻撃判定を行う期間を指定するもので、ReceivedNotifyBeginでは先ほどBP_BPchanに実装したBeginAttackイベントを呼び出し、攻撃用の当たり判定を有効にします。

f:id:nca03132:20180804155805p:plain

同様にReceivedNotifyEndもオーバーライドして

EndAttackを呼び当たり判定を無効にするように呼び出します。

f:id:nca03132:20180804155926p:plain

では作成したANS_AttackをフリップアニメーションであるAM_Flipに適用していきましょう。

AM_Flipをダブルクリックでひらいて

f:id:nca03132:20180804160251p:plain

大体20フレームぐらいのところに、通知ステートを追加→ANS_Attackを設定します。

f:id:nca03132:20180804160444p:plain

あとはANS_Attackの終了フレームを26フレームぐらいに持っていけばOKです。

f:id:nca03132:20180804160559p:plain

これで20~26フレームの間はAttackCollisionが有効になりBP_BPchanのAttackCollisionのオーバーラップイベントが起動→SendGameplayEventToActorが呼び出されてGameplayEventが発生するようになります。

 

アビリティでイベントを受け取る

最後に発生させたGameplayEventをアビリティ側で受け取ります。

今回のケースではGA_Flip中に発生する最中のイベントなのでGA_Flipの中でEndAbilityされるまでの間に発生したイベントをフックします。

GA_Flipのイベントグラフを開いて、CommitAbilityの後に"WaitGameplayEvent"のノードを挟み込みます。このノードは実行自体はすぐ完了して実行ピンは右に流れていきますが、タスク自体は残ってイベントが発生するのをキャッチし、そのたびにEvent Receivedの実行ピンを呼び出します。

 入力ピンのEventTagは先ほどSendGameplayEventToActorで設定した"Event.Attack"を受けるように設定してください。

f:id:nca03132:20180804163215p:plain

今回のケースでは、イベント発生時にデータとして引き渡されるGameplayEventDataの内容をPrintStringするようになっています。

実行してみて、最初からいるマネキンにフリップでアタックしてみると攻撃判定発生により

f:id:nca03132:20180804163429p:plain

こんな感じでデバッグ表示されるはずです。

さて、ここまで出来ればあとはダメージを与えるエフェクトを作成しApplyGameplayEffectToTargetなどで対象に設定すればHPを減らすなどの処理ができるはずなのですが、いろいろやってみたんですがノードの引数が上手く用意できず断念しました(汗

もしこの辺ご存知の方がいらっしゃればぜひぜひコメントでお知らせください、お待ちしております。

ちなみにActionRPGでは、専用のGameplayTaskを用意しておりC++レイヤでエフェクトを与えて処理していました。もしみなさんで実装する場合は、直接そちらを参考にしていただければ、比較的しっかりとした実装ができるかと思います。

 

最後に

以上でGameplayAbilitiesの使い方については終わりにしたいと思います。

かなり端折った説明になっていますが、少なくともアビリティ、タグ、アトリビュート、エフェクト、タスクの概要はなんとなくつかめていただけたのではないかと思います。

上記を抑えておけば、ActionRPGのソースもかなり楽に読めるはずなので、そういった方々のお役に立てる記事になっていれば幸いです。

また今回は全く触れていませんが、GameplayAbilitiesはオンラインゲームでの使用を想定に入れた仕組みになっています。アトリビュートの変更や、アビリティの実行判定をサーバーで行い結果をクライアントに通知する仕組みなども入っているはずなので、そういった用途を想定しているかたにもピッタリな機能なのではないかと思います。