お世話になっております。
現在、複数のレベルを1つのワールドパーティションレベルに配置し、データレイヤーで有効・無効を切り替えておりますが、
レベルインスタンス内のナビメッシュが登録しない問題が発生いたしました。それに関する質問をさせていただきます。
①
各レベルでビルドしたワールドパーティション用のナビメッシュを、そのまま親レベルで使用できるように実現したいのですが、
下記より適切な作成方法や推奨される構成があれば、ご教示いただけますと幸いです。
■プロジェクト設定
Should Discard Sub Level Nav Data = false
■構成
親レベル
└ レベル1
└ RecastNavMesh (WP Static)
└ NavMeshBoundsVolume
└ NavDataChunkActor
└ レベル2
└ RecastNavMesh (WP Static)
└ …
└ レベル3
└ RecastNavMesh (WP Static)
└ …
■補足
・親レベルにはレベルインスタンのみが配置されており、それ以外のアクターは存在しません。
・親レベル上で複数レベルを同時にアクティベートすることはありません。常に1つのレベルのみをアクティベートする構成です。
②
ナビメッシュが登録しない問題は ARecastNavMesh::GetNavigationDataChunk で UNavigationDataChunk が求めているナビゲーションデータの名前が不一致しているため、弾かれていることを把握しております。
そして、下記のように PIE 開始前のナビゲーションデータの名前が UNavigationChunk が求めている名前が同じですが、PIE 開始後はナビゲーションデータの名前の後ろにハッシュ値が追加されました。
レベルインスタンスの仕組みで追加されたと思われますが、それに関してエンジン修正無しのワークアラウンドがあれば、ご教示いただけますと幸いです。
PIE 開始前
RecastNavMesh_UAID_C87F5453448C4EFB02-Default
PIE 開始後
RecastNavMesh_UAID_C87F5453448C4EFB02-Default_8fc519b3b491e78a
※添付しているスクショを確認していただければと思います。
③
上記の構成は今後のエンジン更新でサポートする予定はありますでしょうか?
以上となります。よろしくお願いいたします。
UDN_ナビゲーションデータの問い合わせ_2.png(3.19 MB)
UDN_ナビゲーションデータの問い合わせ_1.png(3.15 MB)
Ken.Kuwano
(Ken.Kuwano)
2
お世話になっております。
① 推奨構成ついて
結論から申し上げますと、子レベル(LevelInstance内)で焼いたナビメッシュを、親パーシスタントレベルのLevel Instanceとして配置した先でそのまま使うという構成は、UE 5.6でも、5.8以降でもサポート対象ではありません。 推奨は「ナビメッシュの本体は親に置き、子レベル(LevelInstance)にはジオメトリだけ置く」ことです。RecastNavmeshはエージェント毎に1つが前提となっており、同じエージェントタイプのRecastNavmeshが複数存在するようなケースは想定されておりません。DataLayerのようなレベルストリーミングが発生するケースにおいて、子に配置するのはNavMeshBoundsかNavModifierのどちらかで、その際には、RuntimeGenerationがDynamicか、DynamicModifierOnlyのどちらかを利用することになります。
②名前不一致と、エンジン修正なしの回避策について
ご認識のとおり、GetNavigationDataChunk は 完全一致 です。
URecastNavMeshDataChunk* ARecastNavMesh::GetNavigationDataChunk(const TArray<UNavigationDataChunk*>& InChunks) const
{
FName ThisName = GetFName();
int32 ChunkIndex = InChunks.IndexOfByPredicate([&](UNavigationDataChunk* Chunk)
{
return Chunk->NavigationDataName == ThisName;
});
また、名前の後ろに付く値は PIE 接頭辞ではなく、Level Instance をセルへ pack するときの ContainerID ハッシュです。エンジンソースを触らない回避方法は現状ありません。取れる手段としましては、ナビメッシュの構成を変更するか、プロジェクト側コードです。前者は、Runtime Generation=Dynamicで運用する方法です。後者は、プロジェクト側のゲームモジュールで
ARecastNavMesh を継承したサブクラスでSupported Agents の Nav Data Class に差し替える方法です。GetNavigationDataChunk は private なので override できませんが、OnStreamingNavDataAdded / OnStreamingNavDataRemoved は virtual、AttachNavMeshDataChunk は public なため、この継承先で NavigationDataName の名前比較を緩くする方法になります。ただし、この方法でLevelInstance内で焼いたナビメッシュを残したとしても、それは想定される本来の使い方からはずれるので、御社のプロジェクト側で十分に動作確認をして頂く必要がございます。
③ 今後のエンジン更新でサポートされるか
現状、その予定は公表されておらず、現時点ではタスクや計画にもないため、このような運用を前提とせずにご利用頂くことをお勧めいたします。
Ken.Kuwano
(Ken.Kuwano)
3
お世話になっております。
以下の情報を追加でお知らせしたいと思います。
④ 動的なナビメッシュを採用する際の注意点について
親 Persistent Level に RecastNavMesh を置き、IsWorldPartitioned = true、RuntimeGeneration=Dynamic に設定します。DynamicModifiersOnly は「歩き面の形は変えず、エリアコスト等だけ更新」するケースで有効なので、Level Instanceでジオメトリのストリーミングが発生する場合には反映できません。Dynamicにしても、ナビメッシュは WorldPartitionNavigationDataBuilder でベースとして焼いておく必要があります。WP Recast はタイルを RecastNavmesh に保存せず、NavDataChunk 側に置きます。以下、Dynamicで運用する際に調整するパラメータの対象です。
- TileSizeUU : 1タイルの辺のサイズ、大きくすると1タイルあたりの更新範囲は大きくなりますが、総タイル数が減るため初期生成のコストが低くなります
- CellSize/CellHeight:ナビメッシュボクセルのサイズ、大きいとナビメッシュが粗くなりますが、広大なマップの場合は可能な限り大きくすることが望ましいです
- bFixedTilePoolSize/TilePoolSize:ストリーミングするなら固定プールの方がメモリの面でも上限を固定できますが、想定する範囲全てにタイルを生成できずにナビメッシュが生成されないこととのトレードオフとなります
- AverageLayersPerTile:多層地形では上げた方が良いですが、不足するとタイル追加に失敗し、ナビメッシュが生成されないことが発生します
- MaxSimultaneousTileGenerationJobsCount:ナビメッシュ生成時に使用するワーカースレッド数の上限、デフォルトの1024だと空きのワーカーを全て使用しナビメッシュを生成するため、ナビメッシュ生成は早くなりますが、ジョブ生成時の瞬間的なヒッチと、ジョブでワーカーが埋まってしまうことのトレードオフが発生します、逆に緩いとヒッチは抑えられますが、ナビメッシュ生成に時間がかかります
- Generate Navigation Only Around Navigation Invokers:LoadingRange が大きいときの生成キャップ設定
- InvokerTileGenerationRadius/RemovalRadius:Invokerの周辺のみナビメッシュ生成を有効にする際の距離、この値が大きいとInvokerでのナビメッシュ生成範囲が広くなりますが、ナビメッシュ生成のメモリも増加します
⑤ 動的なナビメッシュを生成する範囲と、WorldPartitionのLoadRangeの関係性について
NavigationSystem 側に LoadingRange の参照はなく、直接的な関係性はありません。LoadingRange が決めるのは「何がメモリに載るか」だけで、RecastNavmesh がボクセル化するのはdirtyタイルとActiveタイルです。遠景表示のために LoadingRange を大きくする方針そのものはナビメッシュと直結していませんが、このケースではNavInvokerを使用した方がメモリの面でも安定かもしれません。Invokerを利用するケースにおいては、LoadingRange < InvokerTileGenerationRadiusとし、可視範囲よりもナビメッシュの生成範囲を小さくしておくと良いです。
⑥ WorldParttion+静的なナビメッシュは採用可能ですか?
ナビメッシュが生成されてエージェントが移動する面がDataLayer切替で変わらない、という前提であれば可能です。変わるなら Static単体ではナビメッシュ生成が追従しません。DataLayerの切り替えは実質 LevelStreaming なので、ジオメトリの変化に伴うナビメッシュ更新が発生しないことになります。これは LevelInstance のBehaviorがStandaloneのケースも同様です。
⑦ LevelInstance に RecastNavmeshを配置することは現実的か?
例え Should Discard Sub Level Nav Data = false としたとしても、複数LIがある場合は2つ目以降のRecastNavmeshが生成できないため、現実的ではなく、またEpicでもテストされたケースではないため、安全な方法ではありません。
ご返答ありがとうございます。
弊社内でさまざまな検討を行った結果、動的なナビメッシュについて検証を進めることとなりました。
検証を進める中で、追加のご質問をさせていただく可能性がございますが、まずは一度 UE で推奨されている構成を試してみたいと考えております。
引き続き、よろしくお願いいたします。
Ken.Kuwano
(Ken.Kuwano)
5
ご確認頂きありがとうございます。
本件はクローズいたしますが、何か追加でご質問がありましたらコメントを追加して頂くか、別途スレッドを立ててお知らせ頂けますと幸いです。どうぞよろしくお願いします。