今回はFPSでの敵キャラの設定などで使えそうな、扇形の当たり判定でリアルな人間の視界を作る実装について記事を書いていきます!
使用PC: MacBook Pro (13-inch, M1, 2020)
OSバージョン: 11.5.2
Unityバージョン:2020.3.30f
完成形
動画は若干わかりにくいですが、視界の一定範囲にターゲットが入った場合にDebug.Logを出力するような機能になっています。視界範囲内に障害物があった場合は判定されません。
概要
グレーのような扇形の部分を視界の範囲とし、ターゲットとなるGameObjectがそこに入ったら当たり判定を判別する仕組みを作ろうと思います!
構成内容は
①視界の範囲内にいるかどうかの判定
②視界の角度内にいるかどうかの判定
③視界の範囲内にターゲットが存在するかどうかの判定
の3つの構成です!
ちょこっと詳しく説明
①視界の範囲内にいるかどうかの判定
探す人の直線距離内、つまりどこまで遠くまで見れるかどうかを判別します。
この中に入らないとターゲットは見つけられないということになります。
⏬この画像では黄色の範囲内ですね!
②視界の角度内にいるかどうかの判定
探す人の視界の角度を扇形で指定することで、見える範囲に存在するかどうかの判定を行います。
Rotationが変われば、それに応じて判定される扇も回転します。
③視界の範囲内にターゲットが存在するかどうかの判定
途中で障害物などがあったときに、視界やその角度の範囲内にいたとしても直線上に他のオブジェクトで遮られていた場合は判定することができません。
下準備
オブジェクトの配置
Player, Ground, Wall, Targetの4つのオブジェクトを作成します。
今回はPlayerをCyllinder
、GoundをPlane
、WallをCube
、TargetをSphere
で作りました。
わかりやすいようにMaterialで色もつけました。
Materialは
Projectビュー
→Create
→Material
で作成します。
コライダーの設定
①Player, Wall, TargetにはRigidbody
をAdd Componentしてください。
②PlayerにSphereCollider
をつけて、Radiusなどをいじっていい感じに大きさや位置を調整します。
この後このSphereColliderが自分の視界の範囲内にいるかどうかを判定するエリアになります。
③Sphere Colliderのis Trigger
にチェックを入れます。
Targetにタグ付けを行う
①TargetのオブジェクトにInspectorビューからAdd Tag
を選択します。
③もう一度TargetオブジェクトのInspectorビューに戻り、さっき加えたtargetタグを付与します。
スクリプトを作成
①Projectビュー
→Create
→C# Script
でスクリプトを作成します。
コードを書く
👇コードの全文はこちら
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FunSearch : MonoBehaviour
{
public float angle = 45f;
private void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "target") //視界の範囲内の当たり判定
{
//視界の角度内に収まっているか
Vector3 posDelta = other.transform.position - this.transform.position;
float target_angle = Vector3.Angle(this.transform.forward, posDelta);
if (target_angle < angle) //target_angleがangleに収まっているかどうか
{
if(Physics.Raycast(this.transform.position, posDelta, out RaycastHit hit)) //Rayを使用してtargetに当たっているか判別
{
if (hit.collider==other)
{
Debug.Log("range of view");
}
}
}
}
}
}
コードの解説
概要の部分の①、②、③の順番で条件のコードを記述していきます。
変数宣言
public float angle = 45f;
視界の範囲を45度として、変数として宣言しておきます。
視界の角度内に収まっているかどうかの判定については後述のコードの中で行います。
視界の範囲を設定
private void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "target")
先ほどSphere Colliderのis Triggerにチェックを入れているので、Triggerモードで当たり判定を取れているものを教えてくれる関数を書きます。
今回はタグを利用するので、引数にあるother、つまり「当たった側の情報であるtagがtarget
だった場合」という条件をif文で記述します。
角度の範囲内か確認
Vector3 posDelta = other.transform.position - this.transform.position;
float target_angle = Vector3.Angle(this.transform.forward, posDelta);
if (target_angle < angle)
{
Playerの正面に対して、Targetの位置を取得してそれが角度が45度以内かどうか算出します。
まずは位置のベクトルを引き算したものを表します。これを出すことでTargetに対するベクトルを出すことができます。
Vector3 posDelta = other.transform.position - this.transform.position;
相手側のポジションと自分のポジションの差分をposDelta
とします。
中学数学のベクトルの考え方と同じですね!
float target_angle = Vector3.Angle(this.transform.forward, posDelta);
ここでPlayerの正面とposDeltaのなす角度をここで測ります。
Vector3.Angle()
は角度取得を簡単にできるメソッドです。自分の正面のベクトルをとるthis.transform.forward
に対して、第2引数にposDelta
を入れることで正面に対して何度の角度かを得ることができます!
if (target_angle < angle)
if文の中に「算出したtarget_angle
が変数宣言で指定しているangle
に収まっているかどうか」という条件文を書きます。
障害物を判定する
PlayerからTargetに対してRay
を飛ばし、その間に「別のものが当たり判定で取られないかどうか」確認する動作を行なっていきます。
Ray とは、直訳すると「光線」という意味です。
シーン上の指定した地点から光線を飛ばし、光線上にあるコライダーの情報を取得するのに用いられます。
Physics.Raycast(Rayの開始位置, Rayの方向, 衝突したオブジェクトの情報, Rayの長さ);
といったように使います。いくつか要素がありますが、必要な要素はRayの開始位置
とRayの方向
のみです。
今回は以下のように記しました。
if(Physics.Raycast(this.transform.position, posDelta, out RaycastHit hit))
第1引数にはthis.transform.position
、第2引数にはposDelta
を入れます。
すでにVector3でベクトルを作っているので、今回はposDeltaを使用します。
また、out RaycastHit hit
のhitは何があたったかという情報を持っています。
if (hit.collider==other)
{
Debug.Log("range of view");
}
RaycasthitのColliderを使って、あたった相手側である引数other
に当たったかどうかのチェックを行います。
以上!!
実際にunityを動かしてみると、前方向一定の角度内に入っている場合のみにDebug.Logが表示されるはずです。
参考
・https://www.youtube.com/watch?v=sM5eB4I1JwA
・http://physics.thick.jp/Physical_Mathematics/Section3/3-1.html
・https://nekojara.city/unity-vector-angle
・https://rightcode.co.jp/blog/become-engineer/unity-raycast-ray
・https://docs.unity3d.com/ja/current/ScriptReference/Physics.Raycast.html