【動画あり】扇状の当たり判定の取り方!ステルスゲーの視界判定など

2021年10月20日

バイオハザードなどで敵がプレイヤーキャラを見つけたりする当たり判定は扇状になっていることが多いです。今回は扇状の当たり判定をどのように取ればいいかを解説したいと思います。ちなみに動画で内容を確認したい場合はこちらをご覧ください

https://youtu.be/sM5eB4I1JwA

扇状の当たり判定を行うまでの工程

扇形での当たり判定を行う場合、いくつかの処理を経由して行われます

  • 直線距離内に存在するかの判定(SphereColliderなどを利用)
  • 視野の角度内にいるかどうかの判定
  • 直線上にターゲットが存在するかどうかの判定(障害物のチェック)

直線距離内に存在するかの判定

具体的にはSphereColliderやCircleCollider2Dを利用して何かと当たったかどうかを検出する。よくあるタグなどを利用して、検索したい対象とぶつかったかどうかを察知するのが目的。

いわゆる視界の判定になります。どのぐらい目が良いかとかそのあたりの設定によって、遠くのキャラまで見つけることが出来るかの判定。そもそも視界内に捉えられない距離の相手は見つけることができないという判定になるため、真っ先に行います。

視野の角度内にいるかどうかの判定

正面に対して、左右にどのぐらいの角度内にターゲットが存在するかのチェック。

直線距離として近くにターゲットが存在したとしても、真後ろに立っている敵などを発見できないというのは、この角度外に位置しているということになります。そのため、1つ目の直線距離内の判定を行った次に、角度内に収まっているかどうかの確認を行う必要があります。

直線上にターゲットが存在するかどうか

最後の仕上げとして、直線上にターゲットが存在するかどうかの判定を行います。これは、直線距離内や、正面などにターゲットが存在したとしても、壁や障害物に阻まれている場合は気づかないようにする必要があるからです。

足音とかは今回はなし

実際のステルスゲームだと、足音や銃を打ったりした音などで発見されることもあると思います。今回は視線での索敵などがメインになっているので除外しておりますが、ゲームによっては今回の当たり判定以外に、音で気づくなどの要素も追加しましょう。

実装編

実際に扇状の当たり判定を取っていきたいと思います。前述している3つの要素を一つずつ実装したいと思います。シチュエーションとしては下図のようなもの。緑の箱くんが青い球のターゲットを探そうと思います。また、紫の壁が間にある場合は見つけられないとします。また、以下の3つのオブジェクトをそれぞれ下記のように名前をつけます。

オブジェクト名前
探す人(緑)Searcher
ターゲット(青)Target
邪魔な壁(紫)Obstacle

直線距離内にいるかどうかはColliderを利用して判定する

最初の当たり判定として、直線内に存在するかどうかを判定するのにColliderを利用したいと思います。設定は以下

  • 緑の箱(探す人)に十分な大きさのSphereColliderを追加、IsTriggerにチェックを入れる
  • 緑の箱(探す人)にRigidbodyを追加(超重要)
  • 青い球(ターゲット)に形にフィットした当たり判定(この場合はSphereCollider)を付ける
  • 青い球(ターゲット)にタグ「target」を設定する

上記を済ませたら、FunSearchというスクリプトを作成して、次のスクリプトを書いてください。

using UnityEngine;
public class FanSearch : MonoBehaviour
{
	private void OnTriggerStay(Collider other)
	{
		if( other.tag == "target")
		{
			Debug.Log("視界の範囲内");
		}
	}
}

スクリプトが準備できたら緑の箱にスクリプトをAddComponentして動かします。青い球がSphereColliderと接する位置にある時、「視界の範囲内」のログが表示されると成功です。

視野の角度内に収まっているかどうか

次にSearcherの正面に対して視界以内にTargetが位置しているかどうかを調べます。

調べる方法としてはSearcherの正面に対して、Targetへの成す角度(下図赤い角度)が視界の範囲とする角度内に収まっているかどうかを調べます。

上記の条件を踏まえて先程のプログラムを以下のように変更してください。

using UnityEngine;
public class FanSearch : MonoBehaviour
{
	public float m_fSightAngle = 45.0f;
	private void OnTriggerStay(Collider other)
	{
		if( other.tag == "target")
		{
			Vector3 posDelta = other.transform.position - transform.position;
			float targetAngle = Vector3.Angle(transform.forward, posDelta);
			if( targetAngle < m_fSightAngle)
			{
				Debug.Log("視界の範囲内&視界の角度内");
			}
		}
	}
}

プログラムが変更できたら先程のプログラムでログが出ていた位置関係にしつつ、Searcherの角度を回転させてみて、角度によってログが出たりでなくなったりするのを確認してください。

途中に邪魔な壁が存在しないかどうかを調べる

仕上げにSearcherとTargetの間に遮るものが存在しないかどうかを調べたいと思います。今回この評価方法としてはシンプルにSearcherからTargetにRayを飛ばして、当たったオブジェクトがTarget本人かどうかで確認を行います。

using UnityEngine;
public class FanSearch : MonoBehaviour
{
	public float m_fSightAngle = 45.0f;
	private void OnTriggerStay(Collider other)
	{
		if( other.tag == "target")
		{
			Vector3 posDelta = other.transform.position - transform.position;
			float targetAngle = Vector3.Angle(transform.forward, posDelta);
			if( targetAngle < m_fSightAngle)
			{
				if( Physics.Raycast(transform.position,new Vector3(posDelta.x,0f,posDelta.z),out RaycastHit hit))
				{
					if( hit.collider == other)
					{
						Debug.Log("視界の範囲内&視界の角度内&障害物なし");
					}
				}
			}
		}
	}
}

こちらのプログラムに変更して、先程まで当たり判定が取れていた位置関係の状態から間にObstacleを挟んでみてデバッグログが反応しなくなるのを確認してみてください。

うまくいったら扇状の当たり判定の完成です!

実践時の注意点など

キャラクターのモデルとかの原点が、足元にあったりすると地面と当たり判定を取ってしまったりして、最後の障害物がないかどうかのチェックがうまくいかない可能性があります。

こういった当たり判定を取る場合、一つずつどのポイントがおかしくなっているかを振り替えれるように作ることが重要です。今回は3つの段階を経て当たり判定を取れるようにしました。自分のプログラムに反映する時、どこが大丈夫でどの部分がだめなのかを自覚できるようにしておけると良いでしょう。