← Back to all addons

AI Waypoint Movement System (3.x)

Posted by Soho on May 2, 2025 at 4:28 AM

ExperimentalFeature3.0 Verified
💡 3

Adds waypoint logic for enemies and NPCS. This logic could also work as a basic schedule system.

Let me know if you encounter any issues! carthumbup

Features:

Set any amount of waypoints the AI will follow, with individual pause times.

Loop modes: Toggle if the AI repeats the waypoints with 2 settings:

  • Cycle (restart from the first waypoint) 1 > 2 > 3 >4 > 1 > 2 >3 > 4
  • Mirrored (reverse direction at the end). 1 > 2 > 3 > 4 > 3 > 2 > 1

Chase logic still works, and will head back to their last waypoint.

1. Define Waypoint Data:

[Serializable]
public class Waypoint
{
    public Transform transform;
    public float pauseDuration = 2.0f;
}

2. Add Waypoint Settings:

[Header("Waypoint Movement Settings")]
[SerializeField] private bool enableWaypointMovement = false;
[SerializeField] private List<Waypoint> waypoints = new();
[SerializeField] private bool enableLoopMode = true;
[SerializeField] private WaypointLoopMode loopMode = WaypointLoopMode.Cycle;

private int currentWaypointIndex = 0;
private float waypointPauseTimer = 0.0f;
private bool isReversing = false;
private int lastVisitedWaypointIndex = 0;

Define Loop Modes:

public enum WaypointLoopMode
{
    Cycle,
    Mirrored
}

3. Implement Waypoint Movement Logic:

private void UpdateWaypointMovement()
{
    if (m_target != null) return;

    if (waypointPauseTimer > 0.0f)
    {
        waypointPauseTimer -= Time.deltaTime;

        if (roamAtWaypoint)
        {
            UpdateRandomIdleMovement();
        }
        else
        {
            m_subject.SetMovementDirection(Vector2.zero);
        }

        return;
    }

    Waypoint targetWaypoint = waypoints[currentWaypointIndex];
    Vector2 directionToWaypoint = (Vector2)targetWaypoint.transform.position - (Vector2)transform.position;

    if (directionToWaypoint.magnitude > 0.1f)
    {
        m_subject.SetMovementDirection(directionToWaypoint.normalized);
    }
    else
    {
        m_subject.SetMovementDirection(Vector2.zero);
        waypointPauseTimer = targetWaypoint.pauseDuration;
        lastVisitedWaypointIndex = currentWaypointIndex;

        if (enableLoopMode)
        {
            if (loopMode == WaypointLoopMode.Cycle)
            {
                if (currentWaypointIndex + 1 < waypoints.Count)
                {
                    currentWaypointIndex++;
                }
                else
                {
                    currentWaypointIndex = 0;
                }
            }
            else if (loopMode == WaypointLoopMode.Mirrored)
            {
                if (!isReversing)
                {
                    if (currentWaypointIndex + 1 < waypoints.Count)
                    {
                        currentWaypointIndex++;
                    }
                    else
                    {
                        isReversing = true;
                        currentWaypointIndex--;
                    }
                }
                else
                {
                    if (currentWaypointIndex > 0)
                    {
                        currentWaypointIndex--;
                    }
                    else
                    {
                        isReversing = false;
                        currentWaypointIndex++;
                    }
                }
            }
        }
        else
        {
            if (currentWaypointIndex + 1 < waypoints.Count)
            {
                currentWaypointIndex++;
            }
        }
    }
}

AI Waypoint Movement System (3.x)

4. Chase logic support

private void StopChase(float retargetCooldown)
{
    m_retargetCooldownTimer = retargetCooldown;
    m_target = null;
    wasAlerted = false;

    if (enableWaypointMovement && waypoints.Count > 0)
    {
        currentWaypointIndex = lastVisitedWaypointIndex;
    }
}

5. Add Gizmos!

protected override void OnDrawGizmos()
{
    if (enableWaypointMovement && waypoints.Count > 0)
    {
        Gizmos.color = Color.blue;

        for (int i = 0; i < waypoints.Count; i++)
        {
            if (i + 1 < waypoints.Count)
            {
                Gizmos.DrawLine(waypoints[i].transform.position, waypoints[i + 1].transform.position);
            }
            else if (loopMode == WaypointLoopMode.Cycle)
            {
                Gizmos.DrawLine(waypoints[i].transform.position, waypoints[0].transform.position);
            }
        }

        Gizmos.color = Color.green;
        Gizmos.DrawWireSphere(waypoints[currentWaypointIndex].transform.position, 0.3f);

        Gizmos.color = Color.white;
        Gizmos.DrawLine(transform.position, waypoints[currentWaypointIndex].transform.position);
    }
}

