前回、NavMeshで敵はプレイヤーを追いかけるようにしましたが、このままだとどこまでもプレイヤーを追い続ける仕様なので、追いかける際の条件を付加していきます。
この記事では、Sphere Colliderで「索敵の範囲」を決め、Raycastで「壁に隠れていないか」を調べて、視界に入った時だけ敵が追いかける仕組みを作ります。2021年に書いた手順はそのまま残し、2026年11月に「視野の角度」「目の高さ」「視界の可視化」を足した改良版のスクリプトを追記しました。
敵の視界判定は「範囲・角度・壁」の3段階で考える
アクションゲームには障害物が付き物です。
今回は、Raycastを使ってプレイヤーと敵の2点間に壁などの障害物がある場合は、視界から外れるということで、敵はプレイヤーを見失うことにします。
また、それ以外の状態では常に追いかけ続けるようになっているので、敵にSphere Colliderをアタッチして、一定範囲内かつ障害物で視界を遮られていない場合に追いかけるようにします。
人の目に近づけるなら、ここにもう1つ「正面からの角度」の判定が入ります。順番にするとこうなります。
| 段階 | 調べること | 使うもの |
|---|---|---|
| 1.範囲 | 近くにいるか | Sphere Collider(Is Trigger) |
| 2.角度 | 敵の正面側にいるか | Vector3.Angle |
| 3.壁 | 間に障害物がないか | Physics.Raycast |
軽い判定から先にやるのがコツです。Raycastは3つの中でいちばん処理が重いので、範囲と角度で外れた時は飛ばさずに済ませます。
敵にSphere Colliderを設定する(索敵の範囲)

