# How to loop my waypoint circuit

Hello,

I’m using the following waypoint circuit scripts which are making my game object move between two waypoints.

``````using System;
using System.Collections;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;

#endif

namespace UnityStandardAssets.Utility
{
public class WaypointCircuit : MonoBehaviour
{
public WaypointList waypointList = new WaypointList();
[SerializeField] private bool smoothRoute = true;
private int numPoints;
private Vector3[] points;
private float[] distances;

public float editorVisualisationSubsteps = 100;
public float Length { get; private set; }

public Transform[] Waypoints
{
get { return waypointList.items; }
}

//this being here will save GC allocs
private int p0n;
private int p1n;
private int p2n;
private int p3n;

private float i;
private Vector3 P0;
private Vector3 P1;
private Vector3 P2;
private Vector3 P3;

// Use this for initialization
private void Awake()
{
if (Waypoints.Length > 1)
{
CachePositionsAndDistances();
}
numPoints = Waypoints.Length;
}

public RoutePoint GetRoutePoint(float dist)
{
// position and direction
Vector3 p1 = GetRoutePosition(dist);
Vector3 p2 = GetRoutePosition(dist + 0.1f);
Vector3 delta = p2 - p1;
return new RoutePoint(p1, delta.normalized);
}

public Vector3 GetRoutePosition(float dist)
{
int point = 0;

if (Length == 0)
{
Length = distances[distances.Length - 1];
}

dist = Mathf.Repeat(dist, Length);

while (distances[point] < dist)
{
++point;
}

// get nearest two points, ensuring points wrap-around start & end of circuit
p1n = ((point - 1) + numPoints)%numPoints;
p2n = point;

// found point numbers, now find interpolation value between the two middle points

i = Mathf.InverseLerp(distances[p1n], distances[p2n], dist);

if (smoothRoute)
{
// smooth catmull-rom calculation between the two relevant points

// get indices for the surrounding 2 points, because
// four points are required by the catmull-rom function
p0n = ((point - 2) + numPoints)%numPoints;
p3n = (point + 1)%numPoints;

// 2nd point may have been the 'last' point - a dupe of the first,
// (to give a value of max track distance instead of zero)
// but now it must be wrapped back to zero if that was the case.
p2n = p2n%numPoints;

P0 = points[p0n];
P1 = points[p1n];
P2 = points[p2n];
P3 = points[p3n];

return CatmullRom(P0, P1, P2, P3, i);
}
else
{
// simple linear lerp between the two points:

p1n = ((point - 1) + numPoints)%numPoints;
p2n = point;

return Vector3.Lerp(points[p1n], points[p2n], i);
}
}

private Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float i)
{
// comments are no use here... it's the catmull-rom equation.
// Un-magic this, lord vector!
return 0.5f*
((2*p1) + (-p0 + p2)*i + (2*p0 - 5*p1 + 4*p2 - p3)*i*i +
(-p0 + 3*p1 - 3*p2 + p3)*i*i*i);
}

private void CachePositionsAndDistances()
{
// transfer the position of each point and distances between points to arrays for
// speed of lookup at runtime
points = new Vector3[Waypoints.Length + 1];
distances = new float[Waypoints.Length + 1];

float accumulateDistance = 0;
for (int i = 0; i < points.Length; ++i)
{
var t1 = Waypoints[(i)%Waypoints.Length];
var t2 = Waypoints[(i + 1)%Waypoints.Length];
if (t1 != null && t2 != null)
{
Vector3 p1 = t1.position;
Vector3 p2 = t2.position;
points[i] = Waypoints[i%Waypoints.Length].position;
distances[i] = accumulateDistance;
accumulateDistance += (p1 - p2).magnitude;
}
}
}

private void OnDrawGizmos()
{
DrawGizmos(false);
}

private void OnDrawGizmosSelected()
{
DrawGizmos(true);
}

private void DrawGizmos(bool selected)
{
waypointList.circuit = this;
if (Waypoints.Length > 1)
{
numPoints = Waypoints.Length;

CachePositionsAndDistances();
Length = distances[distances.Length - 1];

Gizmos.color = selected ? Color.yellow : new Color(1, 1, 0, 0.5f);
Vector3 prev = Waypoints[0].position;
if (smoothRoute)
{
for (float dist = 0; dist < Length; dist += Length/editorVisualisationSubsteps)
{
Vector3 next = GetRoutePosition(dist + 1);
Gizmos.DrawLine(prev, next);
prev = next;
}
Gizmos.DrawLine(prev, Waypoints[0].position);
}
else
{
for (int n = 0; n < Waypoints.Length; ++n)
{
Vector3 next = Waypoints[(n + 1)%Waypoints.Length].position;
Gizmos.DrawLine(prev, next);
prev = next;
}
}
}
}

[Serializable]
public class WaypointList
{
public WaypointCircuit circuit;
public Transform[] items = new Transform[0];
}

public struct RoutePoint
{
public Vector3 position;
public Vector3 direction;

public RoutePoint(Vector3 position, Vector3 direction)
{
this.position = position;
this.direction = direction;
}
}
}
}

