【Unity】ナビメッシュの目的地を道に沿ってランダムに決定する

投稿者: | 2020-06-18

ナビメッシュの次の目的地を進行方向へナビメッシュの道に沿ってランダムに決定してみます。前の記事の方法でナビメッシュエージェントの進行方向へ大まかに目的地を設定することはできますが、ナビメッシュの無い部分をまたいで目的地を設定したときに、道によってはエージェントが急に進行方向を変えたりしてしまいます。

道に沿って自然に進行方向へ移動させたいというような時のためにナビメッシュレイキャストを使って目的地を決定する方法を考えてみました。

NavMesh.Raycastでは、レイがナビメッシュのある場所とない場所の境界にぶつかると、その場所の位置や法線、距離などの情報を得ることができます。

Physics.Raycastとは違ってナビメッシュ上でレイを飛ばすので、ナビメッシュが斜めになっていても法線のY座標は0のようです。

このNavMesh.Raycastを使って、レイをバウンドさせるように飛ばすことで、道に沿って進行方向へ目的地を設定できると思いました。

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

public class NavMeshRaycastTest : MonoBehaviour
{
    [SerializeField] GameObject sphere;
    [SerializeField] Material[] materials;
    [SerializeField] [Range(0f, 1f)] float t = 0.5f;

    MeshRenderer meshRenderer;
    Vector3 sourcePos;
    Vector3 targetDir;
    int n = 0;

    // Start is called before the first frame update
    void Start()
    {
        targetDir = Vector3.Slerp(transform.forward, transform.right, 0.5f);
        sourcePos = transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.E))
        {                            
            NavMeshHit hit;

            // レイキャスト
            if (NavMesh.Raycast(sourcePos, targetDir * 30f + sourcePos, out hit, NavMesh.AllAreas))
            {
                Debug.Log(hit.normal);

                sourcePos = hit.position; // 衝突した場所を次の始点にする
               
                GameObject s = Instantiate(sphere, sourcePos, sphere.transform.rotation); // 球を作る
                meshRenderer = s.GetComponent<MeshRenderer>();
                meshRenderer.material = materials[n]; // 色を変える

                Vector3 tan = Vector3.Cross(hit.normal, Vector3.up); // 接線

                // 接線がレイの来た方を向いていたら
                if (Vector3.Dot(targetDir, tan) < 0)
                {
                    tan *= -1; // 接線の向きを逆にする
                }

                // 次のレイを法線と接線の間の方向にする
                targetDir = Vector3.Slerp(hit.normal,tan, Random.Range(0f, 1f)); // t       

            }
            // どこにもぶつからなければその場所を次の始点にする
            else {

                sourcePos = targetDir * 30f + sourcePos;

                // 球を置く
                GameObject s = Instantiate(sphere, sourcePos, sphere.transform.rotation);
                meshRenderer = s.GetComponent<MeshRenderer>();
                meshRenderer.material = materials[n];

            }

            n++;
            if (n > 5) n = 0;
        }     
        
    }
}

まず進行方向へ適当にレイを飛ばし、その地点の法線のベクトルを得ます。法線から、Vector3.Crossを使って接線の方向を得ます。そして、Vector3.Slerpで法線と接線の間の方向をランダムで選んで、次のレイを飛ばす方向にします。

このとき、接線が法線を境にして、今のレイが来た方向と同じ側を向いていると、次のレイを進行方向へ飛ばせないので、接線と今のレイの方向の内積から判断して、その時は接線の向きを逆にしています。

レイが当たらない場合は、レイの目標地点がまだナビメッシュ上の、境界でないところにあるので、そこからさらに同じ方向へレイを飛ばします。これで、道に沿って進行方向へ目的地を決定することができると思います。

Vector3.Slerpの第3引数をランダムにしないと、レイが同じエリアを行ったり来たりすることが多いです。

0.84
0.25

ランダムにするとレイがエリア全体に広がります。

しかしこのままだとナビメッシュエージェントが道に沿ってジグザグに移動しそうなので、レイが到達した2地点の中間の点を目的地に設定するなど工夫がいるかもしれません。

コメントを残す

メールアドレスが公開されることはありません。