【UE】動的NavMeshを扱うときの注意点
今回はUnreal Engineで動的NavMeshを扱うときの注意点と対処法について紹介します。
動的NavMeshとは
通常、NavMeshは決まった地形(メッシュ)に合わせて事前に作っておきますが、扉が開いてルートが増えたり、オブジェクトが壊れて道を塞ぐなどルートが変化することがあります。
そういった場合に、ランタイム(ゲーム実行中)に NavMesh を作り直したい場合、動的NavMeshを使用します。
動的NavMeshの注意点
NavMeshのルートを作り変えることを「NavMeshの構築」と呼びます。
この再構築はとても負荷の高い処理であることを開発者は理解する必要があります。
NavMesh再構築が重い理由①:CPUストール(停止)が発生する
NavMeshは内部的にスレッドセーフ (複数スレッドからDirty Areaの更新があっても破綻しない) を強く意識した設計となっており、再構築中は NavMesh データへの同時アクセスが制限されます。
その結果、NavMeshの構築中に別の再構築リクエストが実行されたり、NavMeshの走査が行われると CPUストールが発生し、大幅なロスが生まれます。
NavMesh再構築が重い理由②:デフォルトの稼働スレッド数が少ない
NavMesh構築はスレッドで動作していますが、デフォルトの稼働スレッドは "1tickあたりコアx2" しか処理しないようになっています。(UE4.x系時点)
NavMesh更新のタイミングを知る
更新タイミングの設定
NavMeshの更新タイミングは「プロジェクト設定 > エンジン > ナビゲーションシステム」から「Navigation System > ダーティ領域の更新頻度」で設定できます。

どれくらいの「領域」が更新されるのか
NavMeshは空間ごとに「タイル」と呼ばれる単位で生成されていきます。そして「タイル」→「セル」→「各メッシュポリゴン」といった管理方式を持っています。
タイルサイズは「プロジェクト設定」、レベルごとに作成したNavMeshのインスタンス (RecastNavMesh) にて設定可能で、このタイルサイズに基づき、NavMeshの再構築リクエストが実行されると、そのリクエスト領域に合致するタイルがすべて再構築されます。(合致しないタイルは再構築されません)

再構築リクエストはどの「タイミング」で実行されるのか
NavMeshの再構築リクエストは、"NavMesh影響" を及ぼす設定を行っているインスタンスのTransformが変動した際などに行われます。"NavMesh影響" を及ぼすかどうかの設定は、 UActorComponentにて定義されています。(例えば StaticMeshComponent、SkeletalMeshComponent、PrimitiveComponentなどです)
そして再構築に影響を及ぼすかどうかは「デフォルトがON」になっているため、以下の方針で適切に ON/OFF を設定すると良いです。
| ケース | 設定 | 具体例 |
| シーン中は動かない | 再構築が起きないのでON | 壁、床など |
| シーン中に動く。しかし変動タイミングは限定的 | 「更新頻度:低い=処理負荷への影響:低い」のでON | 破壊オブジェクトなど |
| シーン中に "常に" 動く "小さな" オブジェクト | 「更新頻度:高い=処理負荷への影響:高い」のでOFF | 物理挙動する小物など |
| シーン中に "常に" 動く "大きな" オブジェクト | 処理負荷と要相談だが、基本的にOFF | 移動床など |
"NavMesh影響" を及ぼすかどうかの設定箇所は、以下のように各BPやインスタンスのコンポーネントの詳細で設定、またはC++・BPで CanEverAffectNavigation関数を呼び出して設定します。(UE 4.x時点)


対策①:再構築によるCPUストールを防ぐ方法
構築中の再構築やNavMeshの走査によるCPUストールを防ぐには、主に以下の方針にすると良いです。
- NavMesh構築時間を監視するシステムを作る: これは直接の解決ではありませんが、ゲームが許容する構築時間を監視し警告を出力するシステムを作ります
- 同時に複数のNavMesh再構築が起きないようにする:多重のNavMesh再構築がCPUストールの主な原因です。これを防ぐのが最も効果的です
- NavMeshが構築中に再構築リクエストを送らないように待ちを入れる:暗転中以外のゲームプレイ中に再構築リクエストが重複する場合は、演出を入れるなどで待ち処理を誤魔化す仕組みがあると良いです
“NavMesh影響” を設定する
前述の「再構築リクエストのタイミング」の項で説明した通り、ゲームでの目的や扱いによって “NavMesh影響” を適切に ON/OFF します。
NavMesh構築中チェックの実装方法
NavMeshが構築中かどうかの判定処理は BPには提供されていません。
そのため以下のようなC++コードで実装する必要があります。
bool UCPP_SystemFunctions::IsNavigationBuildInProgress( UWorld* World )
{
if ( !World ) return false;
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if ( !NavSys ) return false;
bool isProgress = NavSys->IsNavigationBuildInProgress( );
if ( isProgress ) return true; // ビルド中.
// ----------------------------------------------------
// 以下はビルド終了後の処理.
return false;
}NavMeshのビルド(構築)ロックの実装方法
例えば、ゲーム開始前のロード画面での暗転中に、自動生成された地形に NavMesh を構築し、その後 “NavMesh影響” を持ったオブジェクトを Spawn したいときがあると思います。しかしこれが行われると、オブジェクトの Spawn ごとに NavMeshの再構築が繰り返し実行され、CPUストールが発生します。
これを防ぐには NavMesh のビルド (構築) を一時的に停止すると良いです。
これもBPには用意されておらず、C++で自作する必要があります。
// ビルドロック
void UCPP_SystemFunctions::NavimeshBuildLock(UWorld* World)
{
if (UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World))
{
uint8 id = 1; // フラグ番号.
NavSys->AddNavigationBuildLock( id );
}
}
// ビルドアンロック
void UCPP_SystemFunctions::NavimeshBuildUnlock(UWorld* World)
{
if (UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World))
{
uint8 id = 1; // フラグ番号.
NavSys->RemoveNavigationBuildLock( id );
}
}対策②:NavMesh構築のスレッド数を増やす
NavMesh構築のスレッドはデフォルトで1 tickあたり「コア x 2」なので増やしておきます。
これもC++での実装が必要で、以下は64コアに増やした例です。
void UCPP_SystemFunctions::NavimeshBuildPriority(UWorld* World, int Prio)
{
// FRecastNavMeshGenerator::ProcessTileTasksAsyncのPendingDirtyTilesが
// 4000タスクとかあるのに1tickにコア*2しか処理しないのでもっと処理する量を上げる
int32 mult = (Prio == 0) ? 2 : 64;
if (UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World))
{
for (ANavigationData* NavData : NavSys->NavDataSet)
{
if (!NavData) continue;
if (auto* Generator = NavData->GetGenerator())
{
const int32 NumberOfWorkerThreads = FTaskGraphInterface::Get().GetNumWorkerThreads();
auto maxTasks = FMath::Max(NumberOfWorkerThreads * mult, 1);
((FRecastNavMeshGenerator*)Generator)->SetMaxTileGeneratorTasks(maxTasks);
}
}
}
}おしまい
以上、動的NavMeshの注意点でした。
UE4時点で得た知見なので、やや古い情報もありますが参考になれば嬉しいです。