namespace UnityStandardAssets.Utility.Inspector
{
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof (WaypointCircuit.WaypointList))]
public class WaypointListDrawer : PropertyDrawer
{
private float lineHeight = 18;
private float spacing = 4;

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);

float x = position.x;
float y = position.y;
float inspectorWidth = position.width;

// Draw label

// Don't make child fields be indented
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;

var items = property.FindPropertyRelative("items");
var titles = new string[] {"Transform", "", "", ""};
var props = new string[] {"transform", "^", "v", "-"};
var widths = new float[] {.7f, .1f, .1f, .1f};
float lineHeight = 18;
bool changedLength = false;
if (items.arraySize > 0)
{
for (int i = 0; i < items.arraySize; ++i)
{

float rowX = x;
for (int n = 0; n < props.Length; ++n)
{
float w = widths[n]*inspectorWidth;

// Calculate rects
Rect rect = new Rect(rowX, y, w, lineHeight);
rowX += w;

if (i == -1)
{
EditorGUI.LabelField(rect, titles[n]);
}
else
{
var item = items.GetArrayElementAtIndex(i);
if (n == 0)
{
EditorGUI.ObjectField(rect, item.objectReferenceValue, typeof (Transform), true);
}
else
{
if (GUI.Button(rect, props[n]))
{
switch (props[n])
{
case "-":
items.DeleteArrayElementAtIndex(i);
items.DeleteArrayElementAtIndex(i);
changedLength = true;
break;
case "v":
if (i > 0)
{
items.MoveArrayElement(i, i + 1);
}
break;
case "^":
if (i < items.arraySize - 1)
{
items.MoveArrayElement(i, i - 1);
}
break;
}
}
}
}
}

y += lineHeight + spacing;
if (changedLength)
{
break;
}
}
}
else
{
var addButtonRect = new Rect((x + position.width) - widths[widths.Length - 1]*inspectorWidth, y,
widths[widths.Length - 1]*inspectorWidth, lineHeight);
{
items.InsertArrayElementAtIndex(items.arraySize);
}

y += lineHeight + spacing;
}

var addAllButtonRect = new Rect(x, y, inspectorWidth, lineHeight);
if (GUI.Button(addAllButtonRect, "Assign using all child objects"))
{
var circuit = property.FindPropertyRelative("circuit").objectReferenceValue as WaypointCircuit;
var children = new Transform[circuit.transform.childCount];
int n = 0;
foreach (Transform child in circuit.transform)
{
children[n++] = child;
}
Array.Sort(children, new TransformNameComparer());
circuit.waypointList.items = new Transform[children.Length];
for (n = 0; n < children.Length; ++n)
{
circuit.waypointList.items[n] = children[n];
}
}
y += lineHeight + spacing;

// rename all button
var renameButtonRect = new Rect(x, y, inspectorWidth, lineHeight);
if (GUI.Button(renameButtonRect, "Auto Rename numerically from this order"))
{
var circuit = property.FindPropertyRelative("circuit").objectReferenceValue as WaypointCircuit;
int n = 0;
foreach (Transform child in circuit.waypointList.items)
{
child.name = "Waypoint " + (n++).ToString("000");
}
}
y += lineHeight + spacing;

// Set indent back to what it was
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
SerializedProperty items = property.FindPropertyRelative("items");
float lineAndSpace = lineHeight + spacing;
return 40 + (items.arraySize*lineAndSpace) + lineAndSpace;
}

// comparer for check distances in ray cast hits
public class TransformNameComparer : IComparer
{
public int Compare(object x, object y)
{
return ((Transform) x).name.CompareTo(((Transform) y).name);
}
}
}
#endif
}
``````
``````using System;
using UnityEngine;

