Need help using Lerp to traverse list of waypoints

I’m trying to move an object through a series of waypoints that form a curve and would like this traversal to be completed in fixed amount of time. One issue I’m running into with this code is that as I try to add more waypoints to make the curve rounder the amount of time it takes for the object to traverse the curve increases.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveController : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private Transform start;
    [SerializeField] private Transform anchor;
    [SerializeField] private Transform end;

    [SerializeField] private int numSegments = 10;
    [SerializeField] private float travelDuration = 5f;

    private bool isFinished;
    private float totalTimeElapsed;
    private float travelDistance;
    private Vector3[] waypoints;

    private void Start()
    {
        waypoints = new Vector3[numSegments + 1];
        SetBezierPositions();
        SetTravelDistance();
    }

    private void Update()
    {
        StartCoroutine(Lerp());

        if (isFinished)
            Debug.Log(totalTimeElapsed);
        else
            totalTimeElapsed += Time.deltaTime;
    }

    IEnumerator Lerp()
    {
        for (int i = 1; i < waypoints.Length; i++)
        {
            float segmentLength = Vector3.Distance(waypoints[i], waypoints[i - 1]);
            float ratio = segmentLength / travelDistance;
            float timeSlice = ratio * travelDuration;
            yield return MoveInSeconds(target, target.position, waypoints[i], timeSlice);
        }
        isFinished = true;
        yield return null;
    }

    IEnumerator MoveInSeconds(Transform obj, Vector3 from, Vector3 to, float duration)
    {
        float timeElapsed = 0f;
        while (timeElapsed < duration)
        {
            obj.position = Vector3.Lerp(from, to, timeElapsed / duration);
            timeElapsed += Time.deltaTime;
            yield return null;
        }
        obj.position = to;
    }

    private void SetTravelDistance()
    {
        float distance = 0;

        for (int i = 1; i < numSegments + 1; i++)
            distance += Vector3.Magnitude(waypoints[i] - waypoints[i - 1]);

        travelDistance = distance;
    }

    private void SetBezierPositions()
    {
        Vector3[] initialWaypoints = new Vector3[numSegments];

        for (int i = 1; i < numSegments + 1; i++)
        {
            float t = i / (float)numSegments;
            initialWaypoints[i - 1] = GetBezierPoint(t, start.position, anchor.position, end.position);
            waypoints[i] = initialWaypoints[i - 1];
        }
        waypoints[0] = start.position;
        waypoints[numSegments] = initialWaypoints[numSegments - 1];
    }

    private Vector3 GetBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
    {
        float u = 1 - t;
        float tt = t * t;
        float uu = u * u;
        return (uu * p0) + (2 * u * t * p1) + (tt * p2);
    }

}

Rather than doing a moveinseconds with Lerp, which guarantees that a single waypoint will take a certain amount of time, you should do a MoveWithSpeed that moves at some constant speed. Instead of Lerp, you should use MoveTowards.

1 Like

Ok, so I wrote some code using MoveTowards instead, but I’m not really sure what I should be using to calculate the maxDeltaDistance.

Currently, it happens way too quickly.

    IEnumerator Lerp()
    {
        for (int i = 0; i < waypoints.Length; i++)
        {
            Vector3 waypoint = waypoints[i];
            while (target.position != waypoint)
            {
                target.position = Vector3.MoveTowards(target.position, waypoint, travelSpeed * Time.deltaTime);
            }
            yield return null;
        }
        isFinished = true;
      
    }

You need to put yield return null inside the loop so it waits a frame between each iteration.

So I did that, and then I ran into some other issues but the object moves a little bit and then stops and stutters. I tried using Time.fixedDeltaTime but the object moved way to fast, I also tried changing the condition for the while loop to not be a straight up equality but instead checking if it’s within a range but that didn’t work either.

Stuttering is happening because you are starting the Coroutine every frame in Update. That means you will have hundreds of coriutines running at the same time, competing to move the object.

1 Like

There is absolutely zero call for a coroutine in this case. Delete the coroutine, it is inappropriate.

  1. Make a waypoint manager that says “Go to this point!” at the appropriate time.

  2. Make a traverser, and follow this pattern:

Smoothing movement between any two particular values:

You have currentQuantity and desiredQuantity.

  • only set desiredQuantity
  • the code always moves currentQuantity towards desiredQuantity
  • read currentQuantity for the smoothed value

Works for floats, Vectors, Colors, Quaternions, anything continuous or lerp-able.

The code: SmoothMovement.cs · GitHub

Obviously the waypoint manager only sets the “desired position” for where you want the agent to go.

This worked beautifully! Thank you for taking the time to help me. My update method for the waypoint manager ended up looking something like this:

float step = travelSpeed * Time.deltaTime;
        if (Vector3.Distance(agent.currentPosition, agent.desiredPosition) < step)
        {
            if (waypointIndex < waypoints.Length)
            {
                agent.desiredPosition = waypoints[waypointIndex];
                waypointIndex++;
            }
        }

Also thank you to PraetorBlue for teaching me a few things about Coroutines.

2 Likes

Brilliant! That’s exactly what I was talking about.

Coroutines are awesome and they are very useful in certain circumstances. In this one not so much, although I suppose you could shenanigan it into place, it’s just not really suited. It’s kinda like you could hammer a nail in with a coroutine but it would probably be easier to use a hammer.