おもちゃラボ

Unityで遊びを作ってます

【Unity】NavMesh 超入門

NavMeshを使ってキャラクタを動かす方法を紹介します。
NavMeshを使えば、簡単に移動できる範囲を指定でき、
指定した経路にそって自動的にキャラクタを動かす
ことが出来ます。

f:id:nn_hokuson:20211116134540p:plain

目次は次のとおりです

まずはキャラクタが移動できるエリアを指定します。経路に指定したいオブジェクトを選択し、インスペクタから「Navigation Static」を選択します。

f:id:nn_hokuson:20160805230448p:plain

メニューバーから「Window」→「Navigation」を選択します。
インスペクタの部分にNavigationタブが開くので、右下の「Bake」ボタンを押して下さい。
ボタンを押すと画面上に経路が水色で表示されます。

f:id:nn_hokuson:20160805231420p:plain

キャラクタにNavMeshAgentをアタッチする

次に経路にそって歩かせるキャラクタ(ここではSDユニティちゃん)を配置します。

ユニティちゃんが配置できたら、経路に沿って動かすためにNavMesh Agentコンポーネントをアタッチします。
ヒエラルキービューからユニティちゃんのオブジェクトを選択し、インスペクタから「Add Component」→「Navigation」→「Nav Mesh Agent」を選択します。

f:id:nn_hokuson:20160805233129p:plain

ゴール地点を指定する

最後にスクリプトからゴール地点を指定します。
プロジェクトビューで「Create」→「C# Script」を選択し、「CharacterController」にリネームします。
次のスクリプトを入力して下さい。

using UnityEngine;
using UnityEngine.AI;
using System.Collections;

public class CharacterController : MonoBehaviour {
    public Transform target;
    NavMeshAgent agent;

    void Start () {
        agent = GetComponent<NavMeshAgent>();
    }

    void Update () {
        agent.SetDestination(target.position);
    }
}

スクリプトを保存したら、ヒエラルキービューのユニティちゃんオブジェクトにドラッグ&ドロップしてアタッチします。

最後に、ゴール地点となるオブジェクトを指定します。先ほどアタッチしたUnityChanControllerスクリプトのTargetのスロットにゴール地点になるオブジェクトをドラッグ&ドロップします。

f:id:nn_hokuson:20160805234149p:plain

実行するとユニティちゃんが自動的にゴール地点まで移動します。

f:id:nn_hokuson:20160805234416g:plain

Off Mesh Linkで飛び地の間を移動する

作成したステージが階段状になったり、飛び地になる場合があります。
このような飛び地で普通にNavMesh使うと、ゴールまでのルートが検出されずに
途中で止まってしまいます
(下の例だと、下段に降りられない)

f:id:nn_hokuson:20211116135055p:plain:w550

そこで、こういう場合はOff Mesh Linkという機能を利用します。
Off Mesh Linkは飛び地になったNavMeshの経路を繋げる仕組みです。
Off Mesh Linkの使い方は、NavMeshを繋ぎたい両端にオブジェクト(ここではシリンダー)を配置します。

f:id:nn_hokuson:20211116140931j:plain:w600

シリンダーのひとつを選択して、インスペクタからAdd Component→Navigation→Off Mesh Linkコンポーネントをアタッチします。このOff Mesh LinkコンポーネントのStart欄とEnd欄にそれぞれシリンダーをドラッグ&ドロップします。

f:id:nn_hokuson:20211116140941j:plain

これで2つのNavMeshが接続できました。実行してプレイヤがゴールまでたどり着くことを確認してください。

f:id:nn_hokuson:20211116142357g:plain

Off Mesh Link上の動きを改善する

このままだとOff Mesh Linkの上を通るときに、キャラクターが加速する&直線的な動きになってしまい、少し不自然です。そこで、スクリプトを修正してジャンプするように修正しましょう。

先ほど作成したスクリプトを次のように書き換えてください。

using UnityEngine;
using UnityEngine.AI;
using System.Collections;

public class CharacterController : MonoBehaviour
{
    public Transform target;
    NavMeshAgent agent;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        agent.SetDestination(target.position);

        StartCoroutine(Jump(agent));
    }

    IEnumerator Jump(NavMeshAgent agent)
    {
        agent.autoTraverseOffMeshLink = false;

        while (true)
        {
            // Off Mesh Linkに乗るまで待機
            yield return new WaitWhile(() => agent.isOnOffMeshLink == false);

            // NavMeshによる移動を停止
            agent.Stop();

            // 自前のジャンプ処理
            float jumpGravity = -25f;
            Vector3 src = transform.position;
            Vector3 dst = agent.currentOffMeshLinkData.endPos;
            Vector3 v = dst - src;
            float jumpTime = 0.7f;
            float v0 = (v.y - jumpGravity * 0.5f * jumpTime * jumpTime)/jumpTime;

            for (float t = 0; t < jumpTime; t += Time.deltaTime )
            {
                Vector3 p = Vector3.Lerp(src, dst, t/ jumpTime);
                p.y = src.y + v0 * t + 0.5f * jumpGravity * t * t;
                transform.position = p;
                yield return null;
            }

            // NavMeshによる移動を再開
            transform.position = dst;
            agent.CompleteOffMeshLink();
            agent.Resume();
        }
    }
}


このスクリプトではプレイヤーがOff Mesh Linkに乗るまで待機します。Off Mesh Linkに乗ったら一旦NavMeshを使った移動を停止して、自前でジャンプの動作を行います。

ジャンプ動作はジャンプ開始地点と着地点が分かっているので、

y = 初速度 * 時間 + 0.5 * 重力加速度 * 時間^2

として計算しています。この時、ジャンプ終了時点で「シリンダーの高低差」ぶん移動していることを考えて、初速度v0は上記の式にこれを代入し

v0 = (高低差 - 0.5 * 重力加速度 * 滞空時間^2) /  滞空時間

として計算します。

ジャンプが終了したら、再びNavMeshによる移動を有効にします。これで実行すると、次のようにNavMesh間の移動が自然に表現できます。
f:id:nn_hokuson:20211116143940g:plain

まとめ

NavMeshを使ってキャラクタを動かす方法を紹介しました。
また、Off Mesh Linkを使うと飛び地になったNavMeshを
つなげて考えることが出来ることを説明しました!

「Unityの教科書」を書きました。よろしくお願いします!(^^)/