Note, I removed the roam at waypoint function as it relies on my idle movement logic. I posted it elsewhere, so I can provide the code if anyone would like

💬 Comments (12)

Gyvr May 02, 2025 at 12:03 PM
Very cool!! Thank you so much for sharing 🙏
❤️ 1
Mr.Titan May 02, 2025 at 04:58 PM
This is nice! I would say a command for this would be perfect
upvote 2
Soho May 02, 2025 at 06:26 PM
I’m working on that soon, once I do I’ll update this version!
MrMystery May 13, 2025 at 07:37 PM
@Soho is there a step missing? Nothing calls UpdateWaypointMovement()?
Soho May 14, 2025 at 05:19 AM

Ahh, I tried to exclude the code for my idle movement, ```private void UpdateIdleMovement() { if (m_target != null) return;

if (enableWaypointMovement && waypoints.Count > 0)
{
    UpdateWaypointMovement();
}
else if (enableIdleMovement)
{
    UpdateRandomIdleMovement();
}

}

MrMystery Sep 09, 2025 at 02:11 PM

Hey all, big thanks again to @Soho for originally creating this waypoint system, it’s great, I use it in my game, I’ve made some modifications to it, and turned it into a separate stand-alone drop in module, so it’s way easier to implement into your game, so please credit Soho, but you can use this package for a drop in - the only required change to the base code is to add this simple method:

In AIController.cs add:

public bool HasTarget()
{
    return m_target != null;
}

To use it, simply change the Controller on your NPC/Monster to AI Waypoint Controller, you will then have options for idle/random movements, and waypoints. Then make some empty game objects, call them Waypoints… and add the Waypoint.cs script to each one… you can then use those waypoints on the monster, or assign them at runtime.

This also has my modified Waypoints in them, which allows you to have the entity run Commands while at a waypoint, for instance, if you wanted an NPC to walk somewhere on the waypoint path, stop, and start a dialogue… etc…

eyesshaking 1 🇧🇷 1 1 🙏 1 👏 1 🔥 1
MrMystery Sep 09, 2025 at 02:12 PM
@Zuko here you go!
Zuko Sep 09, 2025 at 02:24 PM

I want to reinforce my thanks to @Soho for originally creating this incredible add-on. Thank you for helping the community with new stuff for us to enjoy!

And also a huge thank you to @MrMystery for turning this into something even easier to implement for new game devs like me, who have little or no previous experience with C# and Unity in general.** I’d also like to publicly thank you here for all the help you’ve been giving me. My project has advanced so much thanks to you!**

And one last thank you to @Gyvr , who created the universe where all these cool things are able to happen!

❤️ 2 partyparrot 1
Lee @ Raguthra Sep 15, 2025 at 09:25 AM
Hey everyone this is fantastic, I am having an issue though with polydirectional characters though - they don’t change their facing directions when in idle random wander - same character switched to bidirectional flips etc - I haven’t tried the waypoints and will tomorrow but just wondering if this makes sense to you guys? Worst case I can live with bidirectional but four directions would be cool
MrMystery Sep 17, 2025 at 04:44 AM
When you say - same character when switch to bi-directional flips - is the character properly setup for 4 directions? Or are you just changing the field that says it’s poly directional? I use this and have made all my characters 8 directional - and it works fine…
Lee @ Raguthra Sep 17, 2025 at 07:27 AM

I set them up following the four directional archer model and they work no trouble it’s just in here it seems the override for the different directions doesn’t kick in - good to know though that there’s no problem on the script side if it works automatically with 8 direction!!! Means it has to be something I’m missing in my set up.

The bidirectional part was just in testing I turned it to that in the animation strategy to see if it would make a difference which it did - as I said though its good to know it’s on my setup

Lee @ Raguthra Sep 17, 2025 at 07:42 AM
AND with that I found an overlooked setting and I have all straightened out!!! Works perfectly!!!

Want to continue the conversation?