Want more features? Check out the with advanced patrol ai with navmesh pathfinding, player detection with awareness meter, alert states, group coordination, and patrol schedules.
Part of these game systems:
beginner AI & Pathfinding

Waypoint Patrol AI

Enemy patrol between waypoints with configurable speed, wait times, and patrol patterns (loop, ping-pong, random).

Unity 2022.3+ · 2.3 KB · WaypointPatrol.cs

How to Use

1

Create empty GameObjects as waypoints, position them in the scene

2

Attach WaypointPatrol to your enemy

3

Drag waypoints into the array in inspector

4

Choose patrol pattern: Loop, PingPong, or Random

5

Adjust speed and wait time at each waypoint

6

Gizmos show patrol path in Scene view (select the enemy)

Features

  • Three patrol modes: Loop, PingPong, and Random
  • Configurable move speed, rotation speed, and arrival threshold
  • Wait timer at each waypoint with adjustable duration
  • Smooth rotation towards movement direction via Slerp
  • PausePatrol and ResumePatrol methods for state integration
  • Editor Gizmos draw waypoint path and connections in Scene view

When to Use This

Ideal for stealth games, RPGs, tower defense, and any game with guard enemies or NPCs that walk predefined routes. Use Loop mode for circular patrols, PingPong for back-and-forth hallway guards, and Random for unpredictable patrol behavior. Combine with EnemyChaseAI to switch from patrol to chase when the player is detected.

Common Mistakes

The waypoints array must be populated with Transforms in the Inspector — empty or null entries cause the enemy to freeze. Waypoints are world-space positions, so moving the parent of waypoint objects at runtime will not update the patrol path. Setting arrivalThreshold too small (below 0.1) can cause the enemy to orbit the waypoint endlessly without triggering arrival.

Source Code

WaypointPatrol.cs
C#
using UnityEngine;

/// <summary>
/// Moves an enemy between waypoints with configurable patterns.
/// Supports loop, ping-pong, and random patrol modes.
/// </summary>
public class WaypointPatrol : MonoBehaviour
{
    public enum PatrolMode { Loop, PingPong, Random }

    [Header("Waypoints")]
    [SerializeField] private Transform[] waypoints;

    [Header("Movement")]
    [SerializeField] private float moveSpeed = 3f;
    [SerializeField] private float rotationSpeed = 5f;
    [SerializeField] private float arrivalThreshold = 0.2f;

    [Header("Waiting")]
    [SerializeField] private float waitTime = 1f;

    [Header("Pattern")]
    [SerializeField] private PatrolMode patrolMode = PatrolMode.Loop;

    private int currentIndex;
    private int direction = 1;
    private float waitTimer;
    private bool isWaiting;

    private void Update()
    {
        if (waypoints == null || waypoints.Length == 0) return;

        if (isWaiting)
        {
            waitTimer -= Time.deltaTime;
            if (waitTimer <= 0f)
                isWaiting = false;
            return;
        }

        MoveToWaypoint();
    }

    private void MoveToWaypoint()
    {
        Transform target = waypoints[currentIndex];
        if (target == null) return;

        Vector3 targetPos = target.position;
        Vector3 direction3d = (targetPos - transform.position);
        direction3d.y = 0f; // Keep on same plane

        float distance = direction3d.magnitude;

        if (distance <= arrivalThreshold)
        {
            ArrivedAtWaypoint();
            return;
        }

        // Move
        Vector3 moveDir = direction3d.normalized;
        transform.position += moveDir * moveSpeed * Time.deltaTime;

        // Rotate towards movement direction
        if (moveDir != Vector3.zero)
        {
            Quaternion targetRotation = Quaternion.LookRotation(moveDir);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        }
    }

    private void ArrivedAtWaypoint()
    {
        isWaiting = true;
        waitTimer = waitTime;

        switch (patrolMode)
        {
            case PatrolMode.Loop:
                currentIndex = (currentIndex + 1) % waypoints.Length;
                break;

            case PatrolMode.PingPong:
                currentIndex += direction;
                if (currentIndex >= waypoints.Length - 1 || currentIndex <= 0)
                    direction *= -1;
                break;

            case PatrolMode.Random:
                int next;
                do { next = Random.Range(0, waypoints.Length); }
                while (next == currentIndex && waypoints.Length > 1);
                currentIndex = next;
                break;
        }
    }

    /// <summary>
    /// Pause patrol at current position.
    /// </summary>
    public void PausePatrol() => enabled = false;

    /// <summary>
    /// Resume patrol from current position.
    /// </summary>
    public void ResumePatrol() => enabled = true;

#if UNITY_EDITOR
    private void OnDrawGizmosSelected()
    {
        if (waypoints == null || waypoints.Length == 0) return;

        Gizmos.color = Color.cyan;
        for (int i = 0; i < waypoints.Length; i++)
        {
            if (waypoints[i] == null) continue;
            Gizmos.DrawWireSphere(waypoints[i].position, 0.3f);

            if (i < waypoints.Length - 1 && waypoints[i + 1] != null)
                Gizmos.DrawLine(waypoints[i].position, waypoints[i + 1].position);
        }

        // Close loop if in Loop mode
        if (patrolMode == PatrolMode.Loop && waypoints.Length > 1
            && waypoints[0] != null && waypoints[waypoints.Length - 1] != null)
        {
            Gizmos.DrawLine(waypoints[waypoints.Length - 1].position, waypoints[0].position);
        }
    }
#endif
}
Ready for more? Waypoint Patrol AI Pro Advanced patrol AI with NavMesh pathfinding, player detection with awareness meter, alert states, group coordination, and patrol schedules.