情報は力ではない

UE4 とか Blender とか。

GameplayEffect の Stacking と Overflow を使ってみる

この記事は、Unreal Engine 4 (UE4) Advent Calendar 2019 - Qiita 15 日目の記事です。

qiita.com

前日は @ka-s さんの「【UE4】ランドスケープをスタティックメッシュに変換する方法とハマったところの紹介 - Qiita」でした。ランドスケープもスタティックメッシュに変換できるんですね。知らなかったです。

今回は、Gameplay Ability System の要素の一つである GameplayEffect の Stacking と Overflow を使用して何か作っていきたいと思います。

前提

今回は UE4.23.1 で確認しています。 また AttributeSet には Health という Attribute が定義されている前提で話を進めます。

GameplayEffect とは

GameplayEffect は Gameplay Ability System の要素の一つで Attribute を変更するために使用するクラスです。

Gameplay Ability System については、おかずさんの ActionRPG についての資料がわかりやすいかと思います。

実際に試してみたい方はおかわりはくまいさんの記事を参考にするとよいと思います。

okawari-hakumai.hatenablog.com

Stacking と Overflow

GameplayEffect のパラメータには Stacking と Overflow というセクションがあります。
Stacking は同じ GameplayEffect のスタックに関する設定ができます。Overflow は GameplayEffect のスタックが上限に到達した際の設定ができます。

今回はこの機能を用いて、1度目の攻撃はエフェクトが生じるだけでダメージはないが、2度目の攻撃はダメージを受けるというような機能を作成していきたいと思います。

