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.
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.
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.