敵の猿の視界の範囲に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;
}
}
}
}
}
猿の顔は前の記事の方法でなめらかに回転させています。