【Unity】ナビゲーションシステム入門 - 最短経路を探索して進む知的なAIを簡単に作る

Unityのナビゲーションシステムで最短経路を探索して進む知的なAIを簡単に作る方法をまとめました。

Unity2019.2.5

はじめに

Unityのナビゲーションシステムを使うと、地形から自動的に目的地までの最短距離を算出し、
障害物を避けながら目的地まで進む知的なキャラクター(エージェントと呼びます)を簡単に作成できます。

この記事ではナビゲーションシステムの基本的な使い方を紹介します。

前提として、検証用に以下のようにPlaneメッシュを組み合わせて地形を作っておきます。

f:id:halya_11:20191018155602p:plain

ナビメッシュを生成する

ナビゲーションシステムを使うには、まずナビメッシュ(NavMesh)と呼ばれる経路探索用のデータを生成する必要があります。
NavMeshを生成するにはまず、地面となるGameObjectをNavigation Staticとしてマークします。

f:id:halya_11:20191018155749p:plain

次にWindow > AI > NavigationからNavigationウィンドウを開きます。
ウィンドウが開いたらBakeタブを選択します。

f:id:halya_11:20191018155911p:plain

次にこのウィンドウで基本的な設定をしていきます。
とりあえず以下の4つを理解すれば基本的なNavMeshが作れます。

項目名 説明
Agent Radius エージェントの半径
設定するとエッジの余白の幅が変わる
Agent Height エージェントの背の高さ
天井のある障害物を潜り抜けられるかどうかの判定に使われる
Max Slope エージェントが登れる勾配の最大値
Step Height エージェントがどれだけ高い段差を登れるか

適当に設定したらBakeボタンを押下してNavMeshを作ります。
Sceneビューを見ると正常に生成されていることが確認できます。

f:id:halya_11:20191018160752p:plain

青い部分がナビメッシュです。
凸多角形のポリゴンで構成されていることが確認できました。

Bake時の詳細設定項目

さてNavigationウィンドウのBakeタブの下部にはさらに詳細な設定項目があります。

f:id:halya_11:20191018161531p:plain

まずManual Voxel Sizeですが、これはNavMeshが粗くて地形のメッシュの形状と全然異なってしまうときに使います。
ボクセルサイズを小さくするほど精度が上がりますが、ベイク時間も比例して増加します。
この設定項目は通常は変えることはないと思います。

次にMin Region Areaです。
いま、次のように小さい独立したNavMesh領域が作られていたとします。

f:id:halya_11:20191018162534p:plain

Min Region Areaはこのような独立したNavMeshの最小の大きさを定義する値です。
この値を大きくすると、小さいNavMeshしか作れない領域は無視されます。

f:id:halya_11:20191018162613p:plain

また、低めの階段などは斜めのNavMeshとして生成されることが多いです。
これらをしっかり階段を登っているように扱いたい場合にはHeight Meshにチェックを入れます。
高さ用のメッシュ情報が別途生成されます。

エージェントを動かす

次に作成したNavMesh上にエージェントを配置して動かしてみます。
まず適当にCylinderを作成してNavMeshAgentコンポーネントをアタッチします。

f:id:halya_11:20191018165123p:plain

このNavMeshAgentのvelocityに値を代入するとエージェントを動かすことができます。

using UnityEngine;
using UnityEngine.AI;

public class Example : MonoBehaviour
{
    [SerializeField]
    private NavMeshAgent _agent;

    private void Update()
    {
        var velocity = Vector3.zero;
        if (Input.GetKey(KeyCode.LeftArrow)) {
            velocity.x += 5;
        }
        if (Input.GetKey(KeyCode.UpArrow)) {
            velocity.z += 5;
        }
        if (Input.GetKey(KeyCode.RightArrow)) {
            velocity.x -= 5;
        }
        if (Input.GetKey(KeyCode.DownArrow)) {
            velocity.z -= 5;
        }
        _agent.velocity = velocity;

        // Moveで相対位置を指定してもok
        //_agent.Move();
    }
}

f:id:halya_11:20191018165654g:plain

また、NavMeshAgent.destinationに座標を入れるとそこまで最短距離で自動的に移動してくれます。

using UnityEngine;
using UnityEngine.AI;

public class Example : MonoBehaviour
{
    [SerializeField]
    private NavMeshAgent _agent;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.R)) {
            _agent.destination = Vector3.zero;
        }
    }
}

動的に動く障害物を設定する

次に動的に動く障害物を設定してみます。
まず適当にCubeを生成し、NavMeshObstacleコンポーネントをアタッチします。

f:id:halya_11:20191018171050p:plain

これで再生すると、障害物の部分は通れなくなっていることが確認できます。

f:id:halya_11:20191018171333g:plain

ここで、上図から見て取れるようにこの障害物はナビメッシュ自体には変化を与えていません。
つまり、経路探索の際にはこの障害物の部分も経路として計算されてしまいます。
NavMeshを障害物によって削るにはNavMeshObstacleのCarveにチェックを入れます。

f:id:halya_11:20191018171905p:plain

するとこの障害物の形にNavMeshが削り取られます。

f:id:halya_11:20191018171818p:plain

ただし、NavMeshの形が変わるということはA*によるグローバルな経路探索もやり直しになるということを意味します。
これは比較的重い処理であり、頻繁に行われるとパフォーマンスに影響してしまいます。
そのためCarveは基本的にはOFFにして、必要なケースだけONにする必要があります。

これに関してはナビゲーションシステムの仕組みから理解する必要があるため、以下の記事にまとめました。

light11.hatenadiary.com

Off-mesh Linkで非連続なNavMesh間を移動する

今このように非連続なNavMeshが生成されているとします。

f:id:halya_11:20191018194444p:plain

このような場合にはオフメッシュリンクを使うと、離れたNavMesh間が経路として繋げられます。
使い方としては、適当なGameObjectにOffMeshLinkコンポーネントをアタッチして、結びたい二点をStartとEndにアサインします。

f:id:halya_11:20191018194631p:plain

NavMeshを表示した状態で二つのオフメッシュリンクの周りに円が表示されていて、かつ線でつながれていればOKです。

f:id:halya_11:20191018194659p:plain

この状態で、離れたNavMesh上の位置にdestinationを設定して再生します。

f:id:halya_11:20191018195037g:plain

オフメッシュリンクにより離れたNavMeshがつながれていることが確認できました。

Off-Mesh Linkを自動的に作る

段差から飛び降りるためのオフセットリンクと、ジャンプして溝を飛び越えるためのオフセットリンクはベイク時に自動生成することもできます。
これらを自動生成するには、まず対象のGameObjectを選択した状態でNavigationウィンドウのObjectからGenerate OffMeshLinksにチェックを入れます。

f:id:halya_11:20191018200455p:plain

次にBakeタブのGenerated Off Mesh Linksに、飛び降りることが可能な高さとジャンプ可能な距離を入力します。

f:id:halya_11:20191018200553p:plain

設定が終わったらあとはベイクすれば、オフメッシュリンクが自動的に生成されます。

f:id:halya_11:20191018200647p:plain

参考

docs.unity3d.com