MiniAbilitySystem
以前の記事で紹介したGameplayAbilitySystemは大変便利なのですがC++必須でアビリティ、アトリビュート、エフェクト、タスクと機能も豊富なため、プログラムに馴染みのない人にはちょっとハードルが高く取り回しも難しい機能かと思います。
そこで、機能を必要最低限のアビリティに絞り、BPだけで構築した超簡略版のMiniAbititySystemというものを作ってみたので紹介したいと思います。
MiniAbilitySystemとは
そもそもMiniAbilitySystemって何なのよ?って話から始めたいと思いますが、みなさんがアクションゲームをプレイするとき、キャラクターは基本的な移動のほかに、「攻撃」「コンボ」「回避」「魔法」といったアクションを行うかと思います。
また敵から攻撃を受ければ「ダメージ」状態にもなりますし、HPが0になれば「死亡」状態になります。
ゲーム制作においてはこれらのステートが適切に遷移するようにプログラムで制御を書いていくわけですが、この辺の処理はさまざまなロジックが絡み合って複雑なプログラムになりがちです。
MiniAbilitySystemでは、これら各ステートごとにアビリティという形で個別にBPを作成します。それぞれのBPにそのステート専用の動作を記述していくことによりブループリントがスパゲティ状態になりづらくします。
また、各アビリティにはタグ(GameplayTag)を設定することができ、アビリティを実行する際に「このタグがあるアビリティが存在している間は、このアビリティは起動できない」、「このアビリティを実行するためには、このタグが付いているアビリティが存在しないといけない」「このアビリティが起動すると、このタグを持ったアビリティはキャンセルされる」といった排他制御を行うことができます。
今回紹介する例では、攻撃用に3種類のアビリティを作成しますが、これらは3連コンボで発動させる予定です。
攻撃1は攻撃ボタンが押されたら起動し、攻撃2も攻撃ボタンが押されたら起動するように設定します。ただ、このままだと攻撃1,2が同時に実行されてしまいますよね?
これを防ぐために攻撃2は、攻撃1で設定されるタグがない状態では発動しないように設定します。これにより攻撃ボタンを押していくと、かならず攻撃1、攻撃2の順でアビリティが実行されるようになります。攻撃3も同様で、攻撃2のタグがないと実行されないようにしておきます。これで3連コンボの完成です。
また反対にダメージを受けた場合は、攻撃1でも2でも3でも、それらは全部キャンセルしてダメージアビリティを実行するのを最優先にしなくてはなりません。これもタグによる排他制御で制御可能です。ダメージアビリティ実行時は攻撃1,2,3が保持しているタグを持つアビリティは全部キャンセルさせるようにタグの指定を書いておけばいいのです。
このように、大抵の状態遷移はタグの設定により管理可能なので、各ステートによる条件の分岐をBPで書かずにすむ、というメリットを享受できます。
ま、とはいえ実物を見ないことにはイメージもわかないと思いますので、以降、順を追って実際にBPを組みながら説明していきたと思います。
あ、なおUE4エディタは個人的な理由で言語が英語になっております。日本語を使われている方がほとんどだと思いますので、その辺は日本語版機能に読み替えて読んでいただければと思います。
環境のセットアップ
MiniAbilitySystemを入手する
MiniAbilitySystemはここから入手できます。UE4のバージョンは4.22です。
git使ってる方はcloneしてもよいですし、使ってない方はzipをダウンロードでもOKです。
ThirdPersonのプロジェクトを用意する
次にベースとなるThirdPersonプロジェクトを新規作成してください。
で、先ほど用意したMiniAbilitySystemのプロジェクトからMiniAbilitySystemフォルダをMigrateでもってくるか、手作業でContentディレクトリにコピーしてください。
ContentBrowserでは
こんな感じになるかと思います。
Shinbiをプロジェクトに追加・設定する
今回はShinbiを使います。ParagonアセットのShinbiをプロジェクトに追加してください。
追加が終わったらThirdPersonCharacterのスキンメッシュとアニメをShinbiに入れ替えます。
このThirdPersonCharacterを開いて
ComponentsのMeshを選択して
詳細のSkeletalMeshを「Shinbi」、AnimClassを「Shinbi_AnimBlueprint」に設定します。
これでコンパイル&実行すればマネキンの代わりにShinbiが歩くようになっているはずです。
あと最後に、ShinbiのアニメーションBPは常に最初にレベルスタートポーズをとるようになっているので、外してしまいましょう。
このShibi_AnimBlueprintを開いて、EventGraphのBeginPlayにつながっている
このMontagePlayを削除しておきます。
これで下準備は完了です。
アビリティマネージャの追加とアビリティの作成
キャラクタへのアビリティマネージャの追加
では、キャラクタBPにアビリティマネージャを追加しましょう。
このThirdPersonCharacterをダブルクリックで開いて、コンポーネントの追加でBP_MiniAbilityManagerComponentを追加しましょう。
こんな感じになります。
アビリティクラスの作成と登録
次にアビリティクラスを作成します。
ThirdPersonBP→Blueprintsの中で右クリックからBlueprint Classを作成し、親クラスには
このBP_MiniAbilityを選択しましょう。
作成したブループリントの名前はBP_Attack1Abilityとしておきます。
次のこのBPを開いて攻撃に関するノードを追加していきます。
アビリティクラスからアビリティインスタンスが生成された直後に呼ばれるInitializeイベントをオーバーライドして
GetAbilityManagerでMiniAbilityManagerComponentを取得しGetOwnerでオーナーであるActorを引っ張ってきてCharacterにキャストしCharacter変数に保存。さらにCharacterからSkeletalMeshコンポーネントを引っ張ってきてMesh変数に保存します。
次に、アビリティインスタンスがアクティベートされたときにくるActivateAbilityをオーバーライドして
Mesh変数を使ってPlayMontageを呼び出します。再生するモンタージュ(Montage to Play)にはPrimaryMelee_B_Slow_Montageを指定します。OnBlendOut,OnInterruptedのピンの先にEndAbilityを配置しモンタージュ再生が終了次第、アビリティインスタンスが終了するようにします。
注意点として、アビリティは終了時に絶対に「EndAbility」を呼ぶ必要があります。
でないとアビリティインスタンスはずっとメモリに残り続け動作も重くなりいずれメモリがあふれます。
最後にこのアビリティクラスは何のタグを受け取るとアビリティインスタンスを生成・アクティベートするかの設定を行います。
クラスのデフォルト設定を選択し、詳細の
このActivationTagsに起動するタグを指定します。今回は「Activation.Attack」というタグを設定します。
Edit→Add New Gameplay Tagを選択しNameのところに「Activation.Attack」と記述します。
設定できると
こんな感じになります。
このアビリティクラスを先ほどThirdPersonCharacterに追加したアビリティマネージャに登録します。
ThirdPersonCharacterのBPを開きComponentsの中のBP_MiniAbilityManagerComponentを選択し、詳細の中のMiniAbilityManagerの中にあるInitialAbilityClassesに追加します。
このプラスを押して0の項目にBP_Attack1Abilityを設定します。
これで、アビリティマネージャがアビリティクラスを保有した状態となりました。
アビリティの起動
ではマウスを左クリックしたらBP_Attack1Abilityがアクティベートされるようにノードを書き足してみましょう。
ThirdPersonCharacterのBPを開いてマウスイベントのLeftMouseButtonをオーバーライドします。
アビリティマネージャが、指定したタグのアビリティをアクティベートさせる関数はActivateAbilityClassesWithTagsです。
アクティベートタグにはさきほどの「Activation.Attack」を指定し、LeftMouseButtonからつなぎます。
これでマウスを左クリックするとアビリティマネージャが自分が保持しているアビリティクラスリストの中で、起動タグに「Activation.Attack」を持つものをインスタンス化しアクティベートするようになりました。
コンパイルしてプレイしてみてください。
左クリックでShinbiがクルリと剣を振れば成功です。
タグによるアビリティの排他制御
さて、Shinbiが攻撃モーションとってくれるようになりました。まずはめでたい。
が、実は現時点で左クリックを連打してみると・・・・モーションがキャンセルされて何度も最初から開始されます。
これは考えてみれば当然の話で、アビリティインスタンス実行中に、再度左クリックされると(同じBP_Attack1Abilityクラスではあるけど)別のアビリティインスタンスが生成されモンタージュを最初から再生します。これにより以前から動作していたアビリティは、同スロットで再生されたモンタージュによりPlayMontageが中断されEndAbilityに処理が流れて終了します。
これを防ぐためにはアビリティ実行中は別のアビリティを実行しないようにする排他制御が必要です。
具体的にはアビリティが実行されたときに「実行中だよ」的なステートのタグを保持するようにして、再度アビリティが起動されそうになったときに「実行中だよ」というステートタグを持つアビリティが存在したら起動しないように設定します。
アクティベート中に保持するタグを設定
まずは実行開始したアビリティが「実行中だよ」というステートを持つようにタグを設定します。
BP_Attack1AbilityのClassDefaultsをクリックし
詳細のActivationOwnedTagsのところに「State.Montage.UpperBody.Attack1」というタグを追加します。
これでBP_Attack1Abilityがアクティベートされたあと上記タグを保持するようになります。
アビリティのアクティベーションをブロックするタグを設定
次に実行中のアビリティが「実行中だよ」というタグを持っていたら、自分自身のアビリティをアクティベートしないようタグ設定を行います。
詳細のActivationBlockedTagsに「State.Montage」というタグを追加します。
排他の動作原理
これでコンパイルして実行すると、左クリックを連打してもキャンセルが発生しなくなります。
原理としては、まずアビリティインスタンスがアクティベートされたときに「State.Montage.UpperBody.Attack1」というタグを持つようになります。さらに次のアビリティインスタンスがアクティベートされそうになったとき、すでにアクティベート済みのアビリティインスタンスの中でActivationBlockedTagsで指定されてた「State.Montage」というタグを持っているものがいないか調べます。
最初のアビリティは「State.Montage.UpperBody.Attack1」を持っているので「State.Montage」も持っているものと判定され(GameplayTagsでhasTags系の判定をするドットの後半部分はワイルドカード判定となる)アビリティのアクティベートが中断されます。
最初のアビリティインスタンスのモンタージュ再生が終わるとEndAbilityによりアビリティはいなくなりブロックタグもひっかからなくなるので、アビリティのアクティベートが再度実行されるようになります。
アビリティのアクティベーションに必要なタグ
次に、アビリティをアクティベーションするために必要なタグについて考えてみましょう。
先ほどの排他の場合は、すでにアクティベーション済みのアビリティが特定のタグを持っている場合は自アビリティがアクティベーションできないという状況でしたが、反対にすでにアクティベーション済みのアビリティが特定のタグを持っていないと自アビリティがアクティベーションできない、というタグも存在します。
ここではそれを使ってコンボを作ってみましょう。
攻撃2のアビリティクラスの作成
まずBP_Attack1Abilityを複製してBP_Attack2Abilityとします。
BP_Attack2Abilityを開いてPlayMontageで指定しているモンタージュをPrimaryMelee_C_Slow_Montageに変更します。
次にタグ設定です。
BP_Attack2AbilityのClassDefaultsをクリックし詳細のタグ設定を確認します。
2段目の攻撃も起動タグはActivation.Attackのままでよいのですが、前提条件として1段目の攻撃実行中である必要があるのでActivationRequiredTagsに「State.Montage.UpperBody.Attack1」を指定します。これでBP_Attack2Abilityは一段目の攻撃を実行中でないとアクティベートされなくなります。
またActivationOwnedTagsは「State.Montage.UpperBody.Attack2」にしておきます。
2段目の攻撃は1段目の攻撃中に発生するためActivationBlockedTagsはクリアしておきます。
上記の結果、以下のようなタグ設定となります。
攻撃2のアビリティクラスの登録
よく忘れるのですが、アビリティクラスは作成しただけでは実行されません。当たり前ですがアビリティマネージャに登録する必要があります。
ThirdPersonCharacterのBPを開き、コンポーネントでBP_MiniAbilityManagerを選択し、詳細のInitial Ability Classesに先ほど作成したBP_Attack2Abilityを設定します。
この状態でコンパイルし実行してみてください。
・・・・
はい、攻撃の1段目が出なくなり、常に2段目のみ発生しています。
なぜでしょうか・・・・?
結論から言うと現時点ではMiniAbilitySystemの仕様となっています。
MiniAbilitySystemは登録されているアビリティクラスを配列の頭からサーチしていきマッチした条件のアビリティをアクティベートします。
現状は左クリックが発生すると配列の0にいるBP_Attack1Abilityがアクティベートされるのですが、その時点でBP_Attack2Abilityの実行要件を満たしてしまい、配列の1番目のBP_Attack2Abilityも連続でアクティベートしていまうのです。その結果、常に攻撃の2段目が発生するわけです。
この現象は、1個ずつアクティベート可能なアビリティを見ていくのではなく、アクティベート可能なアビリティを最初にリストアップしておき、そのあとでそれらを連続アクティベートするなどの仕組みで回避することが可能なのですが、それはそれで同一フレームで直前のアビリティの挙動によるほかのアビリティの排他制御ができなくなるため、いろいろ悩んだ末、あえてこのままの仕様としています。
なので後発的に発生するアビリティは意識してリストの頭のほうに持っていくようにしてください。
さて、BP_Attack2Abilityを先頭に持ってきました。再度気を取り直してプレイしてみてください。
左クリックでBP_Attack1Ability,BP_Attack2Abilityの順番で攻撃が出るはずです。
MontageNotifyによるモンタージュとの連携
2連コンボが出せるようになりましたが、まだ問題があります。
左クリックを連打すると1段目が発生したすぐあと2段目が出せてしまいます。
本来なら1段目の攻撃が終わるぐらいのタイミングで2段目の入力を受け付けたい感じです。これを解消するためにモンタージュのNofityとの連携を行います。
コンボ入力を受け付けるタイミングにMontageNotifyを設定する
PrimaryMelee_B_Slow_Montageを開いてください(PlayMontageで指定している個所の右側の虫眼鏡をクリックすると早いです)。
Shinbiの1段目の攻撃が再生されていると思いますが、この中で2段目の攻撃入力を受け付ける範囲にMontageNofityWindowを設定します。
Notifiesのスライダーで10フレームぐらいの位置で右クリックしAddNotifyState→MontageNotifyWindowを選択します。
AnimNotify_PlayMontageNotifyWindowが作られますがこれは範囲指定なので終了時間をモンタージュの最後のほうに移動させます。
AnimNotify_PlayMontageNotifyWindowをクリックして右側の詳細パネルのNotifyNameを「Ready.Montage」と指定しておきましょう
これで10フレームと最終フレーム間際で、アビリティのBPに設置してあるPlayMontageのOnNotifyBegin,OnNotifyEndピンに処理が来るようになります。
このときNotifyNameには「Ready.Montage」の文字列が入ってきます。
アビリティのBPで次アビリティ起動許可用のタグを設定する
今度はBP_Attack1Abilityを開いてください。
先ほど書いたようにMontageNotifyの設定によりOnNotifyBegin,OnNotifyEndに処理が来るようになります。
ここではOnNotifyBeginで次アビリティ起動許可用の「Ready.Montage.UpperBody.Attack2」タグを追加し、OnNofityEndで削除するようにします。AddAbilityTagとRemoveAbilityTagを使います。
これでコンボ連携可能時間の間「Ready.Montage.UpperBody.Attack2」が存在するようになりました。
このタグを利用するのはBP_Attack2Abilityなので、そちらを開いてください。
現在アビリティを実行するのに必要なタグとしてActivation RequiredTagsには「State.Montage.UpperBody.Attack1」が設定されていますが、これを「Ready.Montage.UpperBody.Attack2」に書き換えます。
これで適切なタイミングでのみコンボがつながるようになるはずです。
3段目のコンボへ
やり方は同じなので詳細は載せませんが、3段目のコンボも同じ要領で作ることができます。
BP_Attack2AbilityをコピーしてBP_Attack3Abilityを作り、PlayMontageで再生するアニメーションは「PrimaryMelee_D_Slow_Montage」を指定します。
ActivationOwnedTagを「Stage.Montage.UpperBody.Attack3」とし、ActivationRequiredTagsに「Ready.Montage.UpperBody.Attack3」を指定します。
「PrimaryMelee_C_Slow_Montage」を開いて先ほどと同じ要領でコンボのタイミングのMontageNotifyを追加し、BP_Attack2AbilityのPlayMontageのOnNotifyBegin、OnNotifyEndにAddAbilityTagとRemoveAbilityTagをつなげて「Ready.Montage.UpperBody.Attack3」を追加・削除すればOKです。
ダメージによるモンタージュの中断
実装紹介の最後に通常アクションより優先度が高いダメージのアビリティについて、簡単に実装してみたいと思います。
モンタージュの作成
まずダメージのモンタージュを作ります。Shinbiのアニメーションに入っている「HitReact_Front」を右クリックしCreate→CreateAnimMontageを選択します。
これでHitReact_Front_Montageが作成されるので、開きます。
ここで、グループ設定を「DefaultGroup.UpperBody」に変更して保存します。
アビリティBPの作成とアビリティマネージャへの登録
BP_Attack1Abilityを複製してBP_HitReactAbilityという名前で保存し開きます。
PlayMontageで再生するモンタージュの指定を先ほど作った「HitReact_Font_Montage」に変更します。
今回はNotify系の処理は行わないのでOnNotifyBegin,OnNotifyEndから先につながっているピン・ノードは削除します。
タグの設定
ダメージが起動するタグは「Activation.HitReact」とします。
このアビリティ実行中はほかのアビリティのアクティベートを抑制したいのでBlockAbilitiesWithTagで「Activation」を指定しておきます。基本的に今まで作ったアビリティは起動タグを「Activation.***」に設定しているので、このダメージアビリティ実行中はほかのアビリティはすべてアクティベートされなくなります。
また、状態タグであるActivationOwnedTagsには「State.Montage.UpperBody.HitReact」を設定しておきます。
テスト実行
ではテスト実行をしてみましょう。とはいえ、このサンプルではダメージを与える仕組みを作っていませんので、右クリックしたら敵からダメージを与えられたリアクションをするように設定してみます。
ThirdPersonCharacterのBPを開いてマウスイベントのRightMouseButtonをオーバーライドします。
アビリティマネージャからActivateAbilityClassesWithTagsにピンを接続して
アクティベートタグには「Activation.HitReact」を指定し、RightMouseButtonからつなぎます。
これで右クリックするとダメージのリアクションが発生するようになります。
攻撃アビリティを出している最中でも右クリックするとダメージアビリティがアクティベートされ攻撃アビリティは中断します。またダメージモーション再生終了までほかのアビリティのアクティベートを受け付けなくなります。
MiniAbilitySystemの問題点
いろいろ小回りが利いて便利なMiniAbilitySystemにも問題点があります。
- Delayがない
BP_MiniAbilityの基底クラスはUObjectなのでDelayノードが使えません。
何か処理を実行したあと待つことができないのでいろいろ不便な状況に遭遇することがあります。
どうしても必要であればSetTimerEventは動くので、それでなんとか回避する感じになります。 - Timelineがない
これもDelayがないのと同じ原因です。
Timelineが必要な場合はコンポーネントかキャラ側に処理を逃がしてアビリティからそれを呼ぶ形が無難かと思います。
この辺、使用上は要注意となります。
今回紹介しきれなかったちょっとした小話
- タグの設計
今回のサンプルではさらさらと適当にタグをつけているように見えますが、タグ設計は超大事です。ピシッとしたポリシーなしに適当にタグ付けしてると、どんな条件で何が起きるのかさっぱりわからなくなるので、頭の中でタグとアビリティの連携イメージをしっかり固めてから実装しましょう。 - onEndAbility
今回使う機会がありませんでしたが、このイベントもすごく大事です。
アビリティ終了時に必ず通る処理なのでアビリティ実行中でキャラクターのパラメータを一時的に書き換えたりした場合、ここで戻すことが可能です。
自作のゲームでは飛行状態やロープスイング状態もアビリティで管理していて実行中移動モードを書き換えているので、それを戻すのに使ってたりします。
あとはダッシュしたあとで移動スピードを戻すのとかにも使えそうです。 - Tickについて
一応、標準状態でアクティベートされたアビリティはTickを受け取ることができますが、アビリティの数が多くなると処理速度的にオーバーヘッドが大きくなるため、不要であればコンポーネント側でTickをオフしてしまうことをお勧めします。
何かしらの状態変化をアビリティ側で確認したいのであれば都度SendMessageで情報を通知するという手が使えるかもしれません。 - 攻撃の判定
攻撃の当たり判定などもモンタージュ上で攻撃判定したいタイミングにMontageNotifyを仕込んで、PlayMontageのOnNotifyBeginでコールバックを受けて、そのタイミングでコリジョンチェックを行うことにより実現できるかと思います。
実際にダメージを与える処理は、ぱっと思いつく感じだと「UE4で実装されているApplyDamageを使う」「ダメージ情報をMiniAbilityParamに設定して、相手キャラクタのアビリティマネージャが保持しているダメージアビリティを都度アクティベートする」「常時ダメージアビリティをアクティベートするようにしておき、そこにSendMessageでダメージ情報を送る」といったあたりが思いつきますが、まぁ好みで構わないと思います。 - 武器の切り替え
剣から槍などに装備を変えた場合、当然ながら使用可能なアビリティも変化します。そういったときはRemoveAbilityClassesで旧武器のアビリティを削除し、AddAbilityClassesで新武器のアビリティを追加すれば、比較的容易に武器切り替えシステムが実装できます。
最後に
MiniAbilitySystemとかたいそうなこと言ってますが、ごめんなさい、ぜんぜんちんまいソースです。単にシステムって言ってみたかっただけですw
でもコンパクトでソース見れば仕組みはすぐ理解してもらえると思うので、自分好みに組み替えてどんどん活用・応用していってもらえればと思います。
バグの指摘や、こうしたほうがもっとよくなるよ!などのアドバイスもどんどこお待ちしています。
インテリアマッピングの基礎
本記事では、スパイダーマンで使われて再脚光を浴びているインテリアマッピングの手法について解説してみたいと思います。
インテリアマッピングとは文字通りビルの壁面などに格子状に表示される室内の様子をモデルではなくマテリアルで描画しようというもので、壁、床、天井ごとにテクスチャを用意して、それを実際に壁があるかのようにマテリアル上でレイキャスト計算してピクセルを描画します。
技術自体は結構昔からあるようで、UnrealEngine4でも2014年にフォーラムにStefanderさんが実装例を投稿してくださっています。
ただ投稿自体に解説記事がなくフォーラムのやりとりも英語なので、今回はこの実装について簡単な解説を書くことにしました。
実行環境をそろえていただくため、上記フォーラムの最初の書き込みからInteriorMapping.rarをダウンロード、展開しておいてください。
■使ってみるよ
InteriorMapping.rarを展開しInteriorMapping.uprojectを開くと
とバージョンを聞かれるので自分が使っているバージョンを選んでください。本記事では4.20前提で進めていきます。
エディタが開くと、いつもの空マップなので基本のキューブをレベル上に配置し、そこにMaterials/M_Interiorをドラッグ&ドロップしてマテリアルを適用してください。
これでマテリアルが適用されると・・・
はい、できました。
テクスチャが変なところで切られてるとか、格子模様が部屋構成とあってないとか、細かい問題はありますが調整の範疇です。同梱されているInteriorMapping.mapのレベルを開くと、この辺がちゃんと調整された
という絵面を見ることができます。
手軽!簡単!便利!
■M_Interiorについて
では、ここで設定したM_Interiorというマテリアル、どうなっているのでしょうか。
ちょっと中を覗いてみたいと思います。
ブログだと字がつぶれて見づらいと思うので、実機を見ながら読み進めていただけるとよいかと思います。
赤線で囲った部分は格子表示なので今回説明は省略します。
表示が気になる場合は、MF_InteriorMappingSimpleのWallColorをそのままベースカラーにつないでしまいましょう。
マテリアルのロジック自体は中央にあるMF_InteriorMappingSimpleに集約されているようで、パラメータを大量に与えています。
まずは、このパラメータ群について説明します。
以下の内容を参考に適当にいじってみて、見た目にどういう影響を与えるか実験してみるとよいでしょう。
Room Floor(T2d)
床テクスチャの指定です
Room WallXY(T2d)
側壁(YZ平面)テクスチャの指定です。
Room WallZY(T2d)
奥壁(XZ平面)テクスチャの指定です。
Room Frequencies(V3)
直訳すると部屋の周波数・・なんですが意味がわかりませんね、具体的にはxyzそれぞれの軸方向に対して単位距離あたり何部屋作るかの指定となります。
UE4の単位距離は1cmで、そんな距離には1部屋も入らないので小数で指定します。
M_Interiorでは1mで1.5部屋配置させているため
1m(=100cm)で1.5部屋→1cmだと0.015部屋
となり、数値は0.015が指定されています。
※1mで1.5部屋なので2部屋きっちり表示するには100cm*(2/1.5)=133.333..cmの辺が必要です。エディタ上に1mキューブを設置しM_InteriorMappingSimpleをスケールを1.33..するときっちり2x2x2部屋表示になります。
が、壁の座標が境界とばっちり重なってしまいZオーダーの問題でちらつくのでその辺は随時調整しましょう。
Tiling WallZY(V2)
奥壁(XZ平面)テクスチャの単位距離(1cm)あたりのタイリング数です。
このサンプルでは1.5mで1タイルとしているので、数値の大きさは0.015となります。
R:0.015(X方向のタイリング数値)
G:-0.015(Z方向のタイリング数値,Z方向のUVとテクスチャの向きが逆なのでマイナス)
となります。
Tiling WallXY(V2)
側壁(ZY平面)テクスチャの単位距離(1cm)あたりのタイリング数です。
奥壁と同様、数値の大きさは0.015です。
R:0.015(Y方向のタイリング数値)
G:-0.015(Z方向のタイリング数値,Z方向のUVとテクスチャの向きが逆なのでマイナス)
となります。
Tiling Ceiling/Floor(V2)
床・天井(XY平面)テクスチャの単位距離(1cm)あたりのタイリング数です。
奥壁と同様、数値の大きさは0.015です。
R:0.015(X方向のタイリング数値)
G:-0.015(Y方向のタイリング数値)
となります。
Room Ceiling(T2d)
天井テクスチャの指定です。
■MF_InteriorMappingSimpleの解説
さて、いよいよインテリアマッピングの本丸、MF_InteriorMappingSimpleの説明に入っていきたいと思います。
このマテリアル関数では、大きく分けて
・レイキャストで壁面(側壁(ZY平面)、奥壁(XZ平面)、天井・床)に対して衝突する座標の計算
・各壁面で一番最初に衝突する壁の特定
・各壁面でのUV計算
の3つです。
早速ノードを見ていきましょう。
・レイキャストによる壁面(XZ,ZY,XY)の衝突座標の計算
まずはレイキャストによる壁面衝突座標の計算です。
この「Calculate walls and ray fractions」というコメントでくくられている部分で計算を行っており、描画するワールド座標、オブジェクトの中心座標、カメラ座標、部屋の周波数から、衝突点を算出しています。
ノードだけだと理解するのが難しいので図を描きました。
・・・このご時世に手書きですいませんorz。
3次元だと説明しずらいのでxy平面の2次元について考えてみます。
この例ではCameraが原点、Objectがオブジェクトの原点、(字が汚くて見づらいですが)Tが描画座標としています。
①がオブジェクトの原点から描画座標へのベクトルです。
②はオブジェクト原点からカメラ座標へのベクトル
③はカメラ座標から描画座標へのベクトル
となっており、ノードの計算とも合致しています。
④はノードの動きをしっかり追わないとわからないですね。
まず①の続きから追っていくと、①の各軸ごとにRoomFrequenciesと掛け算を行っています。これで何がわかるのでしょうか?
RoomFrequenciesは単位距離当たりの部屋の数です。これにオブジェクト原点から描画座標へのベクトル(軸ごとの距離)をかけるわけですから、計算結果は軸ごとの部屋座標になります。x方向に何番目、y方向に何番目の部屋にいるか?なので、上図の例で方眼紙の1cm平方を1部屋の区切りとするとTの部屋座標は(4.2,4.5)ぐらいの位置になるかと思います(オブジェクトを原点としてx,y方向への話ですよ、念のため)。
次にFloorで小数点以下が切られます。上図の例だとFで(4,4)ですね。オブジェクト原点寄りの部屋の区切りポイントとなります。
その後、MF_Step3が足されます。・・・MF_Step3って何でしょうか?
これはMF_Stepのノードです。MF_Step3はこれのベクター版なので、MF_Stepの動作を見れば内容は理解できるはずです。
MF_Stepの入力にxとaがあり、xがaより大きい場合に1を返し、それ以外は0を返す単純な関数です。
MF_Step3ノードを見てみると、入力aは(0,0,0)、入力xは③(カメラ座標から描画座標へのベクトル)が接続されているので③の各軸要素がプラスであれば1が返ります。
手書き図だと③はx軸方向にプラス方向、yはマイナス方向に伸びているのでMF_Step3の結果としては(1,0)となります(本当は結果は3軸ですが、手書き図は2次元なので、ここでは(1,0))
MF_Step3の結果と先ほどのFloorで切ったF点の部屋座標(4,4)を足し算するとS点の部屋座標(5,4)となります。
さてこのS点は何者なんでしょう?
実はS点はカメラから描画座標へのベクトルである③を伸ばしていった際、仮にRoomFrequenciesで分割された部屋が存在したとすればどこの壁にぶつかるか、奥の壁の部屋座標(図の青い波線部分)を計算したものとなります(これのためにF点を必要に応じてMF_Stepでカメラの奥行方向にずらしS点を生成した)。
S点の部屋座標をRoomFrequenciesで割れば、S点は通常の座標値に戻ります。
最後に⑤ですが、こちらは④/③の値となっています。
この数値の意味について考えてみましょう。。
⑤は当然x,y,z各要素ごとに③を何倍すると④になるか?という値になるのですが、ここで手書き図右下の③x(③のx成分)と④x(④のx成分)に注目してください。
⑤x=④x/③x
となっています。
これはx軸について注目すると③xに⑤xをかけると④xになるという極めて当たり前の話ですが、よく注意して考えると③ベクトル全体に⑤xをかけると、③ベクトルはy軸に平行な部屋の奥の壁の衝突点に到達することに気が付きます(③ベクトルの点線の先)。
同様に③ベクトルに⑤yをかけると③ベクトルが伸びていったときに衝突するx軸に平行な奥の壁の座標が求まります。⑤zも同様です。
これによって衝突座標の計算が可能となります。
■各壁面で一番最初に衝突する壁の特定
ここはとても簡単です。
さきほど⑤を求めたので、⑤のx,y,z成分のうち一番小さいものが最初に当たる壁となります。あとは当たる壁からテクスチャサンプルしてくればOKです。
床・天井だけは③zの正負によって天井に当たっているか、床に当たっているか判定してテクスチャを分けています。
■各壁面でのUV計算
各テクスチャごとのサンプリングはほぼ内容は一緒なので、ここでは側壁(ZY平面)についてのみ解説します。
まず③に⑤xをかけてカメラ位置を起点とした側壁の衝突点を求めます。
そこに②のオブジェクトからカメラへのベクトルを足します。
これにより計算結果は、オブジェクトを起点とした衝突点へのベクトルになります。
側壁(ZY)平面はx軸の値が固定で、y,z座標値がUVの計算対象となるためG,Bでマスクをかけます。そこにタイリング係数をかけます。タイリング係数は単位座標当たりのループ数なので、そのまま側壁のUV値となります。
最後に指定のテクスチャからサンプリングします。
奥壁、天井・床も同様に、上記の計算でサンプリング可能です(※天井・床のみ③zの正負で、テクスチャを変える必要があります)
■まとめ
以上で説明は終わりです。
把握しづらいところもあると思うので、最初は「???」となるかもしれませんが、ノードとにらめっこしてるうちに理解できるようになると思います。
あとはテクスチャを差し替えるなり、タイリング値を変えてループしてることに気づきづらいよう工夫をしたり、手前を外壁テクスチャでマスキングしたりすれば、クオリティはグンと上がるはずです。
■この後は?
今回ご紹介したのは本当に基礎の部分だけです。
本格的な運用としては、光源処理や法線パス、エミッシブ、インテリアの工夫などまだまだやれること満載です。
資料として
UDK | DevelopmentKitGemsInteriorMappingJP
これ張り付けておきます。
UE3用の資料でノードの流れが右から左なのでちょっと面喰いますが、基本的にマテリアルノードは似たものがあるのである程度流用はできると思います。
また、今回は試していませんがStefanderさんの実装には、MF_InteriorMappingSimpleの高機能版でMF_InteriorMappingというものも用意されているようです。こちらを使ってみてもいいかもしれませんね。
みなさんの反響があるようであれば、高機能版の解説も検討してみたいと思います。
また、なにかわからないこと質問などあれば、どしどしコメントしてください!
GameplayAbilitiesの使い方(タスク編)
GameplayAbilitiesの使い方、最終回となります。
今回はタスク(GameplayTask)について説明します。
タスクはアビリティ実行中に、イベント(GameplayEvent)の発生やモンタージュの再生終了など外部でなんらかのトリガーが発生した場合にコールバックを受け取って処理をするためのシステムです。
このブログのGA_DanceやGA_Flipで使われている"PlayMontageAndWait"というノードも実はタスクの一種で、モンタージュを再生したあと、再生、中断などが行われるとコールバックを戻す機能を持ちます。GA_Dance,GA_FlipはMontageの再生が終わったらアビリティ終了としているので、コールバックはすべてEndAbilityにつないでいます。
EpicのサンプルのActionRPGではC++でGameplayTaskの派生クラスが作られていて、内部で各種コールバックをフックして処理をブループリントに返すノードが作られていますが、そこまで説明すると大量にソースの作成が必要になってしまうので、今回は簡単にありもののイベントをフックするノードを使い概要だけの紹介にしたいと思います。
実際には、ブループリントちゃんがフリップ中に攻撃の当たり判定を持たせ、敵にヒットしたらコールバックイベントを発生させ敵にダメージを与える前処理を行うところまでやってみます。
攻撃用 コリジョンの作成と設定
まずはブループリントちゃんに攻撃用当たり判定を作ります。
コンテンツブラウザからBluePrintChan→BluePrintを開いてBP_BPchanをダブルクリックで開きます。
コンポーネントを追加を選んでCollisionのSphereCollisionを追加します。
名前をAttackCollisionとしておきます。
ビューポートにタブを移して標準のカプセルより一回り大きい感じにスケールしてください。大体2倍ぐらいでいいと思います。
次はイベントグラフです。
BeginPlayで攻撃判定用のAttackCollisionのNoCollisionにしておきます。
次に、攻撃開始タイミングで呼び出されるイベントとしてBeginAttack、攻撃終了タイミングで呼び出されるイベントとしてEndAttackをカスタムイベントで作ります。
BeginAttackではAttackCollisionを有効化(QueryOnly)、EndAttack時にAttackCollisionを無効化します。
次に、AttackCollisionが敵に当たった時の処理を書きます。
コンポーネントのAttackCollisionを選択し、詳細の下の方にある"OnComponentBeginOverlap"を選択します。
OnComponentBeginOverlapで、OtherActorが自分自身でなければSendGameplayEventToActorでGameplayEventを発行します。この時、イベントタグには"Event.Attack"を新規に作って設定しておいてください。また入力ピンのGameplayEventData構造体(Payload)の情報はイベント受信側でそのまま受け取れるようになっているので、今回は対象アクターであるTargetを設定しておきましょう。
送信先の設定であるActorは自分自身にして、のちほど設定するGameplayTaskでイベントを受けるようにします。
AnimNofifyState作成
次に、アニメーションモンタージュに設定するための攻撃中通知を作成します。
コンテンツのAbilitiesフォルダで右クリックして"基本アセットを作成"の中の"ブループリントクラス"を選択。
親クラスを"AnimNotifyState"にします。
アセット名はANS_Attackとしておきます
ダブルクリックで開いて、関数のオーバーライドからReceivedNotifyBeginを指定します。
この関数はNotifyStateの開始時に呼ばれるものです。ANS_Attackはアニメーションモンタージュ中で攻撃判定を行う期間を指定するもので、ReceivedNotifyBeginでは先ほどBP_BPchanに実装したBeginAttackイベントを呼び出し、攻撃用の当たり判定を有効にします。
同様にReceivedNotifyEndもオーバーライドして
EndAttackを呼び当たり判定を無効にするように呼び出します。
では作成したANS_AttackをフリップアニメーションであるAM_Flipに適用していきましょう。
AM_Flipをダブルクリックでひらいて
大体20フレームぐらいのところに、通知ステートを追加→ANS_Attackを設定します。
あとはANS_Attackの終了フレームを26フレームぐらいに持っていけばOKです。
これで20~26フレームの間はAttackCollisionが有効になりBP_BPchanのAttackCollisionのオーバーラップイベントが起動→SendGameplayEventToActorが呼び出されてGameplayEventが発生するようになります。
アビリティでイベントを受け取る
最後に発生させたGameplayEventをアビリティ側で受け取ります。
今回のケースではGA_Flip中に発生する最中のイベントなのでGA_Flipの中でEndAbilityされるまでの間に発生したイベントをフックします。
GA_Flipのイベントグラフを開いて、CommitAbilityの後に"WaitGameplayEvent"のノードを挟み込みます。このノードは実行自体はすぐ完了して実行ピンは右に流れていきますが、タスク自体は残ってイベントが発生するのをキャッチし、そのたびにEvent Receivedの実行ピンを呼び出します。
入力ピンのEventTagは先ほどSendGameplayEventToActorで設定した"Event.Attack"を受けるように設定してください。
今回のケースでは、イベント発生時にデータとして引き渡されるGameplayEventDataの内容をPrintStringするようになっています。
実行してみて、最初からいるマネキンにフリップでアタックしてみると攻撃判定発生により
こんな感じでデバッグ表示されるはずです。
さて、ここまで出来ればあとはダメージを与えるエフェクトを作成しApplyGameplayEffectToTargetなどで対象に設定すればHPを減らすなどの処理ができるはずなのですが、いろいろやってみたんですがノードの引数が上手く用意できず断念しました(汗
もしこの辺ご存知の方がいらっしゃればぜひぜひコメントでお知らせください、お待ちしております。
ちなみにActionRPGでは、専用のGameplayTaskを用意しておりC++レイヤでエフェクトを与えて処理していました。もしみなさんで実装する場合は、直接そちらを参考にしていただければ、比較的しっかりとした実装ができるかと思います。
最後に
以上でGameplayAbilitiesの使い方については終わりにしたいと思います。
かなり端折った説明になっていますが、少なくともアビリティ、タグ、アトリビュート、エフェクト、タスクの概要はなんとなくつかめていただけたのではないかと思います。
上記を抑えておけば、ActionRPGのソースもかなり楽に読めるはずなので、そういった方々のお役に立てる記事になっていれば幸いです。
また今回は全く触れていませんが、GameplayAbilitiesはオンラインゲームでの使用を想定に入れた仕組みになっています。アトリビュートの変更や、アビリティの実行判定をサーバーで行い結果をクライアントに通知する仕組みなども入っているはずなので、そういった用途を想定しているかたにもピッタリな機能なのではないかと思います。
GameplayAbilitiesの使い方(アトリビュートセット・エフェクト編)
現在設定しているキャラクターのアビリティではアニメーションモンタージュの再生とウェイトを行ってますが、それ以外にもHP,MPなどのパラメータ変更を担うエフェクト(GameplayEffect)、エフェクトに伴って画面上にパーティクルなどの演出を表示するキュー(GameplayCue)、特定のイベント(GameplayEvent)が発生したときにコールバックを受けて処理行うタスク(GameplayTask)を実行することができます。
今回は、まずキャラクターのHP、MPや移動速度といったパラメータのセットであるアトリビュートセットを定義し、アビリティの使用によりそのアトリビュートセットに変更を加えるエフェクトを実行してみたいと思います。
アトリビュートセットの定義
まずアトリビュートセットに最大ヒットポイント、ヒットポイント、移動スピード、ダメージの4つのパラメータを定義します。C++での作成となるのでコンテンツブラウザのコンテンツ→C++クラス→GPA内で右クリックし新規C++クラスを指定します。
親クラスは、全てのクラスを表示にチェックを入れ検索でAttributeSetを指定します。
名前はGPAAttributeSetという名称にして「クラスを作成」をクリックします
次にソースの修正ですが、今回はフルリストを掲載します。
[GPAAttributeSet.h]
大枠でいうと、ヘッダではアトリビュートとしてMaxHealth,Health,MaxSpeedを定義して、アクセッサを用意しています。cppファイルではコンストラクタで初期値を与え、変数ごとにアトリビュート型を取得する関数を用意します。
またPostGameEffectExecute関数は、アトリビュート値を変更するエフェクト(GameplayEffect)が実行されたときに、その変更されたアトリビュート値をGPACharacterクラスに書き戻す用に呼び出される関数です。今回の場合は、最大スピードが変更された際、GPACharacter::HandleMaxSpeedChanged関数を呼び出していますが、この関数の中ではGPACharacterが保持しているCharacterMovementComponentのMaxSpeedを書き換える予定です。ですが、現在はこの関数は定義されていないので、このままだとコンパイルが通りませんので、次にGPACharacter側のソースに変更を加えます。
アトリビュート変更をCharacterへ反映
〇GPACharacter.hの変更
- #include "GPAAttributeSet.h"の追加
GPAAttributeSetを扱うためにヘッダのインクルードが必要です。GPACharacter.generated.hのインクルードの前あたりに入れておきましょう。 - AttributeSetのメンバ、MaxSpeed書き換え時のハンドリングメソッド宣言
クラス定義の一番下、PossessedByの定義の下あたりに といった感じで追記してください。
〇GPACharacter.cppの変更
- アトリビュートセットの実体を生成
AGPACharacterのコンストラクタでアトリビュートセットの実体を生成してヘッダで定義したAttributeSetのポインタに設定します。
-
以下のソースをファイルの最後に加えて、アトリビュートのMaxSpeedが書き換わった時にCharacterMovementのMaxWalkSpeedを書き換える関数の実体を定義します。この関数は先ほど作成したGPAAttributeSet.cppのPostGameplayEffectExecute関数から呼び出されます。
書き換えたら、ここで一旦コンパイルします。
ここまで来たら、上記を反映させるために一旦エディタを再起動しておきましょう。
エフェクトの作成
次に、アトリビュートを書き換えるエフェクトを作成します。
コンテンツブラウザのコンテンツ→Abilitiesの中で右クリックしてブループリントクラスを選択。
親クラスはGameplayEffectを選びます。
作ったクラスはGE_MaxSpeedHighとします。
GE_MaxSpeedHighをダブルクリックして詳細を確認してください。
ここで変更するアトリビュートに関しての設定を粉います。
Modifiersで+して1項目追加し、0の項目を開いて以下の設定を行います。
- AttributeはGPAAttributeSet.MaxSpeed
設定するアトリビュートはMaxSpeed - Modifier OpはOverride
変更方式は指定値で上書き(ちなみにAddを選ぶと以前の値に加算になります) - Modifier Magnitude→MagnitudeCalculationTypeはScalableFloat
変更時の計算はFloat値を使う - Modifier Magnitude→ScalableFloatMagnitudeは1200
設定値は1200
これで、このエフェクトを実行すると最大移動スピードが1200になります。
この状態でコンパイル・保存して閉じてください。
次に、GE_MaxSpeedHighをコピーしてGE_MaxSpeedNormalを作成し
ScalableFloatMagnitudeを600にしてコンパイル・保存しましょう。
さて、これで最大スピードを変更するエフェクトができたので、これを実行するアビリティを作成します。
アビリティの作成
移動速度をアップするアビリティを作成します。
コンテンツブラウザのコンテンツ→Abilitiesの中で右クリックしてブループリントクラスを選択
親クラスはGameplayAbilityとします。
作ったアビリティはGA_Dashとします。
このGA_DashをダブルクリックしてBPを開き、クラスのデフォルトでタグの設定を
このようにAbility TagsはAbility.Action.Dash、BlockAbilitiesWithTagは、Ability.Actionと指定し、ほかのアビリティと排他して動作するように指定します。
次にブループリントを編集します。
上記のようにActivateAbilityからいつものCommitAbilityして、自分へエフェクトを実行するApplyGameplayEffectToOwnerを呼び出します。このときGameplayEffectClassには先ほど作った"GE_MaxSpeedHigh"を指定します。その後5秒ウェイトしアビリティ終了です。
OnEndAbilityでは移動速度を通常に戻すためApplyGameplayEffectToOwnerで"GE_MaxSpeedNormal"を指定しています。
アビリティの登録と呼び出し
次にキャラクター側にアビリティを登録します。
BP_BPchanを開いて、クラスのデフォルトを指定し、詳細のAbilitiesにGA_Dashを追加しましょう。
最後にキーを押されたらアビリティを呼び出すノードを追加します。
BP_BPchanのイベントグラフで、他のアビリティ呼び出しと同様に3キーが押されたらGA_Dashが呼ばれるようにしておきます。
これで設定は完了です。プレイして3キーを押すとスピードが上り、5秒後に元に戻るか確認しましょう。
キュー(GameplayCue)
最後にパーティクルや演出を表示するキューについて軽く触れておきましょう。
エフェクトはHP回復やダメージを与えるなど、アトリビュートの値を変更するために使われますが、それに伴って画面上に演出を出したいケースは多いはずです。
そんなときに使われるのがこのキューです。
今回、スピードアップのエフェクトを作りましたが、それに伴って表示されるキューを作ってみましょう。
いつものようにコンテンツブラウザのAbilitiesの中で右クリックし”基本アセットを作成”の中にあるブループリントクラスを選びます。親クラスはGameplayCueNofify_Actorとして選択を押します。
作ったアセットの名前はGC_DashEffectとしておきましょう。
GC_DashEffectをダブルクリックし「フルブループリンタを開く」を選んで、ブループリントエディタを開いてください。
以下のようにBeginPlayからアクターの位置にEmitterを発生させてアクターは削除します(EmitterはAutoDestoryにチェックが入ってるので再生後自動削除)
今回再生させるパーティクルはスターターコンテンツにあるP_Explosionとします。
次に、クラスのデフォルトを選択し、詳細の中のGameplayCueで、GameplayCueTagを"Effect.Dash"としておきます。
次に、GE_MaxSpeedHigh側でキューを発生させる設定を行います。
GE_MaxSpeedHighをダブルクリックしてクラスのデフォルトを表示し、
Display項目のGameplay Cuesを+して項目を追加し、GameplayCueTagsに先ほどのEffect.Dashを設定します。
これによりこのエフェクトが発生したときにEffect.Dashのタグが付いているキューが自動的に生成されるようになります。
これで準備は完了です。すべて保存してプレイで開始してダッシュしてみてください。
上手くいっていればこんな感じでブループリントちゃんに爆発エフェクトが発生すると思います。
なお、今回はエフェクト発生時にエフェクトを1回発生させる作りになっていますが、GameplayEffectをInstantではなくHasDurationで効果時間を持たせればエフェクト実行中の間パーティクルを表示し続けるなどの演出が可能です。パーティクルは特定のソケットに張り付けることもできるので、魔法キャスト中、エネルギーが手に集中していくようなエフェクトを出すといったこともできます。
GameplayAbilitiesの使い方(アビリティ編)
GameplayAbilitiesの使い方(セットアップ編)の続きです。
今回は実際にアビリティを作成してブループリントちゃんにくっつけていきます。
アビリティの作成
ここではDance,Flipの2つのアビリティを作ります。
最初に、コンテンツブラウザのコンテンツ配下に、Abilitiesフォルダを作ってください。
この中に新規ブループリントを作成してください。
「親クラスを選択」ではすべてのクラスをクリックし、その中でGameplayAbilityを選びます。
作られたブループリントはGA_Danceと名称を変えます。
GA_Danceをダブルクリックして編集します。最初はクラスのデフォルトしか表示されないと思うので
この「フルブループリントエディタを開く」をクリックしてイベントグラフを表示させます。
まずはデバッグ用にPrintStringを仕込みましょう。
こんな感じで、ActivateAbilityされたらDanceStartと表示してCommitAbilityを呼びます。今回は設定していませんがこれにより裏でアビリティの使用に必要なMP消費やクールダウンロジックが実行されます。その後3秒まってアビリティ終了。終了時にはDanceEndと表示。
できたらコンパイル・保存して、いったんGA_Danceは閉じます。
次に、コンテンツブラウザでGA_Danceを複製してGA_Flipも作っておきます。
中身はほぼ一緒。PrintStringだけDanceの部分をFlipに変えておきます。
できたらコンパイルして保存します。
これでGA_Dance、GA_Flipの2つのアビリティが作成されました。
アビリティの実行
ではブループリントちゃんに実際にアビリティを実行してもらいましょう。
BP_BPchanを開いて、クラスのデフォルトを押し、詳細のAbilitiesという項目の中にあるAbilityListの"+"を2回押して項目を2つ追加します。それぞれプルダウンから"GA_Dance"、"GA_Flip"を指定します。
ここでリストアップされたアビリティはセットアップ編のC++で書いたコードによりAbilitySystemに登録されます。これでブループリントちゃんは、DanceとFlipのアビリティが実行できるようになりました。
では実際に呼び出すコードを書いてみます。今回はデバッグ用なのでキーボードイベントの"1"、"2"を使って、Dance、Flipを呼び出してみましょう。
ブループリントちゃんのイベントグラフでキーボード1,2を押されたら、TryActivateAbilityByClassにより、アビリティを実行させます。
こんな感じですね。これで1を押すとGA_Dance,2を押すとGA_Flipが呼び出されます。
コンパイル&保存して、実際にプレイしてみましょう。
1を押したら左上にこんな感じでDanceStartが出て、3秒後にDanceEndが表示されます。2も同様です。
GameplayTagによる排他制御
さて、現在はデバッグプリントしか表示していませんが、将来的には1を押したらちゃんとブループリントちゃんにはダンスしてもらい、2を押したら前転してもらう予定です。が、ここで問題なのですが、現在1を連打すると連続でDanceが何度も実行されてしまいます。
このままアニメーションを実装すると1を押すたびにアニメーションが頭から再生されてしまいおかしな状態になります。これを防ぐためにアビリティにGameplayTagを設定します。
GA_Danceを開いてもらい、クラスのデフォルトをクリックして詳細パネルの中のTagsの部分に注目してください。
Tagsの一番上にある"Ability Tags"は、このアビリティが実行中に設定されるタグとなります。
3番目にある"Block Abilities with Tag"は、このアビリティ実行中は、このタグが設定されているアビリティの実行がブロックされることを表します。
つまり、この両方に同じタグを設定しておけば、最初の実行時にAbilitySystemに実行中のタグが通知され、連打による2回目のアビリティ実行はブロックされます。
アビリティが終了したら"Ability Tags"で設定したタグは自動的に消えるので、もう一度アビリティが実行できるようになります。
では、実際にタグを設定してみましょう。Ability Tags横の"Edit"を押して"Add New Gameplay Tag"を選択します。"Name"の部分に"Ability.Action.Dance"というタグを書き込んで"Add New Tag"をクリックします。
これで
こんな感じでタグが追加されたと思います。
次に、"Block Abilities with Tag"も同様にタグを追加します。
一度追加されたタグは、下部に表示されるアビリティリストの中からツリー上に選べるようになるので、
こんな感じで"Ability.Action.Dance"を設定します。
コンパイル・保存してプレイしてみてください。連打してもアビリティが終了するまでは再実行されないようになっているはずです。
そうなるとGA_Flipについても同様に"Ability.Action.Flip"を設定すれば再実行は抑制され・・・るのですが、一つ問題が残ります。
DanceとFlipは本来同時にできてはいけないはずなのですが、現状はタグが別なのでセパレートに実行できてしまいます。
もちろん、Blockのタグにお互いのTagを追加していけば両方抑制できるのですが、アビリティが増えていくたびに追記するのも面倒です。
そこで利用できるのがタグの階層構造です。Dance,Flipのブロックアビリティを"Ability.Action"で止めておくと、アビリティ実行中、"Ability.Action"で始まるすべてのタグが設定されているアビリティの実行を抑制してくれます。
最終的に
GA_Danceのタグは
GA_Flipのタグは
となり、これで相互排他が可能となります。
なお、今回は使用していませんでしたが、Cancel Abilities with Tagでは、このアビリティが実行されたときにキャンセルされるタグ(そのタグを持ったアビリティも中断されます)が指定できます。またActivationRequireTagsではアビリティ実行時に必要となる前提条件タグが設定できます。これにより何かのアビリティを実行中のみ実行可能なアビリティなども作ることができます。
アニメーションモンタージュの作成
さて、アビリティ編の最後に実際にアニメーションを再生させてみましょう。
今回は、通常の移動時はアニメーションのステートマシンで再生、アビリティ使用時はモンタージュで再生という形にします。
まずはコンテンツブラウザでBluePrintChan->Animationの中にあるBPChan_AnimBPを開いてください。
アニムグラフのステートマシンの後ろに
こんな感じでステートマシンから線をひっぱってMontage->DefaultSlotを選択。
上記のようにResultにつなぎこみ、コンパイル・保存して閉じます。
次に、DanceとFlipのモンタージュを作ります。
(フォルダ構成としては適切でないですが、サンプルなので簡単に)コンテンツのAbilitiesフォルダで右クリックしてアニメーション→アニメーションモンタージュを選択します。
スケルトン選択ではBPChan_Skeltonを選んで
できたアニメーションモンタージュはAM_Danceという名前にしておきます。
AM_Danceを開いて、A_Hip_Hop_Dancingをモンタージュグループにドラッグ&ドロップします。
出来たら保存して閉じます。
同様にAM_Flipも作っておいてください。モンタージュグループにはA_Anim_FrontFlipをドラッグ&ドロップします。
アビリティ実行でモンタージュを再生
次にAbilityのBPにモンタージュの再生を指定します。
GA_Danceを開いてイベントグラフを編集します。
デバッグ用に入れてあるPrintStringを削除。
WaitDelayの代わりにPlayMontageAndWaitノードを入れます。
Montage to Playに再生させるAM_Danceを指定して、OnComplete,OnBlendOut,OnInterrupted,OnCanceledをすべてEndAbilityにつなぎます。
同様にしてGA_FlipでもAM_Flipを再生させます。
これで準備完了です。プレイで最終確認をしてください。
1を押すとダンス、2を押すとフリップが実行されます。また、それぞれ実行終了するまでは他のアビリティは排他され実行されないはずです。
総括すると・・・
ここまでの実装で、アビリティごとにブループリントが独立していているのをご理解いただけたかと思いますが、これは結構便利なことです。
アビリティを増やすごとにブループリントを独立して作成し、そこにやるべきことを書いて能力を持たせたいキャラクタに登録すればいいので拡張がかなり楽です。
また自前でステート管理をしなくてもアビリティごとのGameplayTag設定で、かなり細かく実行抑制(遷移の管理)が可能です。
次回は!
現状はアビリティでモーションを再生さえているだけなので、次回はアビリティによるパラメータ(HPや移動速度など)変更を行う方法(GameplayEffectといいます)について紹介したいと思います。
GameplayAbilitiesの使い方(セットアップ編)
UnrealEngine4.20のリリースに伴いActionRPGのサンプルが公開されたので、早速調査を開始したのですが・・・これがまぁいかんせん範囲が広くてどこから手を付ければって感じでして。
まずはプレイヤー周りかなーと思って見始めたところ、どうもActionRPGサンプルではGameplayAbilitiesプラグインというものを使ってるらしい。
機能の全体像を把握しておくためにも、ここを先に理解しておいた方が良さそうだ、ということで
GameplayAbilities and You - Epic Wiki
まずは、こちらを見つつ、ごにょごにょしてみた結果を書いてみたいと思います。
あ、もちろん英語が得意な方は上記記事を直接読んでいただいたほうがよいかと思います、オフィシャルなので。
あと、本記事はC++とBlueprintをミックスして使う予定です。なぜならGameplayAbilitiesプラグインがそういうものだからw
なので、本記事もUE4のC++にも多少触れたことがある人が対象となっております。
GameplayAbilitiesとは?
主にアクションゲーム向けだと思いますが、プレイヤーが剣で敵を攻撃したり、ファイヤーボールを発射し敵に当たり爆発してダメージを与えたり、範囲爆発を発生させたり、またそれに伴ってMPやスタミナを消費したりと、その辺の実装をシステム的にサポートしてくれる部分とうたわれています。
言葉だけ聞くとものすごく便利そうですが、本当にそうなのだろうか・・・甘い言葉に過去の自分の経験が警告を発しますw
いずれにしても、使ってみないとわからないのでとりあえず触ってみましょう。
1個ずつ順を追っていきます。
プロジェクト作成
UEの4.20を起動し、[新規プロジェクト]→[C++]→[ThirdPerson]でプロジェクトを作ります。
設定は、デスクトップ、ハイエンド、スターターコンテンツ有りで、プロジェクト名はGPAとしました。
次にプラグインの追加します。
これでプラグインウィンドウを開いて、Gameplayのカテゴリの一番下にある・・・
このGameplay AbilitiesをEnableにします。
チェックボックスをクリックしたときに「こいつはまだベータ版だけどいっすか?」的な確認ダイアログが出たらYESを押してください。
さらに、このプラグインを有効かするにはUE4の再起動が必要だといわれるので
ここのRestart nowを押してください。これでプラグインが有効になります。
AbilitySystemComponentを追加
つぎにVisualStudioでC++側の設定を行います。VisualStudioを閉じてしまった場合はGPA.slnをダブルクリックして再起動しておいてください。
GPA.Build.csを編集して、ビルド時にGameplayAbilitiesのモジュールがビルドされるように変更します。
このファイルのPublicDependencyModuleNames.AddRangeの引数にある文字列リストの最後に"GameplayAbilities"を加えます。
こんな感じですね。
次にキャラクターのヘッダとC++ファイルにAbilitySystemComponentを追加します。
〇GPACharacter.h
- アビリティシステムを使うために#include "AbilitySystemInterface.h"を追加。
- AGPACharacterの継承もとにpublic IAbilitySystemInterfaceを追加
-
AbilitySystemComponentとアクセッサの追加
記述場所はAGPACharacterのクラス定義の一番後ろ、GetFollowCameraの定義の行の後に追加します。
"AbilitySystem"は、UAbilitySytemComponentのポインタ
"GetAbilitySystemComponent"はそのアクセッサ
"AbilityList"はこのキャラクターが保持するAbilityを保存しておくためのリスト
となっています。AbilityListには後ほどBP上でアビリティを登録します。
また、初期化用にBeginPlay,PossessedByもオーバーライドしておきます。
これでヘッダの変更は終了。
〇GPACharacter.cpp
- AbilitySystemComponent.hをインクルード
- コンストラクタの記述追加
コンストラクタの最後、Noteのコメント行の後ろにAbilitySystemComponentを生成する記述を追記
BeginPlayの実装
ソースファイルの最後にBeginPlayでアビリティシステムにアビリティを追加する
基本構造としてはAbilityListをforで回して、中のAbilityを1つずつAbilitySytemの登録します(GiveAbility)。その時IDが必要になるので、0から順番に自動で割り振っています。
全登録が終わったらAbilitySystemのActorInfoを初期化してます。- PossessedByの実装
PossessedByでコントローラに所有されたときにアビリティシステムをリフレッシュします。
この機能は一瞬不要に見えますが、ネットワーク対応ゲームでは先にキャラクタが生成されたあとでコントローラから所有処理が走ることがあるので、入れておいたほうがよいみたいです。
さて、ここまで来たらUE4エディタ側は「すべて保存」して閉じておきます。(起動していてもリロードでおかしくなるので)
VisualStudio側でビルドして通るか確認してください。
ブループリントちゃんのセットアップ
さて、次からは実際にキャラクターにアビリティを追加して・・・・行きたいのですが、このままUE4標準マネキンだとアクションさせるモーションがないので、今回は
株式会社 Indie-us Gamesさんのところで配布されているブループリントちゃんを使わせていただきたいと思います。
ブループリントちゃんをダウンロード&展開していただきUE4エディタを閉じた状態で
BLUEPRINTchan\BPchan\Contentの中にあるBluePrintChanフォルダを、GPA\Contentの中にコピーしてください。
完了したらGAP.uprojectをダブルクリックして再びUE4エディタを起動しましょう。
で、このままプレイしてしまうとマネキンがプレイヤーとして動いてしまうので、アウトライナのThirdPersonCharacterを選択し
詳細のThirdPersonCharacterのPawnの中にあるAuto Possessed Playerを
このようにDisabledにしておいてください。
次にコンテンツブラウザでコンテンツ→BluePrintChan→BluePrintにあるBP_BPChanを
ドラッグ&ドロップでレベル上に配置してください。
そして先ほどのマネキンとは反対にアウトライナ上のBP_BPChanが選ばれている状態で
詳細のBP_BPChanのPawnの中にあるAuto Possessed PlayerをPlayer 0にしてください。
できましたら一旦すべてを保存してプレイしてみましょう。
このようにプレイヤーキャラクターとして動かせるようになっていれば成功です。
セットアップの最後に、ブループリントちゃんをAbilitySystemを保持しているGPACharacterを派生させたものに変更します。
BP_BPChanのブループリントをダブルクリックで開いて、コンポーネントにあるCameraBoomとFollowCameraを削除します。
これらはGPACharacter側ですでに保持しているためです。
次にBP_BPchanのクラス設定を押し
詳細パネルのクラスオプションで親クラスをCharacterからGPACharacterに変更します。
これでブループリントちゃんもアビリティを保持できるようになりました。
コンパイルして保存してください。
最後に、再度プレイを行ってみて正常にブループリントちゃんが動かせるか確認してみてください。
さてこの後は?
セットアップ編はこれで終わりです。お疲れさまでした。
次回は実際にアビリティを作ってブループリントちゃんに実行していただきます!