下の動画は完成イメージです。1度目はグレイマンの周りにエフェクトが発生するだけですが、2度目はダメージを受けるようになっています(敵の攻撃を作るのをサボったため、Q キーの押下で敵からの攻撃とみなしています。。

GameplayEffect の作成

今回は GameplayEffect を2つ作成します。1つは Stack させるための GameplayEffect。もう1つは Overflow した際に発動する GameplayEffect です。 まず作るのが簡単な Overflow した際に発動する GameplayEffect を作成していきます。

Content Browser で右クリックし Blueprint Class を選択し GameplayEffect を作成します。今回は GE_Frostbyte としておきました。

f:id:masahiro8080:20191208162306p:plain
GameplayEffect の作成

作成した GE_Frostbyte を開き Modifires セクションを設定していきます。
この GameplayEffect が発動したら対象にダメージを与えるように設定します。Modifiers セクションから Attribute に Health を設定し、Scalable Float Magnitude でダメージ量を設定します。
GE_Frostbyte はこれで終わりです。

f:id:masahiro8080:20191208162701p:plain
Modifiers セクションの設定

次に Stack する方の GameplayEffect を作成していきます。
同様に GameplayEffect を作成します。名前は GE_StackingFrostbyte にしておきます。作成した GE_StackingFrostbyte のパラメータを設定していきます。
Overflow セクションには Overflow Effects として先ほど作成した GE_Frostbyte を追加します。この Overflow セクションは Stack の最大量を超えた際に発動する GamepleyEffect を設定します。Stack の最大量を超えた際には再び Stack を空にしておきたいので Deny Overflow Application、Clear Stack on Overflow ともにチェックを入れます。

次に Display セクションですがここでは GameplayEffect が発動した際のエフェクトに関わる設定をします。後で氷っぽいエフェクトを追加したいのでそのための設定として Gameplay Cues を一つ追加し Gameplay Cue Tags に Effect.Frostbyte を追加します。追加するには Edit から Add New Gameplay Tag から追加します。

f:id:masahiro8080:20191208164628p:plain
Overflow セクションと Display セクション

次は Stacking セクションです。Stacking セクションでは Stack に関する設定を行っていきます。
Stacking Type は Aggregate by Source に設定します(この設定は Ability System Component が持つこの GameplayEffect のスタックカウントの追加の仕方を設定する項目だと思っているのですが、Aggregate by Target との違いが、正直なところよくわかっていません(すみません))。Stack Limit Count を 1 に設定しました。Stack Limit Count は Stack に積める最大個数です。この設定によりこの GameplayEffect が 2 つ Stack されると Overflow を起こします。

最後に Gameplay Effect セクションです。ここではこの GameplayEffect の期間に対する設定を行います。GameplayEffect の Stack 機能を使用するには、Duration Policy を Has Duration か Infinite に設定する必要があります。ここでは Duration Policy を Has Duration に設定し、Scalable Float Magnitute を 10.0 に設定しました。これによりこの GameplayEffect は 10 秒間 Stack に残ることになります。なので 10 秒間の間に敵の攻撃を受けてしまうとダメージということになります。10 秒を過ぎると Stack から削除されます。

f:id:masahiro8080:20191208165215p:plain
Stacking セクションと Gameplay Effect セクション

GE_StackingFrostbyte の設定は以上です。

次に先ほど Display セクションで設定したエフェクトに関する設定を行っていきます。

GameplayCue の作成

GameplayEffect が発動した際や削除された際の GameplayCue はエフェクトやサウンドに関する処理を司るクラスです。

Content Browser から Blueprint Class を選択し GameplayCueNotify_Actor を作成します。名前を GCN_Frostbyte としておきます。

f:id:masahiro8080:20191208165826p:plain
GameplayCueNotify_Actor の作成

GCN_Frostbyte を開き処理を記述していきます。 まず Functions の Override から On Active を選択し次のような処理を記載します。 My Target から Mesh を取り出し Mesh に Emitter を Spawn しています。Emitter Template には InfinityBlade のアセットで氷属性っぽいエフェクトを使用しています。Spawn Emitter Attached の戻り値を保持しているのは GameplayEffect が削除されたときに Emitter を削除するためです。

f:id:masahiro8080:20191208170055p:plain
On Active 関数

次に On Remove の処理を記述していきます。 GameplayEffect が削除された際に Emitter を消すだけの処理を記載しています。

f:id:masahiro8080:20191208170400p:plain
On Remove 関数

最後に Class Defaults の設定を行っていきます。 変更したのは Auto Destroy on Remove と Gameplay Cue Tag の2つです。 GameplayEffect が削除された際にこの GameplayCue も削除してほしいので Auto Destroy on Remove にチェックを入れています。Gameplay Cue Tag には Effect.Frostbyte を設定しています。これは GE_StackingFrostbyte の Display セクションで設定した値です。これを設定することにより GE_StackingFrostbyte が発動した際に GCN_Frostbyte も発動することになります。

f:id:masahiro8080:20191208170552p:plain
Class Defaults

以上ですべての設定は終わりです。

確認のためにレベルブループリントでプレイヤーに GE_StacingFrostbyte を Stack させる処理を記述していきます。 Player Pawn から Ability System Component を取得し ApplyGameplayEffectSpecToSelf で自分自身に GE_StackingFrostbyte を適用しています。

f:id:masahiro8080:20191208171014p:plain
GE_StackingFrostbyte の動作確認

以上により、この記事の最初に表示した動画のようになります。

おわりに

駆け足でしたが Stacking と Overflow の使い方の紹介でした。これを機に GameplayAbilitySystem に興味を持つ人が増えればよいなと思います。

以上です。

明日はがっちょ( ¨̮ )さんの「地形で遊ぼう!」です。地形の内容ということで楽しみです!

参考

Gameplay Attributes and Gameplay Effects | Unreal Engine Documentation GitHub - Pantong51/GASContent: Repo to gather all Gameplay Ability System content for UE4 GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 4's GameplayAbilitySystem plugin with a simple multiplayer sample project. UE4 GameplayAbility Pluginについてのメモ - Qiita

Procedural Foliage について

この記事は Unreal Engine 4 (UE4) #2 Advent Calendar 2019 の 9日目の記事です。

qiita.com

前日は、なんさんアーティストのためのマテリアル式にゅうもんでした。マテリアルで使用するベクターや Multiply、Add の意味がわかりやすく丁寧に書かれていて良い記事でした!

今回は Procedural Foliage について簡単に紹介しようと思います。 公式のクイックスタートもあるのでそちらも参照してみてください。

docs.unrealengine.com

この記事で使用している UE4 のバージョンは古いですが、4.22 です。
内容は次のような感じで進めていきます。

Procedural Foliage とは

設定したパラメータに基づいてプロシージャルに Static Mesh を配置できる機能です。
Modes パネルから選択できる Foliage ツールは手作業でレベルに Static Mesh を配置できる機能ですが、Procedural Foliage はボタン一つでプロシージャルに Static Mesh を配置できるので、サクッと森などの環境を作りたい場合に使用できるかと思います。

以下では Procedural Foliage の使い方を説明しながら森を作っていきたいと思います。

f:id:masahiro8080:20191207145817p:plain
今回作成した森

Procedural Foliage を使用する準備

Procedural Foliage は Experimental の機能なので Editor Preferences から Procedural Foliage を有効にする必要があります。
Editor Preferences を開き General から Experimental を選択し Procedural Foliage にチェックボックスにチェックを入れます。 Editor Preferences は Edit タブから開きます。

f:id:masahiro8080:20191130125026p:plain
Editor Preferences で Procedural Foliage を有効にする

以上で Procedural Foliage が使用可能になります。

Procedural Foliage 機能を使用した森の作成

Foliage Type の作成

まずはじめに Foliage ツールと同様に Foliage Type を作成します。Foliage Type は Foliage 機能で配置する Static Mesh に関する設定を行うクラスです。
Content Browser で右クリックし Miscellaneous から Foliage Type を選択します。森を作るために木の Static Mesh を配置したいので、名前は FT_Tree01 としておきます。

f:id:masahiro8080:20191130144132p:plain
Foliage Type の作成

作成した Foliage Type を開きます。一番上にある Mesh の項目に Procedural Foliage で配置したい Static Mesh を設定します。ここでは木の Static Mesh を設定しておきます。

f:id:masahiro8080:20191130183300p:plain
Mesh の項目に木の Static Mesh を設定

以上で Foliage Type に関する最低限の設定は終わりです。
他の項目は配置する Static Mesh の密度等の項目なのですが、先に Static Mesh の配置をし、その後で項目を設定していきたいと思います。

Procedural Foliage Spawner の作成

次は Procedural Foliage Spawner を作成します。Procedural Foliage Spawner は、設定したパラメータに基づいてプロシージャルに StaticMesh を配置してくれるクラスです。
Content Browser で右クリックし Miscellaneous から Procedural Foliage Spawner を選択します。森を作りたいので、名前は PFS_Forest としておきます。

f:id:masahiro8080:20191130140142p:plain
Procedural Foliage Spawner の作成

作成した Procedural Foliage Spawner を開きます。Foliage Types の + ボタンを押下し Foliage Type Object に先ほど作成した FT_Tree01 を設定します。Foliage Types は Procedural Foliage Spawner で配置する Static Mesh に関する設定です。

f:id:masahiro8080:20191130183551p:plain
Foliage Type Object に FT_Tree01 を設定

以上で Procedural Foliage Spawner の設定は終わりです。
他のパラメータについては公式リファレンス を参照してください。

Procedural Foliage による Static Mesh の配置

先ほど作成した Procedural Foliage Spawner をレベルに配置し、Static Mesh を配置します。

ちゃっと広いレベルを作成したかったのでランドスケープを使用しています。

f:id:masahiro8080:20191130183946p:plain
Landscape モードでランドスケープを作成

PFS_Forest をレベルに配置します。そのままでは小さいのでスケールを変更して大きな領域にしておきます。

f:id:masahiro8080:20191130184849p:plain
レベルに PFS_Forest を配置する

Static Mesh の配置には、PFS_Forest の Detail タブ内 Procedural Foliage カテゴリにある Resimulate ボタンを押下します。

f:id:masahiro8080:20191130185044p:plain
PFS_Forest の Procedural Foliage カテゴリ

すると Procedural Foliage Spawner の Foliage Types に設定した Static Mesh がレベル上に配置されます。

f:id:masahiro8080:20191130185204p:plain
Resimulate した結果

以上で Foliage Type に設定した Static Mesh を配置し、森を作成することが出来ました。

基本的な使い方は以上です。あとは求める背景に近づけるために、パラメータの調整や新しく Foliage Type を作成していきます。

Foliage Type の調整

配置した木の密集具合や木の大きさ等の調整は Foliage Type で行います。
Foliage Type のパラメータを変えて Procedural Foliage Spawner で Resimulate を繰り返し、求める背景に近づけていきます。

ここでは今回設定したパラメータの一部をざっくりと説明していきます。各パラメータに関しては Procedural Foliage Spawner 同様、公式リファレンス を参照してください。

Placement セクション

Placement セクションでは配置に関するパラメータを設定します。
垂直位置のばらつきや地面に対して垂直に Static Mesh を配置するか、ピッチの角度のばらつきを設定できます。今回は Z Offset で少し垂直位置が下がっている木があるようにしてみたり Random Pitch Angle で少し傾いた木があるように設定しました。

f:id:masahiro8080:20191207155626p:plain
Placement セクション

Collision セクション

Collision セクションでは配置する Foliage Type 同士の重なりに関するパラメータを設定します。
今から配置しようとしている Foliage Type の半径内と他の Foliage Type の半径内が重なった際、ルールや優先度によりどちらかが削除されることになりますが、その半径をここでは設定できます。半径は Collision Radius と Shade Radius の2つを設定できます。今回は Collision Radius を少し変更している程度であまり意味があって変更したわけではないです。

f:id:masahiro8080:20191207160241p:plain
Collision セクション

Clustering セクション

Clustering セクションでは密度や年齢に関するパラメータを設定します。
何世代まで配置するかや配置しようとする Static Mesh の密度、そのばらつきを設定できます。Num Steps は 0 だと最初の世代のみが配置されます。Initial Seed Density は種の密度を設定します。密にしたい場合は大きめの値を設定します。Spread Variance は種のばらつきを設定します。今回は、Num Steps を 5 に設定し、6世代まで配置できるようにしています。また Spread Variance を少し大きめに設定しています。

f:id:masahiro8080:20191207164817p:plain
Clustering セクション

Growth セクション

Growth セクションは年齢による成長に関するパラメータを設定します。
Collision セクション で設定した Shade Radius を無視できるようにしたり、最大の年齢を設定できます。Can Grow in Shade と Spawns in Shade をともに true にすることにより Shade Radius を無視できます。今回は、Max Age や Procedural Scale を変更してみています。

f:id:masahiro8080:20191207170259p:plain
Growth セクション

Instance Settings セクション

Instance Settings セクションでは配置された Static Mesh に関するパラメータを設定します。
Mesh を配置する際のおなじみの設定がほとんどかと思います。今回は Collision Presets を No Collision にしていますが、コリジョンが必要な場合はここで適切なコリジョンに設定します。

f:id:masahiro8080:20191207170730p:plain
Instance Settings セクション

複数の Foliage Type の設定と配置

木の Foliage Type を調整した結果、このような感じになりました(途中で Exponential Height Fog を配置しました)。

f:id:masahiro8080:20191207185605p:plain
途中結果

今は木だけですので、地面に草や石も生やしてみます。

今回は草用と石用の Procedural Foliage Spawner を新しく作成しました。草や石の Foliage Type を作成し、それぞれに Procedural Foliage Spawner に設定しています。これらも木の場合と同様にレベルに配置し、Resimulate します。

f:id:masahiro8080:20191207185150p:plain
草の Procedural Foliage Spawner

もしかしたら、一つの Procedural Foliage Spawner に木や草、石をまとめてもよいのかもしれないのですが納得のいく配置が出来なかったので今回は Procedural Foliage Spawner を複数に分けています。

Static Mesh の配置の抑制

ここまでで次のような感じになりました。
ここで画像の手前から真ん中の辺りに向かって道のようにしたいと思いました。その部分に配置されている花や草が邪魔なので、その部分には配置されないように設定します。

f:id:masahiro8080:20191207171800p:plain
ここまでで作成した森

Procedural Foliage Spawner を配置した範囲の中で Static Mesh を配置したくないという領域がある場合には Procedural Foliage Blocking Volume を使用します。Modes パネルで Procedural Foliage Blocking Volume を検索し Procedural Foliage Spawner の中で Static Mesh を配置したくない場所に配置します。

f:id:masahiro8080:20191201140706p:plain
Procedural Foliage Blocking Volume の配置

あとは Procedural Foliage Spawner 側で Resimulate を押下するだけです。押下すると Procedural Foliage Blocking Volume を配置した部分は Static Mesh が配置されなくなります。

f:id:masahiro8080:20191201140946p:plain
Procedural Foliage Blocking Volume 配置後の Resimulate 結果

この機能を使用し、道のような部分を作成しました。

f:id:masahiro8080:20191207172432p:plain
Procedural Foliage Blocking Volume を配置し、Resimulate

どことなく物足りなさを感じたので、倒れた木を一つ配置して(これは手作業)完成としました。

f:id:masahiro8080:20191207145817p:plain
完成

おわりに

以上で Procedural Foliage についての説明は終わりになります。
Procedural Foliage は、まだ Experimental の機能ということで今後どうなっていくのか楽しみです。 ここまでお読みいただいてありがとうございました。

明日は、@suihanki さんの「VRネタで何か一発」です。

参考

How To Create And Use Procedural Foliage Volumes - UE4 Tutorial Open World ツールのプロパティ リファレンス

GameplayAbility を最初に使用した際にゲームが止まるのを修正する方法

GameplayAbility を使っているプロジェクトで開発していると、エディタの起動後、ゲームをプレイして初めて GameplayAbility を使用した場合に一瞬から数秒ゲームが固まることがあります。

多分、GameplayAbility の使用時に何かを探索しているのかなとか考えてたのですが、解決方法が、AnswerHub に書いてました。

https://answers.unrealengine.com/questions/820453/gameplay-ability-long-lag-on-first-use.html

DefaultGame.ini に GameplayCue のあるフォルダを指定してあげると解決できるみたいです。

 [/Script/GameplayAbilities.AbilitySystemGlobals]
+GameplayCueNotifyPaths="GameplayCue があるフォルダのパス"

GameplayAbility を最初に使用する際に、CueManager が作成されるらしく、その処理の中でプロジェクト内の GameplayCue を探し出し、Cue をプールする処理があるそうです。 上記の GameplayCueNotifyPaths は、どこのフォルダを探索するかを指定する設定のようです。 GameplayCueNotifyPaths のデフォルトは /Game/ なので、指定しないとプロジェクト全体を探索してしまうため、ゲームが固まることがあるみたいです。 なので、上記のように GameplayCueNotifyPaths を指定してあげることで解決します。

ちなみに、親切にもログにちゃんと書いてました。

LogAbilitySystem: Warning: No GameplayCueNotifyPaths were specified in DefaultGame.ini under [/Script/GameplayAbilities.AbilitySystemGlobals]. Falling back to using all of /Game/. This may be slow on large projects. Consider specifying which paths are to be searched.

ちゃんとログは読まなきゃですね。

今回は以上です。

GameplayEffect で継続ダメージ処理をやってみる。

最近、GameplayEffect について調べてます。少しわかってきたので、簡単なサンプルを作ってみたいと思います。今回は毒状態みたいな継続ダメージ処理を GameplayEffect で実装してみたいと思います。

前提

  • Unreal Engine 4.22.3 を使用しています。
  • AttributeSet の実装や AbilitySystemComponent の実装については記述しません。
  • AttributeSet には Health という HP を表す Attribute で設定されていると仮定します。

やりたいこと

今回実装してみるものの仕様はざっくり以下になります。 * 毒状態のような継続ダメージ処理 * 継続する期間は 15 秒間 * 5 秒おきにダメージを喰らう * 継続ダメージが有効中は、パーティクルを発生させる * ダメージ時は、パーティクルを発生させる

下2つの仕様は GameplayEffect が有効かどうかを視覚的に確認したいがために追加しました。

それでは、実装していきます。

GameplayEffect の作成

まず、GameplayEffect のブループリントを作成します。

Blueprint Class から GameplayEffect クラスを選択し、GameplayEffect のブループリントを作成します。

f:id:masahiro8080:20190707132044p:plain

作成した GameplayEffect の Class Defaults で設定をしていきます。 今回は GameplayEffectPeriodDisplay を使用します。

Gameplay Effect カテゴリの設定

まず、Gameplay Effect カテゴリの設定をしていきます。 以下、Gameplay Effect は GE と省略します。

f:id:masahiro8080:20190707131851p:plain

GE の有効時間を一定期間に設定するために、Duration PolicyHas Duration にします。

次に、有効時間を設定するために、Duration Magnitude の項目を設定していきます。 Magnitude Calculation Type は値をどのように設定するかに関する項目です。 今回はそのまま float 値で 15 秒と設定したいので、Scalable Float に設定し、 Scalable Float Magnitude に 15.0 と設定します。 これにより、この GE を起動してから 15 秒間の間、この GE は有効状態になります。

次にダメージ処理のための設定を行います。 Modifiers は、指定した Attribute をどのように更新するかを設定します。 今回は GE が適用される毎に Health を 50.0 ずつ減らすようにしてみました。 そのため、Attribute には AttributeSet の Health、Modifier Op には Add を設定しています (DefaultAttributeSet はこのサンプル用に作成しておいた AttributeSet クラスの名前です)。 これにより GE の実行時に Health の値に -50.0 を追加してくれます。

Period カテゴリの設定

次に Period カテゴリの設定を行います。 ここでは、何秒おきにこの GE を実行するかを設定できます。

f:id:masahiro8080:20190707133055p:plain

今回は 5 秒おきに GE を実行するので、Period に 5.0 を設定しました。 また、GE を起動直後はダメージを発生させたくないので、Execute Periodic Effect on Application のチェックを外しておきました。 この項目を有効にしておくと、GE が起動した際にもダメージが発生することになります。

Display カテゴリの設定

最後に Display カテゴリを設定していきます。 Display カテゴリはエフェクトやサウンドのようなものを設定するために存在する項目のようです。 といっても実際に設定するのは、主にタグかと思います。

f:id:masahiro8080:20190707133418p:plain

Gameplay CuesGameplay Cue TagsEffect.Poison というタグを作成し、設定しました。 これを設定しておくことで、この GE が起動または実行されたときに、このタグに紐づいた Gameplay Cue が処理します。

以上で、GE の設定は終わりです。次に Gameply Cue の実装をしていきます。 Gameplay Cue では、GE が起動したときやダメージ処理が実行されたときのエフェクト処理を記述していきます。

Gameplay Cue の作成

Blueprint Class から GameplayEffect クラスを選択し、GameplayCueNotify_Actor のブループリントを作成します。

f:id:masahiro8080:20190707133708p:plain

作成した Cue の Class Defaults の Gameplay Cue カテゴリの Gameplay Cue TagDisplay で設定したタグを設定します。 これで先ほどの GE が起動された場合にこの Cue が紐づくようになります。

f:id:masahiro8080:20190707133748p:plain

次にエフェクトの発生処理を記述していきます。

まず初めに、OnActive 関数をオーバーライドし、次のように実装します。 OnActivate 関数は、この Cue に紐づいた GE が起動した場合に呼ばれます。 ここでは、GE が有効中であることがわかるように GE が有効中の間常にエフェクトが発生するように P_Smoke を発生させるようにしました。あとで消せるように Emitter Smoke 変数でパーティクルを保持しています。

f:id:masahiro8080:20190707134020p:plain

次に OnExecute 関数をオーバーライドし、次のように実装します。 OnExecute 関数は GE が実行されるたびに呼ばれます。今回のサンプルの場合は ダメージ処理が発生するたびに呼ばれることになります。 ここでは、GE が実行されるたびに P_Explosion を発生するようにしました。

f:id:masahiro8080:20190707134232p:plain

最後に OnRemove 関数をオーバーライドし、次のように実装します。 OnRemove 関数は GE が削除される際に呼ばれる関数です。 ここで GE が削除されるときの後処理を記述しています。

準備は以上です。最後に動作確認をしていきます。

動作確認

レベルブループリントで、キャラクターに GE を付与し、GE を実行するようにします。

f:id:masahiro8080:20190707134612p:plain

キャラクターの Health が減っているかどうかを確認するために TIck イベントを次のように実装しました。 GetHealth 関数や GetMaxHealth 関数は、今回説明しませんが、 キャラクターが持つ AttributeSet から Health と MaxHealth の値を取り出すような処理を C++ 側で記述しています。

f:id:masahiro8080:20190707134811p:plain

実行すると次のような感じです。 GE が起動すると煙が上がり、約5秒おきに爆発エフェクトが発生することがわかると思います。 また、非常に見づらいですが、左上の Debug String から Health が 50 ずつ減っていることも確認できるかと思います。

youtu.be

駆け足で説明してきましたが、このような感じです。 まだまだ GameplayEffect について調べているので、ほかに使い方がわかれば新しく記事を書くつもりです。

今回は以上です。

UE4 で自動テストを試してみる。

この記事は Unreal Engine 4 (UE4) Advent Calendar 2018 の19日目の記事です。
昨日は、@AziO さんの UE4のバグ報告をして修正してもらおう! でした。

今回は UE4 の自動テストをやってみようと思います。やり方の手順は公式のドキュメントが詳しいです。
Unreal Engine | 自動化システムの概要

他にも色々な方が記事を上げてくださっています。例えば次のようなものがあります。
[UE4] コマンドから自動テストを実行してみる|株式会社ヒストリア
[ #UE4 ]Blueprintでユニットテスト・機能テスト - Qiita

今回は自動テストの実行と作成方法を見たあとに、簡単な AI に対するテストをやってみたいと思います。

テストの実行方法

自動テストはプラグインなので、Plugin を有効にし、エディタを再起動をする必要があります。

f:id:masahiro8080:20181218004219p:plain

再起動が終わったら Window から Test Automation を選択し、Session Frontend を開きます。 Automation タブのチェックボックスにチェック入れ、Start Tests ボタンを押すとテストが開始します。

f:id:masahiro8080:20181218004328p:plain

テストの結果は Automation タブで確認できます。 緑になっていたらそのテストは成功。赤は失敗を意味します。またオレンジ色のものは、警告を表しています。

f:id:masahiro8080:20181218004350p:plain

テストの実行方法がわかったので、次に自分でテストを作成して、そのテストを実行していきたいと思います。

テストの実装方法

テストは C++ でもブループリントでも実装できますが、今回はブループリントで実装していきます。

まず、FunctionalTest クラスのブループリントを作成します。ブループリントの名前は BP_FirstTest としました。

f:id:masahiro8080:20181218004408p:plain

次に、Evnet Graph タブで次のように最初のテストを書いてみました。

f:id:masahiro8080:20181218004437p:plain

Start Test イベントノードはテスト実行時に呼ばれるイベントです。 Finish Test ノードは、テスト結果を設定します。テスト結果を Test Result ピンに、テスト終了時にログに残したいメッセージをMessage ピンに設定します。

今回作ったテストは無条件にテストが成功するテストです。
テストを作成したので、作成したテストをテスト用のマップに配置します。

f:id:masahiro8080:20181218004504p:plain

再び Session Frontend を開き、Automation タブの Project から作成したテスト (BP_FirstTest) にチェックを入れ、Start Tests を実行します。 作成したテストが表示されていない場合は Refresh Tests ボタンを押すと表示されると思います。

Start Tests を押すと、Standalone のプレビュー画面が開くと思いますが一旦閉じて、Session Frontend の画面を開き、テスト結果を見るとテストが成功していることが確認できると思います。

f:id:masahiro8080:20181218004423p:plain

ここまでで、テストの作成、実行が出来るようになったかと思います。
自動テストの基本は以上です。以下は、簡単な AI に対してテストを実践してみようと思います。

簡単な AI のテスト

今回は AI の一つの動作をテストを作成しながら、実装していきたいと思います。

作成する AI について

ここで作成する AI の処理は、次のようなものです。

  • 視界内のプレイヤーの位置まで移動する

視界の距離等を決めていない雑な仕様ですが、この処理を行う AI をテストと一緒に作っていきたいと思います。

テストの作成と実行

AI とテストの実装を行う前に、プレイヤーとテスト対象の敵のブループリントを準備します。名前はそれぞれ BP_Player、BP_Enemy としました。 中身は両方とも Character ブループリントの Skeltal Mesh に SK_Mannequin を設定したものになっています。
しかし、それだけだとマップに配置した際に、どっちがプレイヤーなのか敵なのかわかりづらくなるため、頭上にテキストで印をつけています。

下の画像はテスト対象の敵のブループリントです。同じ感じでプレイヤーのブループリントも作成しています。

f:id:masahiro8080:20181218005833p:plain

プレイヤーとテスト対象の敵を準備したので、次に新しくテストを作成していきます。 名前は BP_MoveToEnemyTest として作成します。

まず、変数からです。 マップに配置したテスト対象の敵とプレイヤーをテストで使用したいので、BP_Enemy 型の変数と BP_Player 型の変数を作成しています。

変数

変数名
TargetAI BP_Enemy
Player BP_Player

次にイベントグラフです。 テストの内容としては、テスト開始一定時間後にプレイヤー(Player)とテスト対象の敵(TargetAI)の距離がしきい値以下であるかどうかを確認するようなテストにしています。

イベントグラフ

f:id:masahiro8080:20181218005547p:plain

今回のテスト用にマップ MoveToPlayerTestMap を作成し、 BP_MoveToPlayerTest を配置します。

マップにプレイヤーとテスト対象の敵を配置します。テスト対象の敵の視界内にプレイヤーが入るように配置します。 また、BP_MoveToPlayerTest の TargetAI、Player にマップ上の BP_Enemy、BP_Player をそれぞれ設定しておきます。
今回はビヘイビアツリーの MoveTo を使用するので、Nav Mesh Bounds Volume もマップに配置しておきます。

f:id:masahiro8080:20181218011017p:plain

ここまで出来たら、一度テストを実行してみます。
もちろん、ただ単に Character をマップに配置しただけで、何も実装していないのでテストは失敗するはずです。

f:id:masahiro8080:20181218011523p:plain

期待通り、テストが失敗しました。
次にテストが成功するように AI を実装していきます。

AI の実装

ビヘイビアツリー (BT_Enemy) とブラックボード (BB_Enemy) を用意し、次のように実装しました。
ブラックボードに設定されたプレイヤーまで MoveTo で移動するような処理にしています。

ビヘイビアツリー

f:id:masahiro8080:20181218231251p:plain

ブラックボード

f:id:masahiro8080:20181218231302p:plain

次に敵用の AIController (BP_EnemyAIController) を作成し、AIPerception をコンポーネントに追加します。

f:id:masahiro8080:20181218231440p:plain

AIPerception に視界の設定をします。値については初期値のままです。

f:id:masahiro8080:20181218233420p:plain

BP_EnemyAIController のイベントグラフは次のようにしました。
BeginPlay イベントでは、作成したビヘイビアツリーとブラックボードを使用する設定をしています。 OnPerceptionUpdated イベントでは、BP_Player が視界内にいるかどうかを確認して、視界内にいればブラックボードに Player を設定しています。

f:id:masahiro8080:20181218231517p:plain f:id:masahiro8080:20181218233505p:plain

最後に BP_Enemy の AIController に BP_EnemyAIController を設定します。

テストの再実行

ここまで実装が出来たら、もう一度テストを実行してみます。

f:id:masahiro8080:20181218233309p:plain

テストの成功が確認できました。
これで敵がプレイヤーの位置まで移動することが確認できました。

もう一つだけテストを追加

ここで、視界外のプレイヤーは追わないのかどうかが気になったので、次のようなテストを作成しました。
先ほどのテストとほぼ同じですが、変数にテスト対象の敵と初期位置に配置した Target Point を設定して、 テスト対象の敵が最初の位置から動いていないことを確認するテストにしています。

f:id:masahiro8080:20181218234253p:plain

テスト用のマップにはテスト対象の敵の背後にプレイヤーを配置してみました。

f:id:masahiro8080:20181218234423p:plain

これでテストを実行してみます。

f:id:masahiro8080:20181218234522p:plain

2件ともテストが成功したので、視界内のプレイヤーまで移動するという処理はとりあえず実装できたように思います。

さいごに

今回は自動テストのやり方を簡単に見ていきました。

今回実装したようなテストは、雑すぎてテストではないような気がしますが、テストの実装方法の雰囲気がわかれば幸いです。

個人的に自動テストは好きな機能で、テストがあることである程度安心して開発していくことが出来ます。

自分はゲーム会社で働いていないのでゲームにおけるテストというものがどういうものなのかわかっていません。

ゲーム会社、特に UE4 を使ってる会社、またサークルや個人開発者の方がどういうテストをしているのかに自分は興味があるので、テストに関する情報がたくさんあると嬉しいなと思います。

また、今回の記事で自動テストに挑戦する人が増えると幸いです。

今回は以上です。

明日は @ayumax さんの Unreal.js についての記事です!楽しみ!

UE4 + ARCore でデプスシャドウを実装してみた。

概要

今、UE4 + ARCore でアプリケーションを作ってます。 現実世界に Gray ちゃんを召喚して好きなポーズをつけることができるアプリを目指してます。

キャラクターを Spawn したときに、現実世界に落とす影を、丸影じゃなくてキャストシャドウを使いたいなぁと思ったので、やってみました。 少し C++ を使ってます。

まだ荒いですが、こんな感じです。

f:id:masahiro8080:20181125192750p:plain

環境

Version
Unreal Engine 4.21.0
Android 9 (Pixel 3 XL)

方針

影を落とすのにどういう方法があるのかわからなかったので調べてたのですが @ruyo_h さんの MToon の記事をたまたま見つけました。

qiita.com

この記事の方法が使えそうだったので、実装してみることにしました。

ざっくり説明すると、光源方向に配置した SceneCaptureComponent2D を使ってシャドウマップを作成し、影を落とす地面のマテリアルで事前に作成したシャドウマップを見ながら地面メッシュの各ピクセルが影なのかどうかを決めていくという方法です。

実装

設定

Mobile HDR にチェックを入れています。 また、Build は ES 3.1 を使用しています。

f:id:masahiro8080:20181202220504p:plain f:id:masahiro8080:20181202220416p:plain

シャドウマップの準備

まずはじめにシャドウマップの準備をします。 シャドウマップには Render Target を使用します。Render Target を作成し、名前は RT_ShadowMap としておきます。 作成した Render Target の設定は次のようにしました。

f:id:masahiro8080:20181129204303p:plain

後で説明する SceneCaptureComponent2D の設定で、Render Target のアルファチャンネルに深度値を格納するために Render Target Format を RTF RGBA16f にしています。

次に SceneCaptureComponent2D の設定をしていきます。 Pawn を作成し、SceneCaptureComponent2D を追加します。 この Pawn には、影を落とすための地面メッシュも同時に追加しておきます。

f:id:masahiro8080:20181129205249p:plain

SceneCaptureComponent2D は次の画像のように設定しておきます。 SceneCaptureComponent2D の X 軸正方向が影を落としたいメッシュを向くようにします。

f:id:masahiro8080:20181129225831p:plain

入力した箇所は具体的には以下の項目です。

Property Value
Projection Type Orhographic
Ortho Width 256
Texture Target RT_ShadowMap
Capture Source Scene Color (HDR) in RGB, Scene Depth in A

Capture Source を Scene Color (HDR) in RGB, Scene Depth in A に設定することで、RT_ShadowMap のアルファチャンネルに SceneCaptureComponent2D からの深度値が入っているはずです。

以上で、シャドウマップの設定ができました。

シャドウマップの確認

確認のために次のようなマテリアルを作って確認してみました。

f:id:masahiro8080:20181129231001p:plain

RT_ShadowMap を設定した Texture Sample ノードのアルファ値を 1000 で割った値を Base Color につないでいるだけです。 RT_ShadowMap のアルファに格納されている深度値は cm 単位で入っていると思われます。 Base Color に入れる値を 0 ~ 1 の間の値にするために、ある程度大きめの値で割っています。

作成した Pawn と、上で作成したマテリアルを設定した Plane Static Mesh をマップに配置しました。 Plane Static Mesh にシャドウマップが表示されていることが確認できました。

f:id:masahiro8080:20181129232119p:plain

影を落とす地面メッシュのマテリアル

次に、作成したシャドウマップを影を落とすための地面メッシュにマッピングさせるためのマテリアルを作成します。 そのためには、影を落とすための地面メッシュの各ピクセルが、作成したシャドウマップのどこに対応するかを求める必要があります。 その変換を行うために、影を落とすための地面メッシュの各ピクセルのワールド座標から SceneCaptureComponent2D のローカル座標への射影変換行列が必要です。 この射影変換行列を取得するために、UBPFL_GrayAR という Blueprint Function Library を C++ で作成し、次のような C++ のコードを準備しました。

UBPFL_GrayAR.h

public:
    UFUNCTION(BlueprintCallable, Category = "GrayAR")
    static TArray<FLinearColor> getInverseMatrix(const FTransform& transform);

UBPFL_GrayAR.cpp

TArray<FLinearColor> UBPFL_GrayAR::getInverseMatrix(const FTransform& transform)
{
    TArray<FLinearColor> Ret;
    Ret.SetNum(4);

    FMatrix InverseMatrix = transform.ToMatrixWithScale().Inverse();
    for (int i = 0; i < 4; i++) {
        FLinearColor color;
        color.R = InverseMatrix.M[i][0];
        color.G = InverseMatrix.M[i][1];
        color.B = InverseMatrix.M[i][2];
        color.A = InverseMatrix.M[i][3];
        Ret[i] = color;
    }
    return Ret;
}

これは、取得した Transform から、射影変換行列の逆行列を返す関数です。 射影変換行列の逆行列は、 TArray として返却しています。

この関数から返された値を次の Blueprint のようにマテリアルのパラメータに渡して使用します。

f:id:masahiro8080:20181202182109p:plain

影を落とす地面メッシュのマテリアルは次のようにしています。

このマテリアルは @ruyo_h さんの記事のマテリアルをほぼそのまま使用しています。

f:id:masahiro8080:20181202181607p:plain

使用している custom ノードも次のような感じです。

入力したパラメータから射影変換行列を作り、メッシュの各ピクセルの絶対座標を 射影変換行列と掛け算することで、メッシュの各ピクセルの絶対座標を SceneCaptureComponent2D のローカル座標に変換しています。

float4x4 m;

m = float4x4(
  float4(r1.x, r1.y, r1.z, r1.w),
  float4(r2.x, r2.y, r2.z, r2.w),
  float4(r3.x, r3.y, r3.z, r3.w),
  float4(r4.x, r4.y, r4.z, r4.w));

float4 t = mul(position, m);

return t / t.w;

最後に地面メッシュに作成したマテリアルを設定し、確認を影が落ちていることを確認します。

f:id:masahiro8080:20181202213118p:plain

Android 上でもデプスシャドウが落ちていることが確認できました。

f:id:masahiro8080:20181202220255p:plain

補足

当初は @ruyo_h さんの記事にあるように RTF R32f を使用し、R チャンネルに深度値を設定するようにしていたのですが Android 上でシャドウマップが表示されませんでした。

次の画像は Render Target Format: RTF R32f で Capture Source: Scene Depth in R を使用して Android 上で実行したものです。

f:id:masahiro8080:20181202215134p:plain

次の画像は Render Target Format: RTF RGBA16f で Capture Source: Scene Color (HDR) in RGB, Scene Depth in A を使用して Android 上で実行したものです。 (Plane Static Mesh の角度がおかしいですが)

f:id:masahiro8080:20181202215106p:plain

実装に自分の間違いもあるかもしれないのですが、RTF R32f だとシャドウマップが表示されなかったので、今回は RTF RGBA16f で実装しました。

さいごに

モバイル上で実行するには負荷が高いのかもしれませんが、AR 上で影ができたのでとりあえずは満足です。

雑な説明になってしまいましたが、以上です。

NavMeshBoundsVolume で NavMesh がオーバーレイされないときの調整方法

f:id:masahiro8080:20180728121903j:plain

画像みたいに階段に NavMesh がオーバーレイされていないときの調整の仕方をよくわすれるので備忘録として書いておく。

Version

version
Unreal Engine 4.19

方法

Project Settings で Engine の中から Navigation Mesh を選択する。Generation の中の Cell Height の大きさを大きくする。

f:id:masahiro8080:20180728122444j:plain

大きくすると、ちゃんと NavMesh がオーバーレイされる。

f:id:masahiro8080:20180728122534j:plain