【Unreal Engine】NavMeshとWaypointの経路探索を組み合わせる実験
はじめに
Unreal Engine (UE) Advent Calendar 2023の記事です。
過去、書いた記事で紹介したREエンジンの講演に「迂回する経路を取るためにWaypointで大まかな経路を探索し、NavMeshで詳細な経路を探索する」方法が紹介されていた。迂回する経路を取るための手法として非常に実装しやすい内容だったのでUnreal Engineで実験的に実装してみた。
REエンジンの講演ビデオ
RE:2023 ナビゲーションAIシステム概要とタイトル活用事例 - YouTube
迂回する経路の箇所はこちら
過去に書いた記事
【Unreal Engine】ナビメッシュのランタイム生成のコストを抑える実験 - PavilionDV7の雑多なやつ
動作の様子
プロジェクトはこちら
プロジェクトの紹介
プロジェクトを開くと色々セットアップされたマップが開く。この状態で実行すると、左下に見える「WALKER」から中央にある「TARGET」に向かって移動を開始する。
レベルに配置されている黒や白の球体はウェイポイントを表している。このウェイポイントは交差点となる地点に配置している。画像右上には少し複雑な道に見えるが、実際は1本道なのでウェイポイントは配置されない。
実行したときに画面に色々なデバッグ表示が現れる。
- 赤の球体:今回の実験で得られた経路を表示
- 緑の球体:標準機能であるFind Path to Actor Synchronouslyノードを利用して得られた経路を表示
- 青のライン:各ウェイポイントがどのウェイポイントと接続しているかを表す
標準の経路探索を使った経路よりも実験で得られた経路のほうは大きく遠回りするものであることがわかる。
TARGETの右下と左にあるウェイポイントが黒になっているが、これは「死んでいるウェイポイント」を表している。死んでいるウェイポイントは他のウェイポイントとの接続が切れているとみなされ経路探索では無視される。
ウェイポイントの死んでいる/生きているのフラグは次の画像のように公開されたBoolean型パラメータの「Active」でスイッチできる。チェックが外れていればそのノードは死んでいるウェイポイントということになる。
TARGETの左にあるウェイポイントを「生きている」にスイッチすると、経路探索の結果は次のように変化する。
アセットの紹介
注意:ウェイポイントとノード
今回の記事やプロジェクト中ではウェイポイントとノードという単語が混ざって使われてしまっているが、ここではウェイポイントもノードも経路上の地点という意味で使用している。
実際のところ「ノード」はグラフ理論で用いられる単語で「頂点・節点」を表す。この場合、ノードには人・モノ・行動といった様々な情報を割り当てることができる。
https://algo-method.com/descriptions/105
グラフ理論と経路探索するアルゴリズムの紹介|Tajima Robotics
それに対し「ウェイポイント」は「座標」を表すもので緯度・経度・高度の組の情報が割り当てられる。
BP_Waypoint
BP_Waypointはウェイポイントを表すアクターで生きているか死んでいるかを制御するBoolean型の変数と他ウェイポイントの接続を示す隣接リストを持つ。
隣接リストへの他ウェイポイントの追加は手作業であることに注意。
BP_PathFinder
レベル上に配置され、経路探索を実行するアクターはこのアクターへの参照を取得して経路探索実行を命令する。
外部のアクターは「FindPath関数」を呼び出して経路を得ることができる。FindPath関数では受け取ったスタート位置とゴール位置に最も近いウェイポイントを検索し、スタート・ゴール間の経路探索を実行する。得られたウェイポイントの経路から更にナビメッシュを用いた経路探索を実行し、最終的な経路情報を出力する。
「FindPath_Internal関数」は経路探索アルゴリズムが実装されている。ここでの処理の流れは次に紹介するページを副読本として並列して読みながら見ていけば理解しやすいかもしれない。
ちなみにブループリントでの実装はEpic Developer Communityに投稿されていたものを丸コピしたもの。一部、ウェイポイントアクターが持つフラグに対応させるためにノードを追加した。
Simple A* (A-Star) Navigation - Blueprint Implementation | Unreal engine Code Snippet
「Construct Path Points関数」ではウェイポイントの配列に対しナビメッシュを用いた経路探索を実行し、より詳細な経路を取得してVector型の配列として返す。
やることは色々あります...
この内容をもっと実用的にするならやらなければいけないことがいくつかある。
- ウェイポイント同士の接続作業をもっと簡単にする
- ウェイポイントを複製すると自動で接続リストに加える
- ウェイポイント同士を選択してコマンドを選択すると接続リストに加える(Editor Utility Widgetでできそう)
- String-Pullingの仕組みを作る
- 経路に含まれるが実際には移動する必要のないウェイポイントを削除する
- C++化
- 経路探索は呼び出し回数が非常に多く、結果として負荷が増大してしまうため、C++に移行して呼び出しコストを抑える必要がある
- 標準のナビゲーションを使った移動との統合
- 標準のナビゲーション移動では速度の調整や移動方向に合わせた回転など色々なことをやってくれているので、経路探索は自作のもので移動は標準のものを使いたい
おわりに
「迂回する経路」を求めるのは結構面倒なもの。Unreal Engineでは経路探索時のコスト計算を自分でカスタマイズして迂回する経路を取らせることもできるが、今回紹介した方法はセットアップの手間があるものの、かなり実装しやすいとてもいい方法だと思う。
参考
RE:2023 ナビゲーションAIシステム概要とタイトル活用事例 - YouTubeRE:2023 ナビゲーションAIシステム概要とタイトル活用事例 - YouTube
Simple A* (A-Star) Navigation - Blueprint Implementation | Unreal engine Code Snippet
【Unreal Engine】ナビメッシュのランタイム生成のコストを抑える実験
はじめに
RE:2023 ナビゲーションAIシステム概要とタイトル活用事例 - YouTube
上記の講演ビデオ中にある「ナビメッシュを分割できるオブジェクトを事前に配置しておき、動的なオブジェクトに合わせて有効・無効を切り替えることで、ナビメッシュの動的分割の負荷を大幅に減らす」という内容にとても感心したのでUnreal Engineでも有効な内容であるか実験してみた。
実験内容
- Nav Modifierコンポーネントを追加したブループリントを毎フレーム回転させた場合
- 事前に配置したNav Modifier Volumeを回転物の角度に合わせてArea Classを切り替える場合
上の2つの方法でどれほど負荷(ナビメッシュのリビルドを実行した回数)があるかを計測してみる。
動的にNav Modifierを更新するので
Unreal Engine でナビゲーション メッシュを修正する方法の概要 | Unreal Engine 5.3 ドキュメント
上記の「ランタイム生成 (Dynamic Modifiers Only) を使用」の項目を参考にナビメッシュのランタイム生成を有効にしておく。
計測方法
[UE5] UnrealInsights を使ってみよう|株式会社ヒストリア
上記のページを参考にUnreal Insightsを用いてプロファイリングを実行する。ゲームが起動してからフレームの変動が落ち着いたあたりから10秒間を取り出し「Recast: build navmesh」のカウント数をそのまま負荷の指標とした(厳密な測定方法がわからないので、こういう方法を取ってみた)。
結果
画像右側にある「Timersタブ」にある「Recast: build navmesh」のカウント数に注目。
- Nav Modifierコンポーネントを追加したブループリントを毎フレーム回転させた場合の結果
- 事前に配置したNav Modifier Volumeを回転物の角度に合わせてArea Classを切り替える場合の結果
気づいたこと その1
この実験中「毎フレームナビメッシュを生成させたとき、経路探索が実行できない」ことに気づいた。
もしもアスレチック要素のあるゲームで常に移動・回転し続けるオブジェクトがあり、更にNPCに追いかけっこさせたい場合、事前にNav Modifier VolumeないしはNav Modifierコンポーネントを追加したアクターを配置してArea Classを切り替える方法がおすすめ。
上のような実装が難しい場合は動くオブジェクトを完全に停止する時間を作ってあげて、ナビメッシュのビルドが完了するのを待つ方法もある。ただし見た目やゲームプレイに大きく影響するので、その点は注意すること。
ランタイム時にナビメッシュのビルドが走るタイミング
- Nav Modifierコンポーネントの場合
・Set Area Class関数を呼び出した時。 ・ルートコンポーネントのトランスフォーム(座標、回転、スケール)が更新された時。
- Nav Mesh Bounds Volumeの場合
・Set Area Class関数を呼び出した時。
Nav Mesh Bounds VolumeはMobility(可動性)を「Movable」にしてランタイム時に動かしても、ナビメッシュのビルドは走らないので注意すること。
参考
【UE5】UI Navigationを使ったプロジェクトをパッケージングしたときのエラー
はじめに
Unreal EngineでキーボードでのUI操作を実装する際に大変便利な UI Navigationプラグイン(無料) というプラグインがある。
とても便利だが、このプラグインを使ったプロジェクトをパッケージングするときに注意するべき点が1つある。
症状 - パッケージを起動したときにキーボード操作を受け付けない
タイトル画面でUI Navigationを使った操作を実装したときパッケージ版ではキーボード操作を一切受け付けない状態になっている場合がある。
試しにパッケージの種類をShippingではなく「Development」でパッケージする。
Developmentパッケージを起動するとUI Navigationの使用部分で「Error in UINav PC Component: Not all Enhanced Menu Inputs have been setup!」というエラーが画面に出力される。
解決方法
今回のエラーメッセージの場合、UI Navigationで利用されている「Enhanced Input関係のアセットがパッケージに含まれていない」ことが原因。Input ActionアセットやInput Mappingアセットが不足している状態ということ。
問題を解決するにはパッケージ設定にある「Additional Asset Directories to Cook」にUI Navigationが利用しているアセットディレクトリを指定する必要がある。Project Settings -> PackagingにあるAdditional Asset Directories to Cookに次のような項目を追加する。
- /UINavigation/Audio
- /UINavigation/Data
- /UINavigation/Fonts
- /UINavigation/Input
- /UINavigation/Textures
こうすることでUI Navigationが利用する各アセットをパッケージに含めることができた。改めてパッケージングしなおすと問題なく動作するはず。
参考
マケプレのUI NavigationのQ&Aにも同様の質問があった。
【UE5】Projectile Movement Componentを再アクティブ化したときに動かない問題
問題のブループリント
Set ActiveでFalseを渡すことで非アクティブ化しComponentのTickも停止させている。その後、逆の手順で再アクティブ化している。
しかしタイトルの通り、この手順ではProjectile Movement Componentは正しく動かない。
解決策
幸いにも同じ問題についてコミュニティフォーラムに回答があった。
Make a projectile move again after it comes to a stop - Blueprint - Epic Developer Community Forums
解決するには次の2点が必要。
- Projectile Movement ComponentからSet Updated Componentを呼び出す
- Projectile Movement ComponentのVelocityを再設定する
修正したブループリントは次の通り。
非アクティブ化した時点でProjectile Movement ComponentのVelocityは0になってしまう。そのため、Set Updated Componentを呼び出しても実際には移動しない(Velocityが0だから)。なので明示的にProjectile Movement ComponentにVelocityを再設定する必要がある。 このときVelocityには「方向 x Speed」の値を設定しなければならない(Initial Speedは再アクティブ化のときには参照されない)。
参考
Make a projectile move again after it comes to a stop - Blueprint - Epic Developer Community Forums
【テクスチャ】地割れテクスチャをGIMPで作る
はじめに
全く同じものは作れないけど近しいものをGIMPで作っていくよ。
参考
「地割れテクスチャのつくり方」のご紹介です🐣
— GAKU MORIYA (@gakumoriya) 2022年11月29日
セルパターン(静的プレート)は、
地面やガラス等の"ひび割れ"を表現するのに相性がいい模様でした🧐
How to make crack textures in AfterEffects.#aftereffects #3DCG #realtimeVFX #エフェクト #VFX pic.twitter.com/gAgWXjgihk
製作開始
サイズは1024 x 1024にする。
ツールバーから Filters -> Render -> Noise -> Cell Noise... を選択する。
Cell Noiseの設定ウィンドウが出現するので次のようにセットする。ここで重要なことは画像赤枠の「Palettize」のチェックを入れること。Shapeは最大値にしておくとパリッとしていい感じになる。
次に Filters -> Edge-Detect -> Edge... を選択する。
Edgeの設定ウィンドウで次のように値をセットする。Amountを最大値にするだけで、後はデフォルト値。
続いて左のツール類から「Warp Transform」を選択。設定ウィンドウで値をセットし適用する。
少し盛る
できたテクスチャに放射形状モーションブラーとブルームを掛けてみる。それぞれ
Filters -> Blur -> Zoom Motion Blur...
Filters -> Light and Shadow -> Bloom...
で適用する。
値はお好みで調整すること。
最終的な成果物は以下の通り。右クリックから名前をつけて保存で保存してOK。プレビューの関係で表示されていないかもしれないけど確かに貼ってあります。
UE5での使用例
次のようなマテリアルを組んでPlaneメッシュに割り当ててみた。
【UE5】鎖エフェクトを作る
はじめに
第19回ぷちコンに提出したゲームで実装した鎖エフェクトの実装を紹介。
実装開始
まずはテクスチャを用意する
自分はGIMPがメインツールなのでPhotoshop等の有料ソフトを使っている方は近しい操作で作成すること。
サイズはとりあえず1024 x 1024で作成する。 矩形選択の角を丸め、「境界線を描画」で1と0のような図形を作る。
いくつか複製し、これを横方向に並べていく。並べ終えたら出力する。
GIMP – 四角形の角を丸くする方法【写真/画像の角を丸める方法も】 | Howpon[ハウポン]
単純な画像の縁を描く(縁取りや輪郭線、アウトライン) / 《WEB素材&デザイン》
自分が作成したテクスチャを貼るので作成するのが面倒であれば「画像を保存」で保存してもOK。(念のためpng形式で保存すること) もしかするとプレビューの関係でページに映ってないかもしれない。すまん。
パーティクルマテリアルを作る
作成した鎖テクスチャをエンジンにインポートし「sRGB」の項目にチェックを入れておく。
続いてマテリアルアセットを作成し、マテリアル設定を次のようにセットする。
更に次のようにマテリアルを実装する。Texture Sampleには作成した鎖テクスチャを設定している。
鎖エフェクトを作成する
ここからは手順が非常に多くなるのでゆっくり読み進めること。
Niagara SystemアセットをDynamic Beamをテンプレートとして作成する。 続いてUser ParametersにFloat型とVector型の2つの変数を追加する。
それぞれの値を次のようにセットする。
Emitter Update -> Emitter Stateを選択し、プロパティを次のようにセットする。(画像赤枠部分)
Emitter Update -> Beam Emitter Setupを選択し、次のようにセットする。画像青枠部分の「Absolute Beam Start」「Absolute Beam End」の各チェック状況に注意すること。
Beam EndプロパティでLerp Vectorを使うことでBeam StartからBeam Endまで伸びるような動きを作ることが出来る。Lerpで使うAlpha値には「NormalizedLoopAge」を設定している。この値はEmitter Stateでセットした「LoopAge」と「CurrentLoopDuration」の除算の結果であり、値は0.0~1.0の範囲に収まるのでAlpha値として使うのにピッタリ。
Unreal Engine の Niagara エフェクトの Emitter Update グループのリファレンス | Unreal Engine 5.1 ドキュメント
Emitter Update -> Spawn Burst Instantaneousを選択し、Spawn Countを「2」にする。(1ではエフェクトが表示されない。始点と終点で2点必要だからだろうか)
Particle Spawn -> Initialize Particleを選択し、画像赤枠の部分の値をセットする。Colorはお好みでOK。画像の色だと熱を帯びた黄色や白のような色になる。
Particle Spawn -> Beam Widthを選択し、Beam Widthに「200」をセットする。
Particle Updateの「Color」と「Solve Force and Velocity」を削除し「Scale Color」を追加する。 Scale Colorは次のようにセットアップする。
最後にRender -> Ribbon Rendererを選択し、Materialを作成した鎖マテリアル、UV0 Settingsを画像赤枠のようにセットする。
後はセットアップしたEmitterをいくつか複製する。
鎖の本数を増やすためにEmitterを複製する方法を選んだのは「同タイミングに生成・消滅させるため」。Emitter StateのLoop Durationを利用して複数本のRibbonエフェクトを生成させる方法があるが、この鎖エフェクトで同様の設定をしても1本ずつしか生成されなかった。
【ナイアガラ超基礎 #10】ビームをはどうやって作られるか【Unreal Engine】 | 謎の技術研究部
あるEmitterのLocation Eventを受け取って鎖を生成する方法も試してみたが、操作が煩雑で調整に時間がかかりそうだったので、まずきちんと動く一本の鎖を作って複製する方法を選んだ。
【UE4ゲーム制作講座】NiagaraでNARUTOの千鳥っぽいエフェクトを作る - YouTube
実際に使ってみる
Actorクラスを継承してエフェクト再生用のアクターブループリントを作成する。このときキー入力を受け付けるために「Auto Receive Input」を「Player 0」に変更しておく。
続いてNiagara Particle System Componentを追加し、作成した鎖エフェクトをセット。Event Graphに次のように実装する。
エフェクト再生用アクターをレベルに配置するが、わざと「原点から少し離れた位置」に配置し再生してみる。(X:500.0 Y:500.0 Z:0.0あたりに置いてみる)
アクターを配置した位置から鎖が伸びる位置はワールド座標のX:0.0 Y:0.0 Z:500.0になっている。これは鎖エフェクトで定義したUser ParametersのBeam Endの初期値の値である。
今回のエフェクトは配置した真上に鎖が伸びてほしいのでブループリント側でパラメータの値を改めて設定する必要がある。エフェクト用ブループリントを開き次のように実装を修正する。
これでプレイすると初回はワールド座標のX:0.0 Y:0.0 Z:500.0に伸びるが、2回めからは正しく配置したアクターの真上に鎖が伸びる。 初回でおかしな方向に伸びるのはNiagara Particle System Componentの「Auto Activateフラグ」にチェックが入っているため。