敵オブジェクトに、一定範囲内に入った事を検知するためのSphere Colliderをアタッチします。
Radiusは「5」にしています。
Is Triggerにチェックを入れておきます。
ここで1つ注意があります。トリガーの出入り(OnTriggerEnterやOnTriggerStay)は、ぶつかる2つのうち少なくとも片方にRigidbodyが付いていないと呼ばれません。敵がNavMesh Agentだけで動いていて、プレイヤー側にもRigidbodyがない場合は、どちらかにRigidbodyを付けてIs Kinematicにチェックを入れてください。
Raycastで壁に隠れたら見失うスクリプト
敵の行動スクリプトを修正します。当時のスクリプトから、視界の判定に関係する部分だけを抜き出して、日本語のコメントを付けました(ダメージのエフェクトの部分は省いています)。
using UnityEngine;
using UnityEngine.AI;
public class SlimeController : MonoBehaviour
{
private NavMeshAgent agent;
private RaycastHit hit;
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
// Sphere Colliderの範囲にいる間、毎回呼ばれる
private void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
{
GameObject Target = GameObject.Find("SDunitychan");
// 敵からプレイヤーへのベクトル・距離・向き
var diff = Target.transform.position - transform.position;
var distance = diff.magnitude;
var direction = diff.normalized;
// プレイヤーの方向へRayを飛ばす
if (Physics.Raycast(transform.position, direction, out hit, distance))
{
if (hit.transform.gameObject == Target)
{
// 最初に当たったのがプレイヤー → 見えているので追いかける
agent.isStopped = false;
agent.destination = Target.transform.position;
}
else
{
// 壁などに当たった → 見えないので止まる
agent.isStopped = true;
}
}
}
}
}
範囲内に入ってくるオブジェクトは、プレイヤーとは限らないので、プレイヤーの場合は「Player」タグで識別するようにします。
プレイヤーが範囲内に入ったら、2点間のベクトル等を計算します。
Rayはゲーム上では見えない光線のようなものです。Raycastを使う事で、この光線に衝突したコライダの情報を取得することで、障害物なのかプレイヤーなのか判断します。
ヒットしたコライダがプレイヤーなのかをif文でチェックし、プレイヤーの場合のみ敵は追いかけるようにします。
それ以外なら「agent.isStopped = true;」でナビゲーションを止めるようにします。
スクリプトを保存したらゲームを実行してみます。
Unityちゃんが壁に隠れた際は、敵の動きもちゃんと止まっていますね。
視界に入ったら追いかける改良版|視野角と目の高さを足す
上のスクリプトでも動きますが、作り込んでいくと気になる所が3つ出てきます。
1つ目は、背中側にいても見つかること。範囲の中なら真後ろでも追いかけてきます。2つ目は、Rayが敵の足元(transform.position)から出ていること。床の小さな段差や低い柵にRayが当たって、見えているのに止まることがあります。3つ目は、OnTriggerStayの中で毎回GameObject.Findを呼んでいること。Findはシーン全体を名前で探すので、敵が増えるほど重くなります。
この3つを直したのが次のスクリプトです。Sphere Collider(Is Trigger)とNavMesh Agentを付けた敵に付けてください。
using UnityEngine;
using UnityEngine.AI;
// Sphere Colliderの範囲に入ったプレイヤーを、
// 「視野の角度」と「Raycastで壁がないか」の2段階で判定して追いかける
[RequireComponent(typeof(NavMeshAgent))]
public class EnemySight : MonoBehaviour
{
[SerializeField] float viewAngle = 60f; // 正面から左右それぞれ何度まで見えるか
[SerializeField] float eyeHeight = 0.5f; // 敵の目の高さ(足元からの高さ)
[SerializeField] float targetHeight = 1.0f; // プレイヤーのどこを見るか(足元からの高さ)
[SerializeField] LayerMask sightMask = ~0; // Rayが当たる物(壁とプレイヤーのレイヤー)
NavMeshAgent agent;
Transform target; // 範囲内にいるプレイヤー(いなければnull)
void Awake()
{
agent = GetComponent<NavMeshAgent>();
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
target = other.transform;
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
{
target = null;
agent.isStopped = true; // 範囲の外に出たら止まる
}
}
void Update()
{
if (target == null) return;
if (CanSee(target))
{
agent.isStopped = false;
agent.SetDestination(target.position);
}
else
{
agent.isStopped = true; // 壁に隠れた・背後にいる時は止まる
}
}
bool CanSee(Transform t)
{
Vector3 eye = transform.position + Vector3.up * eyeHeight;
Vector3 aim = t.position + Vector3.up * targetHeight;
Vector3 diff = aim - eye;
// 1. 角度:高さの差は無視して、正面から何度ずれているかを見る
Vector3 flat = new Vector3(diff.x, 0f, diff.z);
if (Vector3.Angle(transform.forward, flat) > viewAngle) return false;
// 2. 壁:目からプレイヤーまでRayを飛ばし、最初に当たった物がプレイヤーか
// QueryTriggerInteraction.Ignore で、ほかの敵の索敵範囲(トリガー)には当たらないようにする
if (Physics.Raycast(eye, diff.normalized, out RaycastHit hit, diff.magnitude + 0.5f,
sightMask, QueryTriggerInteraction.Ignore))
{
bool isPlayer = hit.transform == t || hit.transform.IsChildOf(t);
Debug.DrawLine(eye, hit.point, isPlayer ? Color.green : Color.red); // Sceneビューで線が見える
return isPlayer;
}
return false;
}
// 選んでいる時だけ、Sceneビューに視野の両端を黄色い線で描く
void OnDrawGizmosSelected()
{
float range = 5f;
SphereCollider sphere = GetComponent<SphereCollider>();
if (sphere != null) range = sphere.radius;
Vector3 eye = transform.position + Vector3.up * eyeHeight;
Vector3 left = Quaternion.AngleAxis(-viewAngle, Vector3.up) * transform.forward;
Vector3 right = Quaternion.AngleAxis(viewAngle, Vector3.up) * transform.forward;
Gizmos.color = Color.yellow;
Gizmos.DrawLine(eye, eye + left * range);
Gizmos.DrawLine(eye, eye + right * range);
}
}
このスクリプトは、Unity 6000.3.14f1のDLLでビルドが通ることを確かめています(エディタ上で動かしたのは当時の元のスクリプトだけです)。
プレイヤーは範囲に入った時に覚えておく
改良版では、OnTriggerEnterでプレイヤーのTransformを覚え、OnTriggerExitで忘れるようにしました。名前で探す必要がなくなるので、プレイヤーの名前を変えても動きます。判定そのものはUpdateで行っています。
角度はVector3.Angleで、高さを消してから測る
Vector3.Angleは2つの向きのあいだの角度を0〜180度で返します。viewAngleを60にすると、正面から左右60度ずつ、合わせて120度が見える範囲です。
高さの差を消してから測っているのは、プレイヤーが段差の上にいるだけで「角度の外」と判定されないようにするため。上下の見え方は、次のRaycastに任せています。
Rayがほかの敵の索敵範囲に当たらないようにする
最初はここでつまずきやすいです。Raycastは、Physics設定の「Queries Hit Triggers」がオン(初期値)だと、Is Triggerのコライダーにも当たります。敵が2体並んでいると、手前の敵の索敵用Sphere ColliderにRayが当たって、プレイヤーが見えているのに止まってしまうんです。
改良版では、Raycastの最後の引数にQueryTriggerInteraction.Ignoreを渡して、トリガーを無視しています。自分自身のコライダーについては、Raycastは「Rayの出発点を中に含むコライダー」を検出しないと公式リファレンスに書かれているので、目の位置が自分のコライダーの中にあれば当たりません。
もう1つ、sightMaskで当たるレイヤーを絞っておくと確実です。壁と床のレイヤー、プレイヤーのレイヤーだけを選び、エフェクトやアイテムのレイヤーは外しておきましょう。
視界を可視化して調整する(Debug.DrawLineとGizmos)
視界の判定は目に見えないので、数値を決めるのが難しいところです。改良版には、確認用の線を2種類入れています。
Debug.DrawLineは再生中だけ、Sceneビューに線を出します。プレイヤーに届いた時は緑、壁に当たった時は赤。赤い線の先を見れば、何に遮られているのかがすぐ分かります。Gameビューにも出したい時は、Gameビュー上部のGizmosボタンをオンにしてください。
OnDrawGizmosSelectedは、敵を選んでいる時だけ、視野の左右の端を黄色い線で描きます。再生しなくても見えるので、viewAngleをインスペクターで変えながら、ちょうどいい広さを探せます。範囲の長さはSphere ColliderのRadiusを使っていますが、敵のScaleを変えている場合は実際の範囲とずれる点だけ注意です。
視界に入っても追いかけない時の確認点
改良版を付けても敵が動かない時は、次の順に見ていくと原因が絞れます。
| 症状 | 確認すること |
|---|---|
| 線がまったく出ない | プレイヤーのTagがPlayerか、どちらかにRigidbodyがあるか |
| 赤い線が床や柵で止まる | eyeHeight・targetHeightを上げる、sightMaskから外す |
| 赤い線が別の敵で止まる | 敵のレイヤーをsightMaskから外す |
| 緑の線なのに動かない | NavMeshがベイクされているか、敵がNavMeshの上にいるか |
最後の「緑の線なのに動かない」は、視界ではなくNavMeshの側の問題です。Unity 6のNavMesh Surfaceでベイクできない・表示されない時の原因は、別の記事に7つまとめました。
巡回・捜索・攻撃まで付けるなら
今回の視界の判定は、敵AIの一部分です。普段は決まったルートを巡回して、見つけたら追いかけ、見失ったら最後に見た場所を探す。そこまで作るなら、ステートで切り替える形にすると整理しやすいです。視界の判定も、同じ「距離→角度→壁」の順で組み込んでいます。
聴覚や仲間への合図まで自分で作るのは大変なので、敵AIのアセットに任せるのも1つの手です。視界の設定がインスペクターで済むものも多く、無料のUnity Behaviorと有料の5本をこちらで比べています。
有料アセットを買うなら、セールの時期に合わせると安く済みます。毎月のUnityアセットストアのセール情報や、セール時におすすめの人気アセット40選も参考にしてみてください。
まとめ|Raycastの視界判定は軽い順に
敵の視界は、Sphere Colliderで範囲、Vector3.Angleで角度、Physics.Raycastで壁、の順に判定すると作りやすいです。Rayは目の高さから出し、トリガーを無視する設定にしておくと、見えているのに止まる失敗がぐっと減ります。
Raycastは、便利な機能ですが使いすぎると処理が重くなるので気をつけましょう。