namespace UnityStandardAssets.Utility
{
public class WaypointProgressTracker : MonoBehaviour
{
// This script can be used with any object that is supposed to follow a
// route marked out by waypoints.

// This script manages the amount to look ahead along the route,
// and keeps track of progress and laps.

[SerializeField] private WaypointCircuit circuit; // A reference to the waypoint-based route we should follow

[SerializeField] private float lookAheadForTargetOffset = 5;
// The offset ahead along the route that the we will aim for

[SerializeField] private float lookAheadForTargetFactor = .1f;
// A multiplier adding distance ahead along the route to aim for, based on current speed

[SerializeField] private float lookAheadForSpeedOffset = 10;
// The offset ahead only the route for speed adjustments (applied as the rotation of the waypoint target transform)

[SerializeField] private float lookAheadForSpeedFactor = .2f;

[SerializeField] private ProgressStyle progressStyle = ProgressStyle.SmoothAlongRoute;
// whether to update the position smoothly along the route (good for curved paths) or just when we reach each waypoint.

[SerializeField] private float pointToPointThreshold = 4;
// proximity to waypoint which must be reached to switch target to next waypoint : only used in PointToPoint mode.

public enum ProgressStyle
{
SmoothAlongRoute,
PointToPoint,
}

// these are public, readable by other objects - i.e. for an AI to know where to head!
public WaypointCircuit.RoutePoint targetPoint { get; private set; }
public WaypointCircuit.RoutePoint speedPoint { get; private set; }
public WaypointCircuit.RoutePoint progressPoint { get; private set; }

public Transform target;

private float progressDistance; // The progress round the route, used in smooth mode.
private int progressNum; // the current waypoint number, used in point-to-point mode.
private Vector3 lastPosition; // Used to calculate current speed (since we may not have a rigidbody component)
private float speed; // current speed of this object (calculated from delta since last frame)

// setup script properties
private void Start()
{
// we use a transform to represent the point to aim for, and the point which
// is considered for upcoming changes-of-speed. This allows this component
// to communicate this information to the AI without requiring further dependencies.

// You can manually create a transform and assign it to this component *and* the AI,
// then this component will update it, and the AI can read it.
if (target == null)
{
target = new GameObject(name + "Waypoint Target").transform;
}

Reset();
}

// reset the object to sensible values
public void Reset()
{
progressDistance = 0;
progressNum = 0;
if (progressStyle == ProgressStyle.PointToPoint)
{
target.position = circuit.Waypoints[progressNum].position;
target.rotation = circuit.Waypoints[progressNum].rotation;
}
}

private void Update()
{
if (progressStyle == ProgressStyle.SmoothAlongRoute)
{
// determine the position we should currently be aiming for
// (this is different to the current progress position, it is a a certain amount ahead along the route)
// we use lerp as a simple way of smoothing out the speed over time.
if (Time.deltaTime > 0)
{
speed = Mathf.Lerp(speed, (lastPosition - transform.position).magnitude/Time.deltaTime,
Time.deltaTime);
}
target.position =
.position;
target.rotation =
Quaternion.LookRotation(
.direction);

// get our current progress along the route
progressPoint = circuit.GetRoutePoint(progressDistance);
Vector3 progressDelta = progressPoint.position - transform.position;
if (Vector3.Dot(progressDelta, progressPoint.direction) < 0)
{
progressDistance += progressDelta.magnitude*0.5f;
}

lastPosition = transform.position;
}
else
{
// point to point mode. Just increase the waypoint if we're close enough:

Vector3 targetDelta = target.position - transform.position;
if (targetDelta.magnitude < pointToPointThreshold)
{
progressNum = (progressNum + 1)%circuit.Waypoints.Length;
}

target.position = circuit.Waypoints[progressNum].position;
target.rotation = circuit.Waypoints[progressNum].rotation;

// get our current progress along the route
progressPoint = circuit.GetRoutePoint(progressDistance);
Vector3 progressDelta = progressPoint.position - transform.position;
if (Vector3.Dot(progressDelta, progressPoint.direction) < 0)
{
progressDistance += progressDelta.magnitude;
}
lastPosition = transform.position;
}
}

private void OnDrawGizmos()
{
if (Application.isPlaying)
{
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, target.position);
Gizmos.DrawWireSphere(circuit.GetRoutePosition(progressDistance), 1);
Gizmos.color = Color.yellow;
Gizmos.DrawLine(target.position, target.position + target.forward);
}
}
}
}
``````

So the gameobject moves from the first waypoint to the second and then returns to the first one and so on. I would like to make this process loop in a different way. When the game object reaches the last waypoint, I want to make it immediately spawn at the first waypoint again, then walk to the last one, then spawning at the first one again, etc. Does somebody have any suggestions on how to solve this?

This code seems to be missing whatever code actually sets the object’s position to somewhere in the waypoints. It looks like you can just provide a different input to GetRoutePoint or GetRoutePosition.

It definitely looks like there is no mechanism in this code to detect the end of the route. (That is, if I call GetRoutePosition(99999f), it looks like line 73 will just trigger an out of range exception) So presumably that mechanism must be on whatever is calling this. Whatever is calling this, just needs to not send the numbers that are associated with the final “leg” of the waypoints where it returns to the start.

Incidentally, I could be missing it in this absolute rat’s nest, but it doesn’t look like this code actually does do any automatic “return to start” on the waypoints, which means the last waypoint must just be the first waypoint again; surely you can just remove that?

Ok, thanks. And what input should this be?