【Unity】視界に入ったらプレイヤーの方を向く

投稿者: | 2020-04-25


敵の猿の視界の範囲にFPSキャラクターが入ると、猿がプレイヤーの方を向くようにしました。

まず敵の視線のベクトルと、敵からみたプレイヤーの方向のベクトルの角度θのコサインを求めます。
コサインθを求めるために内積を計算します。

float dot = Vector3.Dot(transform.forward, (player.position - transform.position).normalized);

内積がわかるとコサインθがわかります。

分母はそれぞれのベクトルの長さを掛け算したものですが、transform.forwardは単位ベクトルで、プレイヤーへのベクトルは「.normalized」をつけて長さを1にしているので、分母は1です。なので、コサインθは内積の値と同じです。

コサインθの値は、中心が(0, 0)で半径が1の円上の点と中心を結ぶ線分(長さ=半径1)と、x軸とのなす角をθとすると、その点のx座標の値と同じです。

なので、θが0のとき1で、θが大きくなるほどCosθの値は小さくなり、90度のとき0、180度のとき-1になります。
180度をすぎると、角度が大きくなるにつれて、1に戻っていきます。なので、0 <= θ <= 180度 と考えて、240度 を 120度に変えてもコサインの値は同じです。

プレイヤーを定点カメラにして、内積を表示します。


猿の視界にカメラが入るとき、内積は1に近い値になっているのがわかります。

冒頭のGif画像では、猿にスクリプトをつけて、猿がプレイヤーを発見していないときは内積を計算して、プレイヤーが視界の範囲にいるかを判断します。プレイヤーが視界に入って猿がプレイヤーに気付いているときは、猿からプレイヤーにレイを飛ばし、猿とプレイヤーの間に障害物が無い限り、猿がプレイヤーの方を向き続けます。
障害物があって、レイがプレイヤーに当たらなくなると、猿は正面に向き直りながらまた内積の計算を始めます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class DotTestScript : MonoBehaviour
{
    [SerializeField] Transform player;
    [SerializeField] Text text;

    bool frag = false;
    bool frag2 = false;
    Vector3 pos;
    Vector3 defDir;

    // Start is called before the first frame update
    void Start()
    {
        defDir = transform.forward;
    }

    // Update is called once per frame
    void Update()
    {

        // 猿がプレイヤーに気付いている
        if (frag)
        {
            RaycastHit hit;

            // レイをプレイヤーに向かって飛ばす
            if (Physics.Raycast(transform.position, player.position - transform.position, out hit, Mathf.Infinity))
            {
                // プレイヤーと猿の間に障害物が無いとき
                if (hit.collider.tag == "Player" || hit.collider.tag == "MainCamera")
                {
                    // 猿がプレイヤーの方を向く
                    transform.LookAt(Vector3.Lerp(transform.forward + transform.position, player.position, 0.05f), Vector3.up);
                }
                // 障害物があるとき
                else
                {
                    // 気付いていない状態に遷移
                    frag = false;
                    frag2 = true;
                }               
            }
        }
        // 猿がプレイヤーに気付いていない
        else
        {
            float dot = Vector3.Dot(transform.forward, (player.position - transform.position).normalized);

            text.text = dot.ToString();

            // プレイヤーが猿の視界に入ったとき
            if (dot > 0.7f)
            {
                // 気付いている状態に遷移
                frag = true;

                text.text = "";
            }

            // 猿に正面を向かせる
            if (frag2)
            {
                float dot2 = Vector3.Dot(transform.forward, defDir);

                // 猿が正面を向いていないとき
                if (dot2 < 0.95f)
                {
                    // 正面になめらかに回転
                    transform.LookAt(Vector3.Lerp(transform.forward + transform.position, defDir + transform.position, 0.08f), Vector3.up);
                }
                // ほぼ正面を向いたとき
                else {
                    // 正面を向かせるのをやめる
                    frag2 = false;
                }               
            }          
        }      
    }
}

猿の顔は前の記事の方法でなめらかに回転させています。

コメントを残す

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