初めに
この記事はUE4初心者が苦労しながら、チーム制作・個人製作で学んだ事を記述していくものとなっています。なので、間違っている可能性・別の方法が良かったりする可能性も普通にあるので、その点だけ注意して下さい。あと、中には「知ってるわ!」みたいな機能もあると思います。その場合は飛ばして下さい。
作成で使用していたバージョンは「Ver 4.26.2, Ver 4.27.2」ですが、記事を書く上で使用しているのはUE5です。少し配置が変わってるぐらいで仕様自体はあまり変わらないので許して...。
一覧
とてつもなく多いので量が多いので、スマホの方は下の一覧でタップして下さい。
目次 | 見出し |
---|---|
マテリアル | テクスチャの透明度を適用したい |
-- | マテリアルパラメータコレクション |
-- | マテリアルダイナミックインスタンス |
-- | マテリアルインスタンス |
-- | マテリアルファンクション |
-- | マテリアルのノード |
-- | 便利なショートカット |
コリジョン | Block, Overlap, Ignore |
-- | コリジョンプリセット |
-- | メッシュの判定 |
-- | トレース系の判定 |
AI | AIの作成 |
-- | ブラックボード |
-- | ビヘイビアツリー |
-- | ビヘイビアツリー自体を停止したい |
-- | ビヘイビアツリーのデバッグ |
-- | 標準のデコレーター・タスクノード |
-- | ビヘイビアツリーの動き |
マテリアル
テクスチャの透明度を適用したい
Texture Sample
ノードを使って、テクスチャ(.pngなど)の透明度設定を適用したい。RGBAピンをベースカラーに繋いでも設定されない。
- 「詳細 > マテリアル >
Blend Mode
> Translucent」に設定 -
Texture Sample
ノードの「RGBピン」を「ベースカラー」に繋げ、同じく「Aピン」を「オパシティ」に繋げる
参考サイト
オパシティとラフネスが同時に設定出来ない
「詳細 > マテリアル > Blend Mode
> Translucent」に設定した上で、``Metallic`‘やラフネスを設定したいが、無効状態になっている。
- 「詳細 > 透過処理 >
Lighting Mode
> Surface TranslucencyVolume」に設定 - 「Metallic・スペキュラ・ラフネス」が有効状態になるので、それぞれのピンに繋げる
参考サイト
マテリアルパラメータコレクション
マテリアルパラメータコレクション(以下MPC)を一言で言うなら「複数のマテリアルに共通の値を外部から動的変更可能な機能」です。プログラマー的に言うなら「構造体のStatic変数」、DirectX的に言うなら「コンスタントバッファ」になるのかな~と思います。
MPC
はマテリアル専用の構造体みたいなもので、特定のマテリアルに依存せず外部から自由に変更が可能です。逆に言うなら、値を変更すると使用しているマテリアル全てが変わってしまいます。設定出来るパラメータは「スカラー(float)・ベクター(float4/Linear Color)」のみ可能です。勿論、動的に設定が可能ですが、取得も可能です。
MPC
の追加・編集
※ 今回はテストなので、名前が適当になっていますが気にしないで下さい。
マテリアル側
- 「右クリック > パラメータ >
CollectionParameter
」でノードを召喚
- 「ノードの詳細 > 一般 >
Collection
」で作成したMPC
を設定 - 「ノードの詳細 > 一般 >
Parameter Name
」でパラメータ名を設定
今回のテストではVector
パラメータを「ベースカラー」に、Scalar
パラメータを「オパシティ」に設定しました。
ブループリント側
下図のように、MPC
は使用しているマテリアル全てに影響を与えるので、違うアクター(マテリアル)であっても全てのアクタが変更されます。かなり便利だと思いますが、どこからでも変更出来るので怖くもありますね。
といっても、自分としては「1マテリアル1MPC」と考えているので、マテリアルを跨ぐ使い方はあまりしたくない...というようりも、跨ぐ必要性に駆られていないだけなのですが...。
「マテリアルを使用したアクター毎に変更したい」といった場合は、下記のマテリアルダイナミックインスタンスを使う事になります。むしろこちらの方が多いと思いますが...。
参考サイト
マテリアルダイナミックインスタンス
マテリアルダイナミックインスタンス1(以下MDI)を一言で言うなら「マテリアルの値を動的に変更可能な機能」です。マテリアルを使うなら「コレ!!」といった技術になります。
MDI
はマテリアル専用の変数みたいなもので、マテリアルに依存し各アクター毎に自由に動的変更可能な機能です。設定出来るパラメータは「スカラー(float)・ベクター(float4/Linear Color)・テクスチャ(Texture)」が可能です。勿論、動的に設定が可能ですが、取得も可能です。何気にテクスチャも変更できるのはありがたいですね。
マテリアル側
- ノードを追加
- 「詳細 > 一般 >
Parameter Name
」で名前を付ける(もしくはノードをクリックしてF2
) - 「詳細 > マテリアルエクスプレッションパラメータ >
Default Value
」で初期値を設定 - 「詳細 > マテリアルエクスプレッション >
Group
」でグループを設定しておくべき- 設定しておかないと、後述するマテリアルインスタンスで
Global Scalar / Vector Parameter
になってしまう
- 設定しておかないと、後述するマテリアルインスタンスで
ブループリント側
作成
- 「レンダリング > マテリアル >
Create Dynamic Material Instance
」でノードを召喚 - 引数の設定
-
戻り値(
Material Instance Dynamic
型)を変数化2 - 「レンダリング > マテリアル > Set Material」でノードを召喚(ターゲット側に
MDI
化予定のマテリアルを既に設定している場合は不要)
変更
「レンダリング > マテリアル > Set Vector / Scalar / Texture Parameter Value
」でノードを召喚
「状況に合わせた表示」をONにしている場合、Material Instance Dynamic型から右クリックしないと、MPC側のノードが表示されるので注意
取得したい場合
「Get Vector / Scalar / Texture Parameter Value
」ノードを使用する
もしくは、「Get Vector / Scalar / Texture Parameter Values
」ノードを使用する(簡単に確認はしたが要検証)
引数の設定
- ターゲット ->
Create Dynamic Material Instance
の戻り値(Material Instance Dynamic
型) -
Parameter Name
-> マテリアルで追加したパラメータの名前
Name型(直接指定)なので打ち間違いに注意
-
Value
-> 変更したい値(各ノードに応じた型(Scalar =float
型 / Vector =Linear Color
構造体 / Texture =Texture
オブジェクト参照))
下図のようにMDI
はマテリアル自体の値を変更をする為、アクターを個別に変更可能です。イメージとしては「スポーンしたアクター毎にマテリアルを持っている」が合っているでしょうか。
初めて使う前は結構躊躇していたのですが、いざ使ってみると全然簡単という事に気付きました。そのためか、私自身この機能をよく使っています。
動的に変化する場合はこの機能を使うと非常に便利です。逆に、「別に動的じゃなくて静的でいいんだよ」といった場合は、下記のマテリアルインスタンスを使用します。
参考サイト
マテリアルインスタンス
マテリアルインスタンス(以下MI)を一言で言うなら「マテリアルの値を動的に変更可能な機能」です。こちらもよく使います。プログラム的には「派生クラス」のイメージが近いです。
基本的にはマテリアルダイナミックインスタンスの静的バージョンの認識で大丈夫です。
特に、コンポーネントにマテリアルを設定する時は、常にMI化して設定するべきです3。
MI
なのですが、「個別に作成せずある程度自由な形でマテリアルを作成し、MI
を使って個別に設定する」といった使い方も出来ると思います。つまり、色違い・テクスチャ違いを実装するのに色違い毎にマテリアルを作成せず、ベースのマテリアルをMI
化したものを個別に設定するという事です。この考えは派生と同じですよね。
作成
追加
マテリアルダイナミックインスタンスの「マテリアル側」と同じです。つまり、VectorParameter / ScalarParameter
で対応します。
変更
- 「詳細 > パラメータ グループ >(設定したグループ名)>(設定したパラメータ名)」から、変更したいパラメータのみチェックを付けて値を変更
下図のようにMI
はマテリアル自体の値を変更出来ますが、あくまで静的な変更です。ですが、同じアクターでもSet Material
関数を用いて複数個のMI
を用いれば、疑似的に動的な変更が可能ではあります。あくまで「1つのMI
では静的変更になるよ」という事です。
UE4のマテリアルを使い始めて真っ先に使う機能ではないのでしょうか?やはり、使い方自体はとてもシンプルなのがいいですよね。
少し変わったパラメーターとして、「StaticBoolParameter
・StaticSwitchParameter
・StaticComponentMaskParameter
」といったものがあります。名前の通り、静的に決定し、実行中に変更不可のパラメーターノードです。つまり、「MI
上で静的に変更する専用ノード = コンパイル時にシェーダーコードが確定するノード」です。少し面白い?ノードです。
参考サイト
マテリアルファンクション
名前の通りのマテリアル専用の関数です。例えば、複数個のマテリアルを作成している場合、同じような処理をまとめたい時に使用します。
作成
「コンテンツブラウザ > 追加 > マテリアル・テクスチャ > マテリアル関数」で作成します。
編集
デフォルトでは最初から「Input In(Vector3)」と「Output Result」が既にあります。いわゆる、引数と戻り値になります。引数を追加したい場合は「右クリック > 関数 > FunctionInput
」、戻り値を追加したい場合は「右クリック > 関数 > FunctionOutput
」で可能です。
引数の設定はノード詳細から
- 「マテリアルエクスプレッションファンクションインプット >
Input Name
」-> 名前 - 「
Input Type
」-> 型 - 「
Description
」-> 引数の説明表示(後から見ても分かるように記述しておくべきです) - 「マテリアルエクスプレッション >
Sort Priority
」-> 関数を呼び出す側の順番指定
同じように、戻り値の設定はノード詳細から
- 「マテリアルエクスプレッションファンクションアウトプット >
Output Name
」-> 名前 - 「
Description
」-> 戻り値の説明表示(後から見ても分かるように記述しておくべきです) - 「マテリアルエクスプレッション >
Sort Priority
」-> 関数を呼び出す側の順番指定
次に、マテリアル自体の設定は何も選択していない状態で詳細から
- 「マテリアルファンクション >
Description
」-> 関数の説明表示(後から見ても分かるように記述しておくべきです) - 「
Exposes to Library
」-> 関数呼び出し時の検索欄に表示するか? - 「
Library Categories Test
」-> 関数呼び出し時の検索欄でのカテゴリー
参考サイト
マテリアルのノード
Customノード
DirectXでお馴染みのHLSLコードで記述する事が可能なノードです。例えば、見た目を簡略化したり、マテリアルエディタ上では実装不能なループ処理を記述する場合に使います。ポストプロセス系のマテリアルなど(例:自作ブルーム)はよく使うのかな?と思います。
「右クリック > カスタム > Custom」でCustom
ノードを召喚可能です。
特に重要なのが、ネット上に存在するコードをそこそこ簡単に引用出来ます4。例として、「Shadertoy・NieR:Automata Glith Effect」などのサイトにあるコードを使わしてもらう場合などが上げられます。
Shadertoy上のコードを移植するには、そもそもGLSL
なのでHLSL
に変換する必要があります。その時は「GLSLをHLSLに書き換える - Qiita」が非常に参考になります5。変換する時に「VSCode」を使うと、まとめて置き換え出来たり、型に色が付いたりするので出来るのでオススメです。そして、変換したコードをCustom
ノードに貼り付けた上でキチンと動けば完了です。
使い方
詳細な使い方はここには載せないので下記を参考にして下さい。ノード自体の使い方や引数・戻り値の設定方法なども記述されています。個人的に注意すべき点が「引数の名前(スペル)は大文字・小文字まで完全一致させる必要がある」です。でないとエラーが発生します。後、UnrealEngine用の変更があったりするのでその点も注意です。例えば
- 「
float2 p = fragCoord/iResolution;
」→「float2 p = fragCoord;
」 - 「
float3 col = texture( iChannel0, uv ).xyz;
」→「float3 col = iChannel0.Sample( iChannel0Sampler, fragCoord ).xyz;
」- テクスチャのサンプルの仕方については「Unable to sample custom texture inside custom expression - UNREAL ENGINE FORUMS」を参考にして下さい。
Float2, 3, 4系について
Float4での一例なのですが、例えば...
- 分解したい ->
BreakOutFloat4Components
- 作成したい ->
MakeFloat4
- 変換したい ->
ComponentMask
- 値を確認したい(デバッグしたい)->
DebugFloat4Values
参考サイト
その他の使えるノード
- OneMinus
- TexCoord
- Time
- Rotator
まだまだ沢山紹介出来るノードはありますが、もっと知りたい方は下記サイトを覗いてみて下さい。
便利なショートカット
全て「キー + 左クリック」の形になります。その為、左クリックは省略します。
ショートカットキー | 内容 |
---|---|
A | 足し算 |
D | 割り算 |
M | 掛け算 |
1 ~ 4 | float~float4の定数(Constant, Constant2/3/4Vector) |
T | テクスチャサンプル |
O | OneMinus(1-x) |
S | スカラーパラメータ |
U | テクスチャUV座標 |
V | ベクターパラメータ |
残念ながら、引き算はハブられてしまいました。引き算よりもOneMinusの方が使う機会が多いからですね?
コリジョン(当たり判定)
若干UE4の当たり判定がめんどくさく感じるのは気のせいでしょうか?かなり簡単に実装出来るのですが...何故か使いづらさを感じるこの頃...。
Block, Overlap, Ignore
いわゆる、「衝突・検知・無視」になります。
- Block
- Overlap
- Ignore
※ 画像はコリジョンの概要から参照してきました。
プログラム上でのお話
とりあえず変更するには「詳細 > コリジョン > コリジョンプリセット > NoColision / BlockAll / OverlapAll
」で変更可能です。コリジョンプリセットの詳細に関しては後述します。
当たり判定をブループリント側で取得するには、「詳細 > イベント > On Component Hit / Begin Overlap / End Overlap
」の「+」をクリックすると関数が出現した上で、関数へとフォーカスします。既に存在している場合は「+」が「ビュー」へと変化します。
注意点
「Overlap」についてなのですが、検知したいオブジェクト両方とも「Generate Overlap Events」にチェックを入れる必要があります。
更に、物理シミュレーション中に「Hit」判定を取りたい場合は、「Simulation Generates Hit Events」にチェックを入れる必要があります。物理シミュレーションは「詳細 > 物理 > Simulate Physics
」をONにしている場合です。
参考サイト
コリジョンプリセット
-
Collision Enabled
-
Object Type
- トレース反応
- オブジェクト反応
勿論、コレらの設定はBP側で変更可能です。一例ですが、一時的に当たり判定を無くしたいといった場合は、下図のようになります。
コリジョンプリセットの追加
実はプリセットは独自に追加する事が可能で、「プロジェクト設定 > エンジン > コリジョン」から追加・編集が可能になっています。詳細は下記サイトをご覧ください。
参照サイト
メッシュの判定
例えば、独自にスタティックメッシュのアセットを読み込んだとして、それらの当たり判定をスタティックメッシュ単体に付与する事が可能です。読み込んだスタティックメッシュのアセットを開き、「コリジョンのタブ >「~を追加」」で簡易的なコリジョンを追加する事が可能です。
簡易的なコリジョンを調整する機能として、「コリジョンのタブ > オートコンベックスコリジョン(凸型分解)」を使い、当たり判定の詳細を弄ることが可能です。
ですが、例えば円柱内に空洞がある場合など、うまくいかない場合があります。その場合は、「詳細 > コリジョン > Collision Complexity
> Use Complex Collision As Simple
」を選択する事でメッシュに完全一致する当たり判定を設定出来ます。表示されない時は、ウィンドウ左上にある「表示 > 複雑なコリジョン」を選択する事で表示されます。
正確な分、重たくなりますし、物理シミュレーションに対応していないというデメリットも存在するので地形などのオブジェクトにしておくのが無難だと思います。適用した結果が下図になります。
参考サイト
トレース系の判定
少し注意点があるライントレース(レイキャスト)系のお話です。下図の図形は青が「Overlap」、黄色が「Block」になっています。
Line Trace By Channel
限定的なレイトレースを実行するノードです。
特徴
トレース応答を判断基準にしている為、「Overlap」は反応せずに「Block」のみ反応します
Multi Line Trace By Channel
Line Trace By Channelの複数感知版ですが、そこそこややこしいのが、このノードです。
特徴
- 「Blockオブジェクト」までに存在する複数個の「Overlapオブジェクト」を感知します
- 「Blockオブジェクト」が感知出来ない場合は「Overlapオブジェクト」を無視します
参考サイト
Line Trace For Objects
通常イメージするレイトレースはこのノードになると思います。
特徴
上記ノード(~ Channel)と違い、判断する基準が「Object Type」になっています。なので、その点を気にしないと反応しない事になりかねません。ただし、引数が配列なので複数タイプを判断可能です。
Multi Line Trace For Objects
このノードも同じく「Object Type」を判断基準にしている場合のレイトレースになっています。Multi Line Trace By Channel
よりもシンプルに反応してくれます。
参考サイト
AI
敵などに使われるAIです。基本的にはビヘイビアツリーで実装する事になります。詳細は下記サイトを覗いて見て下さい。というより、公式・先人の方々が十分説明されているので、詳細についてはあまり触れません。
AIの作成
まず、何も考えずAI三点セット(ブラックボード・ビヘイビアツリー・AIコントローラー)を作成します。
次に、AIを使用したいキャラクターを開き「詳細 > ポーン > AI Controller Class
」に作成したAIコントローラーを設定します。
「詳細 > ポーン > Auto Possess AI
> Placed in World or Spawned
」に設定してください。でないと「AIが動かねぇ~」という事態になりかねません。
次に、ビヘイビアツリーの「詳細 > BEHAVIORTREE
> Blackboard Asset
」にブラックボードをセットして、AIコントローラー側で「Run Behavior Tree」ノードを呼び出して完了です。詳しい実行方法は下記サイトをご覧ください。
ブラックボード
初見だと、「ナニコレ?」となること間違いなしの機能ですが、自分なりの解釈では「インターフェース機能でアクセスするAI専用の構造体」です。例えば、キャラクター情報やビヘイビアツリー全体でアクセスする要素だったり、デコレーターノードで使用したりなど、色々な用途で使用すると思います。詳しい説明は下記サイトを覗いて見て下さい。ブラックボードの立ち位置は下記サイトの画像が参考になります。
ブラックボードにアクセスするには「Set Blackboard Value as ~
」で設定し、「Get Blackboard Value as ~
」で取得します。「Key」に関しては「変数へ昇格」で変数化(Blackboard Key Selector
構造体)した上で、「詳細 > 変数 > インスタンス編集可能」をONにしておいて下さい。
すると、ビヘイビアツリー側で変数の設定が可能になり、候補にBBのキーが表示されます。また、違う方法も存在するのですが、その方法は「Name型」で直接指定となっているので、関連付けが可能なこちらのやり方をオススメします。
ビヘイビアツリー
UnrealEngine君の一押しのAIシステムですが、いくつかの点に気を付ければ普通に使いやすいシステムだと思います。ともかく、資料も充実していますし、公式サイトでの日本語の説明も分かりやすくまとめてあるので、初めて使う方も安心して使う事が可能なはずです。
※ 上図はかなり適当なのでサンプルのようなものです
サービスノード
- 説明
- 「ブラックボードの更新用ノード」に当たります
- 使い方
- 注意点
デコレーターノード
- 説明
- 「ビヘイビアツリーの条件式ノード」に当たります
- 使い方
「詳細 > フローコントロール > オブザーバーを中止」を変更すると、挙動が変わったりするので注意
- 基本的にはフローコントロールは
Self
でOK- 例えば、「このタスクで終了したい」場合で
Both
に設定すると、低優先度(数値が大きい物)が飛ばされるようになる
- 例えば、「このタスクで終了したい」場合で
- 「詳細 > コンディション >
Inverse Condition
」は条件を反転する事が可能なので、「~ではない」のような場合に便利
タスクノード
- 説明
- 「ビヘイビアツリーの行動処理ノード」に当たります
- 使い方
「Finish Execute」ノードの呼び忘れに注意。でないと、永遠にタスクが終わらない事に...。
ノードの移動
何気に便利なデコレーターとサービスノードのドラッグアンドドロップ。あまり紹介されていない気がするのは、気のせいでしょうか?
参考サイト
ビヘイビアツリー自体を停止したい
Stop Logic
ノードを使う事で可能。例えば、プレイヤーが死亡した時にAIを動かしたくない時に使えます。
ビヘイビアツリーのデバッグ
全体的な流れ
通常のブループリントと同じ「F9」でブレイクポイントを置く事が可能です。デバッグ状態になると、簡単なタスクの動きを監視する事が可能で、赤線は前回の流れを表し、白線は現在の流れを表します。ただし、タスク毎にしか止まらず、「デコレーター」や「タスクまでの途中経過(セレクター)」で停止する事は出来ません。その場合は内部のブループリントでブレイクポイントを置く必要があります。
勿論、ブレイクポイントを設定していない状態で実行すると、リアルタイムでAIの動きが表示されます。
デコレータノード
「じゃあ、全くデコレーターのデバッグが出来ないじゃん。使えねぇ~」と思うかもしれませんが、ちゃんとデコレーターの挙動は表示されます。具体的には、失敗したデコレータの右側に赤い縦線で表示します。なので、連続でデコレータが存在する場合、かなり分かりやすいと思います。
更に言うと、後述するブラックボードデコレーターに関しては具体的な値と結果を表示してくれます。
キー
更に、ブレイクポイントに止まった状態で「ブラックボード > キー」には、その時点での値が「現行値」といった形で表示されるようになります。何気に便利な機能です。
標準のデコレーター・タスクノード
元々搭載されていて使えそうなノードを抜粋して記述します。
基本的に自作のブループリントノードはC++と比べ遅くなります。パフォーマンスを気にするならばC++で作成するのも考えておいた方がいいかもしれません。
タスクノード
-
Move Directly Toward
- 指定のアクタか座標へ直線移動するノード
- ナビメッシュ関係なく動きます
- 詳しくは「UE4 AIがナビメッシュを使わずに移動する際のTips - Hatena Blog」をご覧ください
-
Move To
- ナビメッシュを使用して指定の座標へ移動するノード
- ナビメッシュを使用しているので、途中に存在するオブジェクトを避けて動きます
- 設定次第で到達不可能な場合でも、出来る限り移動しようとします。
- ナビゲーションシステムについては「[UE4] ナビゲーションシステムの概要 - Qiita」を、ナビゲーションメッシュについては「UE4 ナビゲーションメッシュを作成する - 凛のUE5/UE4とゲーム制作と雑記ブログ」をご覧ください
-
Play Animation
- アニメーション再生を行うノード
- 何と、ビヘイビアツリー側でアニメーション再生が可能になっています
- 非常に簡素な敵のアニメーション管理なら使えそうですが、「アニメーションブループリントとビヘイビアツリーの両方で管理をする」といった事は絶対止めた方がいいです
-
Play Sound
- サウンド再生をするノード
- ビヘイビアツリー側で再生可能なのですが、まだ
Play Animation
よりかは使う機会が多いのかなと思います
-
Rotate to face BB entry
- 指定の方向に回転をするノード
-
Move Directly Toward
の回転版に当たります - ただし、「指定の」
- 詳細は「ビヘイビアツリーの「Rotate to face BB entry」タスクについて - Hatena Blog」をご覧ください
-
Run Behavior
- 別のビヘイビアツリーを起動するノード
- いわゆる、「ビヘイビアツリー in ビヘイビアツリー」です
- 詳細は「第四十八回 UE4標準のタスクについて "Run Behavior"編 - FC2」をご覧ください
- 動的バージョンもあるのですが、そちらについては「第0073回 / NPCに仕事をさせる - Ameba」をご覧ください
-
Wait Blackboard Time
- 待機をするノード(キーで時間指定)
- もっとシンプルな
Wait
ノードもあるのですが、時間調整が動的に出来ないのでこちらのノードをオススメします - 攻撃後や行動後のクールタイムなどに使用します(デコレーター側にクールタイムノードがあるのですが残念ながら静的指定なので微妙...)
簡単に紹介しましたが、非常に簡素なキャラに限ってはビヘイビアツリーだけで、アニメーション・サウンド・行動がコントロール出来るので時短になります。ただし、少しでも複雑になってくる場合は流石に、行動・サウンドぐらいにしておいた方がいいとは思います。
デコレーター
-
Blackboard Based Condition
- ブラックボードの値だけで評価をするノード
- intやfloatなどは大なり小なりなどの比較も可能です
- コンポジット
-
Blackboard Based Condition
以上、ブループリント未満のロジックを組む事が出来るノード - 「ブラックボードの単純比較だけでは物足りないが、ブループリントを作成するまでもない」といった場合に使います
- And, Or, Notの論理演算を使用する事が可能です
-
-
Conditional Loop
- 条件が一致している限り常に処理が続くノード
- ブラックボードの値を使えるので、特定の条件で常に処理しておきたいといった場合は非常に便利だと思います。
- ルートから再評価という形でも同じにはなると思いますが、このノードを使うと実行範囲が限られるので、バグを減らせるのかもしれません
-
Cone Check
- コーンの範囲内に目標が存在するかを判定するノード
- コーンの半径は静的決定ですが、原点・方向・目標(座標指定)はブラックボードの値を使用できます。
- パッと思いつくのは、ステルスゲームなどの視野内判定に使えると思います
-
Does Path Exist
- 2点間の座標でナビメッシュを使った経路探索を行うノード
- 経路探索に複数の種類があり、それぞれ速度と精度が異なります
- 勿論、ブラックボードの値を使えるので動的決定です
-
Is At Location
- 指定の位置に存在するかを確認するノード
- ある程度の誤差を許容する範囲は静的決定ですが、指定の位置に関してはブラックボードの値を使います
- ナビメッシュを使って、経路と一致しているかを確かめる事も出来ます
デコレーターノード側も標準でかなり便利なノードが存在します。なのですが、少し不満点を挙げるならもう少し動的に決定出来るノードを増やしてほしいです。特に、Cooldown
ノードは本当に思います。動的なら間違いなく便利なので紹介していました6。
デコレーターノードの自作ブループリントだけ、速度低下云々については無いみたいです。なので、標準のノードに不便に感じたら、気にせず自作ブループリントノードを作成してしまって構わないと思います。
ビヘイビアツリーの動き
AI最後として、割とややこしいビヘイビアツリーの挙動のお話です。というのも、普通に使うだけでも色々ゴチャゴチャな動き方をします。
例えば、デコレーターノードの「詳細 > フローコントロール > オブザーバーを中止」をNone
以外にしたり、サービスノードの「詳細 > サービス > Call Tick on Search Start
」をONにすると関数の呼ばれるタイミングが変わってきます。ホントにヤヤコシイデス。
大体の流れを理解しておけば、各関数の意味が分かってくると思います。
前提条件
実行前
まず、AIコントローラー側で「Run Behavior Tree」ノードが実行されます。恐らく、ノード内の処理でルート直下のサービスノードが呼び出されます。ノード内では「下位ノードの情報取得開始」と「実行開始」が呼び出されます。
-
Run Behavior Tree
ノード 実行-
ルート直下のサービス2 動作
- イベント Receive Search Start(下位ノードの情報取得開始?)
- イベント Receive Activation(実行開始)
-
ルート直下のサービス2 動作
タスク1 実行
「Run Behavior Tree」が呼ばれた後、ルート直下のサービスで「更新」と「下位ノードの情報取得開始」が呼び出されます。次に、左下にあるデコレーター(下位ノード)が呼ばれ、「状態判定」が行われて成功した時だけ、同ノードの「実行開始」とタスク・サービスノードの処理が実行されます。
下位のデコレーターから上位のサービスへ移行して、サービスの「更新」を呼び出します。更に、下位のサービスへ移行して「実行開始」を呼び出します。ようやく、下位のタスクの「実行開始」を呼び出してタスクの更新処理を始めます。
タスクの更新ですが、最初にタスクの「更新」が呼び出され、サービスの「更新」が呼ばれるのですが、上から順に呼ばれます。つまり今回は「サービス2 → サービス1」の順番になります。タスクで「Finish Execute」が呼ばれるまで、「タスク1 → サービス2 → サービス1」が繰り返されます。
タスクの終了の合図として「Finish Execute」関数を呼び出し、デコレーターの「実行終了」が呼び出されます。そして、次点のデコレーターへ移行した上で「状態判定」が行われ、成功した場合のみ「実行開始」が呼び出されます。もし、した場合、実行していたタスクにくっついていたサービスの「実行終了」が呼び出されます。
文章まとめ
-
ルート直下のサービスノード2 動作
- イベント Receive Tick(更新処理)
- イベント Receive Search Start(下位ノードの情報取得開始?)
-
下位ノードのデコレータ1 動作7
- Perform Condition Check AI(状態判定 ※今回は全て成功)
- イベント Receive Execution Start(実行開始)
-
上位のサービス2へ移行
- イベント Receive Tick(更新処理)
-
下位のサービス1へ移行・動作
- イベント Receive Activation(実行開始)
-
タスク1 動作
- イベント Receive Execute(実行開始)
- イベント Receive Tick(更新処理)
-
最上位のサービス2へ移行
- イベント Receive Tick(更新処理)
-
次の上位のサービス1へ移行
- イベント Receive Tick(更新処理)
- タスクの更新で「Finish Execute」が呼び出されるまで無限ループ
- タスク1を更新
- 最上位のサービス2を更新
- 次点のサービス1を更新
- 下位のノードが見つからないので終了 = 最下位
- タスクの更新に戻る(1へ戻る)
- タスク1で「Finish Execute」関数を呼び出す(タスク1 更新終了)
-
デコレーター1へ移行へ移行
- イベント Receive Execution Finish(実行終了)
-
次点ノードのデコレータ5 動作
- Perform Condition Check AI(状態判定)
- 失敗した場合
-
実行中のサービス1へ移行移行
- イベント Receive Deactivation(サービス1 実行停止)
- 一番上へ戻る(サービス2 動作)
-
実行中のサービス1へ移行移行
- 成功した場合
- イベント Receive Execution Start(実行開始)
タスク3 実行
前項でデコレータ5が状態判定で成功した場合のお話です。最初に、サービス5へ移行した後に「下位ノードの情報取得開始」が呼び出され、デコレータ3へ移行します。そして、デコレータの「状態判定」で「成功」した後に「実行開始」が呼び出されます。
関連が停止したサービス1へ移行した後、実際に「実行停止」が呼び出されます。停止されたので、次点のサービス5へ移行したうえで、「実行開始」と「更新」が呼び出されます。更に、下位のサービス3へ移行して、「実行開始」が呼び出されます。
サービス関係の準備が整ったので、実際にタスク3の「実行開始」と「更新」が呼び出されます。タスク1の時と同じように、上から呼ばれるので「サービス2 → サービス5 → サービス3」の「更新」が呼び出されます。そして、タスク3の「更新」へ戻ります。
終了の合図がタスク3で呼び出され、デコレータ3へ移行した上で「実行終了」が呼び出されます。次に、次点のデコレータ4へ移行して「状態判定」が呼び出されて成功した場合は、デコレータの「実行開始」が呼び出されます。
文章まとめ
-
サービス5へ移行
- イベント Receive Search Start(下位ノードの情報取得開始?)
-
デコレータ3へ移行
- Perform Condition Check(状態判定 ※今回は全て成功)
- イベント Receive Execution Start(実行開始)
-
サービス1へ移行
- イベント Receive Deactivation(実行終了)
-
次点のサービス5へ移行
- イベント Receive Activation(実行開始)
- イベント Receive Tick(更新処理)
-
最下位のサービス3へ移行
- イベント Receive Activation(実行開始)
-
タスク3へ移行
- イベント Receive Execute(実行開始)
- イベント Receive Tick(更新処理)
-
サービス2へ移行
- イベント Receive Tick(更新処理)
-
サービス5へ移行
- イベント Receive Tick(更新処理)
-
サービス3へ移行
- イベント Receive Tick(更新処理)
-
タスク3へ移行
- Finish Execute 呼び出し(更新終了合図)
-
デコレータ3へ移行
- イベント Receive Execution Finish(実行終了)
-
デコレータ4へ移行
- Perform Condition Check(状態判定 ※今回は全て成功)
- イベント Receive Execution Start(実行開始)
タスク4 実行
大体の流れは「タスク3 実行」と同じで、呼ばれる場所が異なるだけなので、ある程度は飛ばします。タスクの更新終了(Finish Execute関数を呼んでから)から載せます。
実行完了の手順は、サービスもデコレータも下位から上位の順で呼び出されます。つまり、「デコレータ4 → デコレータ5」・「サービス4 → サービス5」といった感じに「実行終了」が呼び出されます。
終了手順が呼び出されたら全てのタスクを一巡し終えたので、最初からである「サービス2の更新」から始まります。
ただし、タスク中に中断・終了した場合などは、最上位から最下位のノード順で「実行終了」が呼ばれます。そして、タスクノードの「イベント Receive Abort」が呼ばれるみたいです。名前の通りですね。
「イベント Receive Search Start」ノードについて
今回は「下位ノードの情報取得開始」だろうと考えて作成しましたが、間違っているならすいません。呼ばれる順番については書かれているサイトはあったのですが、目的そのものについては「コレだ!」といったものは見つかりませんでした。なので、憶測で決めました。ビヘイビアツリー自体がツリー構造のはずなので、「下位ノードの検索を行う」前に呼ばれるものだと思ったからです。
呼び出しの履歴(テキスト)
上記の動きは下記のテキストを書き起こしたものです8。念の為、テキストの方も残しておきます。コメント(//)に関しては独自に追記したものです9。
サービス2 Search Start // サービス2 下位ノードの情報取得開始?
サービス2 Activation // サービス2 実行開始
// AIコントローラー側でのRun Behavior Tree呼び出し済み
サービス2 Tick
サービス2 Search Start // サービス2 下位ノードの情報取得?
デコレーター1 Condition Check // デコレーター1 成功判定
デコレーター1 Execution Start // デコレーター1 実行開始
サービス2 Tick
サービス1 Activation // サービス1 実行開始
タスク1 Execute // タスク1 実行開始
タスク1 Tick
サービス2 Tick
サービス1 Tick
// タスク1 実行中
タスク1 Tick
サービス2 Tick
サービス1 Tick
// タスク1 Finish Execute呼び出し
デコレーター1 Execution Finish // デコレーター1 実行終了
デコレーター5 Condition Check // デコレーター5 成功判定
デコレーター5 Execution Start // デコレーター5 実行開始
デコレーター5 Search Start // サービス5 下位ノードの情報取得?
デコレーター3 Condition Check // デコレーター3 成功判定
デコレーター3 Execution Start // デコレーター3 実行開始
サービス1 Deactivation // サービス1 実行終了
デコレーター5 Activation // サービス5 実行開始
デコレーター5 Tick
デコレーター3 Activation // サービス3 実行開始
タスク3 Execute // タスク3 実行開始
タスク3 Tick
サービス2 Tick
デコレーター5 Tick
デコレーター3 Tick
// タスク3 実行中
タスク3 Tick
サービス2 Tick
デコレーター5 Tick
デコレーター3 Tick
// タスク3 Finish Execute呼び出し
デコレーター3 Execution Finish // デコレーター3 実行終了
デコレーター4 Condition Check // デコレーター4 成功判定
デコレーター4 Execution Start // デコレーター4 実行開始
デコレーター3 Deactivation // サービス3 実行終了
デコレーター4 Activation // サービス4 実行開始
タスク4 Execute // タスク4 実行開始
タスク4 Tick
サービス2 Tick
デコレーター5 Tick
デコレーター4 Tick
// タスク4 実行中
タスク4 Tick
サービス2 Tick
デコレーター5 Tick
デコレーター4 Tick
// タスク4 Finish Execute呼び出し
デコレーター4 Execution Finish // デコレーター4 実行終了
デコレーター5 Execution Finish // デコレーター5 実行終了
デコレーター4 Deactivation // サービス4実行終了
デコレーター5 Deactivation // サービス5 実行終了
タスク1 Abort // タスクを中断した際に呼び出される(ゲーム終了など) 今回の場合はESCキーで終了
// 全タスク完了
次回へ
予想以上に書く量が多くなってしまい、投稿が遅くなってしまいました。申し訳ありません。実はまだまだ書くことがあるので、次回へ続きます。次回はアニメーションやアセットなどを色々書きます。流石にそこそこの量で分割するので、全3~4回で終わると思います。今回は多すぎたなぁ。今度こそ3月末に上げるぞ~!
2週間で終わらなくて悲しい事態になってしまいました。自分の悪い癖なのですが、「1記事で書き終わりたい欲」が...ダメですね...最初から下書きを分割しとくべきでした。言い訳するなら、全て動くようにUE5上で確認したり、追加で書きたい事が増えたりするんですよね...。
-
公式的には「マテリアルダイナミックインスタンス」なんですが、普段使いでは「ダイナミックマテリアルインスタンス」なんですよね。ですが、今回は公式の言い方で統一します。 ↩
-
MDI
の値を変更する時は、この戻り値を使用するためです。変更するタイミングが初期化以外なら、変数化しないと変更できません。※ 今回はテストなので戻り値のピンの値をそのまま使っています(本来はローカル変数にしておくべきかと...) ↩ -
MI
化する利点は「コンパイル時間の短縮・作業の簡易化・シェーダーサイズ軽量化」などが挙げられます。 ↩ -
個人的にゲームを作成して、SNSに動画を公開する程度なら問題ないと思いますが、少なくとも使用させてもらったURLなどはコメントなどとして記述しておくべきです。 ↩
-
ごく稀に、マクロでHLSL/GLSLを切り替えて使えるコードが存在するのですが、基本的にはこちら側で変換する必要があります。 ↩
-
ふと思ったんですが、静的なノードがどれも時間に関係するノードですね。やはり、時間関係となるとややこしくなるのでしょうか?となると、
Wait Blackboard Time
が特殊なだけ? ↩ -
UnrealEngineのビヘイビアツリーでは、ルート分岐した時は左から実行されます。 ↩
-
今回の記事で一番メンドクサカッタ作業だったりします。 ↩
-
方法は完全にゴリ押しで、全てのノードの全ての関数にPrintStringでそれぞれ記入します。そして、アウトプットログの出力された文字をWordコピペして、余計なものは消して、ジャンル分け(着色)して...という悲しい方法ですね。更に、それをQiitaの記述方法に変換するという...。 ↩