Part of these game systems:
beginner AI & Pathfinding

Enemy Chase AI

Enemy AI that detects the player within radius, chases with line-of-sight, and returns to patrol when player escapes.

Unity 2022.3+ · 2.5 KB · EnemyChaseAI.cs

How to Use

1

Attach EnemyChaseAI to your enemy

2

Tag your player as 'Player'

3

Set detection radius and field of view

4

Configure chase speed and give-up distance

5

Hook OnPlayerDetected and OnAttackRange events

6

Gizmos show detection/attack ranges in Scene view

7

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

EnemyChaseAI.cs
C#
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
}