e.blog

主にUnity/UE周りのことについてまとめていきます

NavMeshAgentの挙動を手動でアップデートする

概要

NavMeshを使うことで、Unityでは簡単に目的までのパス(経路)を計算することができます。

docs.unity3d.com

経路探索して移動させたいオブジェクトに「NavMeshAgent」をアタッチし、「Window > Navigation」メニューで表示されるナビゲーション設定にて、経路構築を行うことですぐに「目的地への移動」を実現させることができます。

しかし、例えばアニメーションと組み合わせたり、なにかしらの自身のロジックと組み合わせて利用したいというケースもあると思います。
(例えばAIを実装して、AIに移動を行ってもらいたいが位置や回転のアップデートは自分のタイミングで行いたい場合など)

そういう場合には勝手に移動されてしまうと問題です。
そこで、手動で位置と回転をアップデートする方法を書いておこうと思います。
(ちなみにドキュメントにも記載があるので、そちらも合わせて見てみるといいかもしれません)

docs.unity3d.com

準備

さて、基本的にはNavMeshAgentをアタッチし、目的地を設定した段階で自動的に位置の移動が開始されます。
そのため、いくつかのスクリプトを記述して自動更新を止める必要があります。

自動更新を止めるスクリプトを書く

具体的には以下のようにプロパティを設定します。

void Awake ()
{
    _agent = GetComponent<NavMeshAgent>();

    // updateを自動で行わないように設定する
    _agent.updatePosition = false;
    _agent.updateRotation = false;

    // 目的地はインスペクタなどで事前に設定しておく
    _agent.SetDestination(_target.position);
}

これで、NavMeshAgentによるゲームオブジェクトの自動更新が停止します。
(ただし、見た目の更新が停止しているだけで、内部的な経路の探索と位置は常にアップデートされていきます)

更新を手動反映させる

自動反映をとめたので、今のままだとゲームオブジェクトはまったく動かなくなります。
今回の目的はこれを手動で更新する方法です。

位置に関してはnextPositionに、アップデート間隔で計算された次の位置が保持されているので、これをそのまま設定してやれば位置の更新ができます。
しかし回転についてはいくつか自分で計算しないとならないのでそれを行います。

最終的なコードは以下のようになります。
計算自体は軸と角度を求めるだけです。

void Update ()
{
    // 次の位置への方向を求める
    var dir = _agent.nextPosition - transform.position;

    // 方向と現在の前方との角度を計算(スムーズに回転するように係数を掛ける)
    float smooth = Mathf.Min(1.0f, Time.deltaTime / 0.15f);
    var angle = Mathf.Acos(Vector3.Dot(transform.forward, dir.normalized)) * Mathf.Rad2Deg * smooth;

    // 回転軸を計算
    var axis = Vector3.Cross(transform.forward, dir);

    // 回転の更新
    var rot = Quaternion.AngleAxis(angle, axis);
    transform.forward = rot * transform.forward;

    // 位置の更新
    transform.position = _agent.nextPosition;
}