kedzkiestの日記

普段の発見や開発の進捗について書いていきます。

NavMeshObstacleを使って部分的にNavMeshをリベイクしたような効果を得る

はじめに

Unityを使って制作していたゲームでNavMesh上の障害物がシーンから消えるたびにNavMeshをリベイクしていたのですが、そのたびに更新が必要ないところまで再計算されてしまい、計算負荷がかかって画面がカクついていました。そこでNavMeshObstacleを導入することで実行速度が改善されたため、備忘録として記事を書きました。

環境

Unity 2021.3.7f1

ゲームのコンセプト

一人称視点のゲームです。平面上にグリッド状の迷路がランダムに生成され、迷路のどこかにプレイヤー、敵、ゴールが生成されます。

迷路の大きさを7*7で設定すると、あるときはこんな初期配置。

敵は迷路内を巡回しており、プレイヤーを見つけると追いかけてきます。プレイヤーの目的は敵に捕まらないようにゴールを目指すことです。

また、プレイヤーの特殊能力として、一定時間ごとにパワーがチャージされ、外周を除く迷路内の好きな壁を壊すことができます。壁を壊した後のスペースは通行可能です。

敵が迷路内を巡回するしくみ

UnityではNavMeshAgentコンポーネントをゲームオブジェクトにアタッチすることで、NavMesh上を動くエージェント(AI)が作れます。同じことを今回の敵にも行っています。エージェントはNavMeshがないと動けないため、迷路が生成されたタイミングで一度NavMeshをベイクしています。

迷路を上から見ている状態。青い部分がNavMeshで、エージェントが動ける領域。

どこで問題が起こったか

前述したようにプレイヤーが迷路内の壁を破壊できる仕様のため、壁を破壊した後は敵エージェントが動ける領域もそれに合わせて変化する必要があります。しかしNavMeshのベイクは迷路の生成が終わった後一度しか行っていないため、破壊前の壁があった場所にはNavMeshが張られていません。

壁を破壊してもエージェントにとっては同じ地形。

そこでNavMeshSurfaceを導入しました。NavMeshSurface.BuildNavMeshメソッドによりゲーム実行中にNavMeshのベイクができます。これを使い、壁を破壊した後すぐにNavMeshをベイクすれば敵が新しい地形を認識できると考えました。
そこで実行してみると・・
youtu.be
壁を破壊する瞬間だけ画面がカクつくのが分かると思います。これは、BuildNavMeshメソッドによってシーン内すべてのNavMeshが再計算されることで計算負荷がかかるからだと思われます。動的にNavMeshを更新できるようにしたはいいものの、これでは快適にプレイできません。

どのように解決したか

迷路の壁となるゲームオブジェクトにNavMeshObstacleコンポーネントをアタッチし、Carveのオプションをオンにしました。
実行結果をご覧ください。
youtu.be
NavMeshObstacleを使うことで、ゲーム中にNavMeshの動的な変更が効率よく行えました。

おわりに

NavMeshObstacleを使うのはこれが初めてではなく、以前にゲームAIを学んでいたときに学習記録も残しています。
youtu.be
そのせいか、NavMeshObstacleは動いている障害物に使うものだという先入観を持っていましたが、今回の一件があったことで今後、より柔軟な使い方を検討できそうです。