Enemy Chase AI
Enemy AI that detects the player within radius, chases with line-of-sight, and returns to patrol when player escapes.
How to Use
Attach EnemyChaseAI to your enemy
Tag your player as 'Player'
Set detection radius and field of view
Configure chase speed and give-up distance
Hook OnPlayerDetected and OnAttackRange events
Gizmos show detection/attack ranges in Scene view
Pair with WaypointPatrol for idle behavior
Features
- Detection radius with configurable field of view angle
- Line-of-sight check using raycasts against obstacle layers
- Chase behavior with smooth rotation towards the player
- Give-up distance for returning to origin when player escapes
- Attack range trigger with OnAttackRange event callback
- Editor Gizmos for detection radius, FOV cone, and attack range
When to Use This
Great for RPGs, horror games, stealth games, and FPS titles that need enemies reacting to the player. Use this for guard enemies, monsters, or any AI that should detect and chase the player within a vision cone. Combine with WaypointPatrol for full patrol-detect-chase-return behavior loops.
Common Mistakes
The player GameObject must be tagged as Player (exact match, case-sensitive) or the script cannot find the target. If requireLineOfSight is enabled but obstacleLayers is set to Nothing, the raycast will never hit obstacles and the enemy will see through walls. The fieldOfView value is the full cone angle (120 means 60 degrees each side), not half — setting it to 360 disables the FOV check entirely.
Source Code
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// Enemy AI with detection radius, line-of-sight check,
/// and chase behavior. Returns to origin when player escapes.
/// </summary>
public class EnemyChaseAI : MonoBehaviour
{
[Header("Detection")]
[SerializeField] private float detectionRadius = 10f;
[SerializeField] private float fieldOfView = 120f;
[SerializeField] private bool requireLineOfSight = true;
[SerializeField] private LayerMask obstacleLayers;
[Header("Chase")]
[SerializeField] private float chaseSpeed = 5f;
[SerializeField] private float rotationSpeed = 8f;
[SerializeField] private float giveUpDistance = 15f;
[SerializeField] private float attackRange = 1.5f;
[Header("Target")]
[SerializeField] private string playerTag = "Player";
[Header("Events")]
public UnityEvent OnPlayerDetected;
public UnityEvent OnPlayerLost;
public UnityEvent OnAttackRange;
private Transform player;
private Vector3 originPosition;
private bool isChasing;
private bool isReturning;
public bool IsChasing => isChasing;
private void Start()
{
originPosition = transform.position;
GameObject playerObj = GameObject.FindGameObjectWithTag(playerTag);
if (playerObj != null)
player = playerObj.transform;
}
private void Update()
{
if (player == null) return;
float distToPlayer = Vector3.Distance(transform.position, player.position);
if (isChasing)
{
if (distToPlayer > giveUpDistance)
{
StopChasing();
return;
}
if (distToPlayer <= attackRange)
{
OnAttackRange?.Invoke();
return;
}
ChasePlayer();
}
else if (isReturning)
{
ReturnToOrigin();
}
else
{
if (CanDetectPlayer(distToPlayer))
{
isChasing = true;
OnPlayerDetected?.Invoke();
}
}
}
private bool CanDetectPlayer(float distance)
{
if (distance > detectionRadius) return false;
// FOV check
Vector3 dirToPlayer = (player.position - transform.position).normalized;
float angle = Vector3.Angle(transform.forward, dirToPlayer);
if (angle > fieldOfView * 0.5f) return false;
// Line of sight check
if (requireLineOfSight)
{
if (Physics.Raycast(transform.position + Vector3.up, dirToPlayer, distance, obstacleLayers))
return false;
}
return true;
}
private void ChasePlayer()
{
Vector3 direction = (player.position - transform.position);
direction.y = 0f;
if (direction.magnitude > 0.1f)
{
Vector3 moveDir = direction.normalized;
transform.position += moveDir * chaseSpeed * Time.deltaTime;
Quaternion targetRot = Quaternion.LookRotation(moveDir);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, rotationSpeed * Time.deltaTime);
}
}
private void StopChasing()
{
isChasing = false;
isReturning = true;
OnPlayerLost?.Invoke();
}
private void ReturnToOrigin()
{
Vector3 direction = (originPosition - transform.position);
direction.y = 0f;
if (direction.magnitude <= 0.3f)
{
isReturning = false;
return;
}
Vector3 moveDir = direction.normalized;
transform.position += moveDir * (chaseSpeed * 0.6f) * Time.deltaTime;
Quaternion targetRot = Quaternion.LookRotation(moveDir);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, rotationSpeed * Time.deltaTime);
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(1f, 0.3f, 0.3f, 0.3f);
Gizmos.DrawWireSphere(transform.position, detectionRadius);
Gizmos.color = new Color(1f, 0.6f, 0f, 0.3f);
Gizmos.DrawWireSphere(transform.position, giveUpDistance);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
// FOV lines
Gizmos.color = Color.yellow;
Vector3 fovLeft = Quaternion.Euler(0, -fieldOfView * 0.5f, 0) * transform.forward * detectionRadius;
Vector3 fovRight = Quaternion.Euler(0, fieldOfView * 0.5f, 0) * transform.forward * detectionRadius;
Gizmos.DrawLine(transform.position, transform.position + fovLeft);
Gizmos.DrawLine(transform.position, transform.position + fovRight);
}
#endif
}