Unityのナビゲーションシステムの技術的詳細とパフォーマンス上の注意点についてまとめました。
Unity2019.2.6
はじめに
ナビゲーションシステムはとても便利ですが、仕組みを知らずに使うとパフォーマンスに影響を及ぼしがちです。
そこでこの記事ではUnityのナビゲーションシステムの技術的な仕組みを簡単に解説し、
パフォーマンスを落とさないために注意すべき点について説明します。
ナビゲーションシステムの基本的な知識については本筋と外れるため本記事では説明しません。
こちらについては以下の記事にまとめていますので、必要に応じて参照してください。
ナビゲーションシステムによる経路探索の仕組み
さて、いま以下のように地面と障害物が配置されているとします。
最短経路を求める際、ナビゲーションシステムはまずすべての地面と障害物を集めます。
そしてこれらから通行可能なエリアを計算しポリゴンとして表します。
この処理をベイクと言い、出力されたポリゴンをナビメッシュと呼びます。
このとき、ポリゴンはかならず凸多角形になっています。
凸多角形にしておけば、ポリゴンの内側にある二点間は確実に移動可能であることが保証されるためです。
さてここまでは通常、非ランタイムで行われる処理です。
(NavMesh building componentsを使うとランタイムでビルドすることもできます)
次にランタイムで目標地点が設定されると、ナビメッシュに対してA*アルゴリズムを使った経路探索が行われます。
これはGlobal Navigationと呼ばれて、比較的重い処理になります。
これにより、現在の位置のポリゴンから次にどのポリゴンに進めば最短経路になるかがわかります。
ちなみにA*に関しては本記事では説明しませんが、下記にまとめていますので必要に応じて参照してください。
ここまでの処理で、次に目指すべき位置、その次に目指すべき位置・・とゴールまでの経路が決定されます。
あとはこれを辿るだけなのですが、他のエージェントや動的に動く障害物に衝突しないように進む必要があります。
これはLocal Navigationと呼ばれ、Global Navigationよりも軽い処理になります。
まとめるとこんな感じになります。
以上がナビゲーションシステムによる経路探索の仕組みです。
障害物を配置する際にはパフォーマンスに注意
GameObjectにNav Mesh Obstacleコンポーネントをアタッチすると、そのオブジェクトを障害物にできます。
これでエージェントはこの障害物をLocal Navigationで衝突判定するので通過できなくなります。
ただ、上図から見て取れるようにこの状態ではNav Mesh自体には変化は加わっていません。
つまり、Global Navigationの際には通過できる経路として認識されてしまいます。
つまり、たとえばこの障害物により袋小路が作られてしまった場合には、
エージェントはまずGlobal Navigationにより算出された経路に従い袋小路を進んでいきますが、
障害物に近づいたときにはじめてLocal Navigationにより進めないことが判明し、そこで止まってしまいます。
障害物の形にNav Meshをくり抜くには、Nav Mesh ObstacleのCarveにチェックを入れます。
これにより動的な障害物をNav Meshに影響させることができますが、ここでパフォーマンスの問題が出てきます。
まず一点目にこのくり抜く処理自体の処理負荷が高いことです。
また、Nav Meshの構成が変わるということは、Global Navigationの計算をやり直す必要があります。
これは上述の通り比較的重い処理なので、頻繁に行われるとパフォーマンスに影響します。
これらの理由から、基本的にはCarveのチェックは外してLocal Navigationのみで処理するべきです。
上述の袋小路のような状況が起こる場合のみ、Carveにチェックするべきです。
障害物でNav MeshをCarveする場合の設定
このように動的な障害物は注意して扱わないとパフォーマンスに影響するため、
Carveする条件などが細かく設定できるようになっています。
まずCarve Only Stationaryにチェックを付けると、
障害物が動いてから止まった後、一定時間が経過した時点でNav Meshのくり抜き処理を行います。
この「一定時間」はTime To Stationaryで設定できます。
また、Carve Only Stationaryのチェックを外すと、
障害物が一定距離を移動した時点でNav Meshのくり抜き処理が行われます。
この「一定距離」はMove Thresholdで設定できます。