Part of these game systems:
Waypoint Patrol AI
Enemy patrol between waypoints with configurable speed, wait times, and patrol patterns (loop, ping-pong, random).
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)
Source Code
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
}