【Unity】NavMeshSurfaceを使ってゲーム再生中にNavMeshをベイクする方法
今回は動的にNavMeshをベイクするNavMeshSurfaceについてです。
動的にNavMeshをベイクする
UnityのNavMeshはエディターのみで実現可能な機能でしたが、Unity 5.6からLow Level APIのような物が公開され、NavMeshを動的にベイクすることが可能になりました。
正確には、動的なベイク、及びベイクしたデータをアセットとして追加ロードですね。
動的にNavMeshをベイクすることが可能になった事で、ステージのちょっとした変更(例えば橋がかかる、部屋が移動する等)でもNavMeshを使用した経路探索が使用できるようになった訳です(単純にせき止めるだけならNavmeshObstacleでも可能でしたが)
で、今回は高レベルのコンポーネントのNavMeshComponentsの一つ、NavMeshSurfaceを使用した動的なNavMeshのベイクをやってみます。
高レベルのコンポーネントを入手する
高レベルのコンポーネントはGithubにて公開されています。
このコンポーネントはNavMeshComponentsから入手できる他、NavMeshウィンドウのBakeにURLがあるので、そちらからも入手できます。
入手出来るというか、Githubのページにジャンプするので、そこからCloneもしくはDownloadする感じです。
ダウンロードしたコンポーネントの内、必要な項目はGizmosとNavMeshComponentsです。二つともプロジェクト内にインポートします。
NavMeshComponentsは兎も角GizmosはAssets直下に配置する必要があります。
NavMeshSurfaceを利用した動的なNavMeshのベイク
NavMeshSurfaceを利用した動的なNavMeshのベイクをやってみます。
この手順を利用する場合、殆どNavMeshウィンドウのBakeとObjectは使用しなくなります。このあたりの設定はコンポーネント側で行うためです。
AgentsとAreasは相変わらず使用します。
まずは使ってみる
実際の使用手順です。といっても難しい事はありません。
最小限の方法は、
- NavMeshSurfaceコンポーネントのセットされているオブジェクトをシーン内に配置
- NavMeshAgentがパス計算を始める前にNavMeshSurface.Bakeを呼び出す
これだけです。
NavMesh Surfaceのセット
まずNavMeshSurfaceのセット。
NavMesh SurfaceのNavMesh生成に使用するジオメトリはRendererとColliderの二種類なので、どちらかを選択します。
NavMeshSurface.Bakeの呼出
あとはNavMeshSurface.Bakeを呼び出すだけです。
NavMeshAgent.SetDestinationを呼び出す他のコンポーネントより先に呼び出して欲しいので、DefaultExecutionOrderで実行順番を-102とか非常に前列に配置しています。
DefaultExecutionOrderについてはコチラ。
応用
基本的には上の方法で利用可能ですが、それでは不十分なケースも多々あります。
例えばベイクの範囲調整とか、エッジ調整とか、その辺り。
ベイクの範囲を調整
ベイクの範囲を制限したいケースがあります。特にベイクにかかる時間を減らしたいようなケースです。広大な範囲をベイクするのは時間がかかるので、その辺りをコンパクトに抑えたいという話です。計算を実行時に行うのでこの辺りは特に。
で、ベイクの範囲ですが、二つの方法があります。
- ベイク対象の範囲を限定する
- ベイク対象をNavMeshSurfaceの子に限定する
ベイクの対象範囲を制限する方法では、NavMeshSurfaceを中心としてベイクの範囲を枠で囲った範囲に制限します。
ベイクの範囲を限定出来るのでベイク速度は向上しますが、枠の範囲外にいるエージェントは動作しなくなるかもしれません。(もしくは範囲内に強制移動)
このアプローチを見た後は、複雑な地形を複数のNavMeshSurfaceで簡略化しようと思うかもしれません。その場合、NavMeshは他のNavMeshに渡れない点に注意する必要があります。要するにNavMesh間移動にNavMeshLinkが必要です。
3つ目の方法は、NavMeshSurfaceオブジェクトの子に配置したメッシュに対してベイクする…というものです。
もしステージが親子構造になっている場合、親にNavMeshSurfaceを配置するだけで良いので簡単です。
ベイク対象から外す
さて、NavMeshSurface側でベイクを行う訳ですが、例外的にコレを外したい…といった場合があります。
具体的には範囲内にColliderかRendererを持つユニットが存在する場合、プレイヤーキャラの周辺にNavMeshがベイクされず穴が開く…みたいな事が起こります。
この対策として四つのアイディアがあります。
- レイヤーで分割する
- NavMeshModifier.IgnoreFromBuildをセットする
- NavMeshSurfaceのCollect ObjectsをChildrenにして子から外す
- 動的にオブジェクトを追加する / ビルド時には非アクティブにする
レイヤーはベイクの対象となるレイヤーを制限するだけです。
このアプローチでは、広い範囲で細かいレイヤー制御が期待出来ます。
ただしUnityのレイヤー数は何故か未だに31(しかもレンダリングとコライダーの判定が独立していない!)ので、この項目にレイヤーを使うのは出来れば避けたい所です。
開発後半でレイヤーが足りなくなると悲惨です。
NavMeshModifierは、ビルド時にNavMeshの設定(Not WalkableやJump等)を加えることのできるコンポーネントです。
ここでIgnore From Buildオプションを追加すると、ビルド対象から外してくれます。
あとは単純にNavMeshSurfaceが子を収集してる場合は子から外してしまえばビルド対象外になりますし、ビルドする際にオブジェクトが無ければビルドに含まれません。
エッジの調整
NavMeshのエッジの調整です。
エッジ(Radius/Voxel Size)は大きければ大きいほど大雑把にNavMeshが構築されます。この設定が大きいと細い道が潰れて通れなくなったりします。
逆に小さければ小さいほど、経路探索やベイクに時間がかかります(範囲によってはPCですら数十秒!)
通常のNavMeshではこの項目はNavMeshウィンドウのBakeでやってきたのですが、NavMeshSurfaceはコンポーネント側に移動しています。
設定する方法は二通り。
- Agent Typeの設定
- AdvanceのVoxel Sizeの設定
Agent TypeはNavMeshのエージェントのタイプを指定します。
この項目はNavigationウィンドウで設定を作成し、各NavMeshAgent毎に設定する項目です。
エージェントの幅や高さ、登れるステップの高さや登れる坂の角度等を指定します。要するに今までBakeで設定していたような項目です。
後はAgents Typeを作ってNavMeshSurfaceでその項目を指定すれば良いです。大きいNavMeshAgentには粗い(細かい所を移動出来ないNavMesh)が、小さいNavMeshAgentには細かい(細かい所が移動可能なNavMesh)が生成されます。
NavMeshSurfaceは一つのNavMeshにつき一つのAgentTypeを指定できて、かつ範囲が重複しても問題無いので、トム&ジェリーのように、大きいエージェントだけが通れない道…みたいな事も出来ます。
なお異なるAgentTypeのNavMeshは使えない点に注意。
#Unity 5.6のNavmeshで、「小さいユニティちゃんは小さい穴を通れる。大きなユニティちゃんは小さい穴が通れないので大きな穴を通る」をする。イメージは猫がネズミを追っかけるアニメ pic.twitter.com/zSvXNsE9eD
— 椿 (@tsubaki_t1) 2017年1月4日
AdvanceのVoxel Sizeは、Agent Typeの設定でさらに微調整したいような場合に使います。
なおややっこしいですが、NavMeshAgentについているRadiusはNavMeshAgent同士やNavMesh Obstacle用の判定です。こちらは再パス計算よりも高速ですが、再パス計算しないので通れなくなっても再パス計算を行いません。歩きスマホしてる人と同じような動きをします。
関連
キャラクターの周辺のみNavMeshを作る(動的に)みたいな場合は、NavMeshBuilderを使用したコチラの方法が良いです。
Unite 2017のスライドです。
その他NavMeshのTips