With enough hours swearing and crying, I though it would be best to share my solution, so that all of y’all can use this if you need it. This solution is based off of Sebastian Lague’s Path Creator, which videos of can be found on YouTube (as well as download links).
Step 1: Disable Lague’s default Endpoint Behavior
Lague did an excellent job making loop, reverse, and stop behavior using clamp01, pingpong, and the like, but we don’t need it. Instead, we will add a fourth option to EndOfPathInstruction, SwitchPath.
public enum EndOfPathInstruction {SwitchPath, Loop, Reverse, Stop};
This will allow the block to reach the end of the path.
Step 4: Add some helper functions
In vertexPath.cs, navigate to the Public methods and Actors field, where we will add the following two methods:
public float GetTAtDist(float dst, EndOfPathInstruction endOfPathInstruction = EndOfPathInstruction.SwitchPath)
{
float t = dst / length;
return t;
}
// Gets a distance value based on t value
public float GetDistAtT(float t, EndOfPathInstruction endOfPathInstruction = EndOfPathInstruction.SwitchPath)
{
float dist = t * length;
return dist;
}
While this functionality is already built into Lague’s system, it relies on the time value t to always be between 0 and 1, but we need to be able to tell when t is outside of those bounds.
Step 3: Add our own Endpoint Behavior
First, we will add a RailController.cs script to the current rail (or path) to store what the next and previous rails are to this one.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace PathCreation.Examples
{
public class RailController : MonoBehaviour
{
public GameObject previousRail;
public GameObject nextRail;
public GameObject NextRail()
{
if(nextRail != null)
{
return nextRail;
}
return null;
}
public GameObject PreviousRail()
{
if (previousRail != null)
{
return previousRail;
}
return null;
}
}
}
The game objects attached to this script should be the parent objects that Lague’s PathCreator script is attached to for the next and previous rail, respectively.
Next, in PathFollower.cs, we need to add:
public GameObject rail;
public RailController railController;
float railDist;
Note: railDist is being used to replace distanceTraveled, so that we can continue tracking the total distance the object has traveled. If this is not important to you, you can continue using distanceTraveled wherever you see railDist.
if (rail != null)
{
pathCreator = rail.GetComponent<PathCreator>();
railController = rail.GetComponent<RailController>();
}
Remember to initialize the path creator and rail controller in start().
In update(), we are going to want something that looks like this:
if (rail != null)
{
if (pathCreator != null)
{
//Total distance traveled
distanceTravelled += speed * Time.deltaTime;
//Distance on this particular rail
railDist += speed * Time.deltaTime;
transform.position = pathCreator.path.GetPointAtDistance(railDist, endOfPathInstruction);
transform.rotation = pathCreator.path.GetRotationAtDistance(railDist, endOfPathInstruction);
}
if (endOfPathInstruction == EndOfPathInstruction.SwitchPath)
{
float t = pathCreator.path.GetTAtDist(railDist, endOfPathInstruction);
// Goes to previous rail if you have backed off
if (t <= 0 && railController.PreviousRail())
{
// Updates the rail that the follower is on, as well as the pathcreator object
rail = railController.PreviousRail();
pathCreator = rail.GetComponent<PathCreator>();
railController = rail.GetComponent<RailController>();
// Puts the follower at the end of the next rail, rather than the beginning
railDist = pathCreator.path.GetDistAtT(1, endOfPathInstruction);
}
// Goes to next rail
else if (t >= 1 && railController.NextRail())
{
// Updates the rail that the follower is on, as well as the pathcreator object
rail = railController.NextRail();
pathCreator = rail.GetComponent<PathCreator>();
railController = rail.GetComponent<RailController>();
// Puts the follower at the beginning of the next rail, rather than the end
railDist = 0;
}
}
}
At this point, we should have the ability to move from one path to another (which don’t necessarily even need to touch), which at the moment is a little redundant, given that Lague has support for compound curves. However, the real treat is in…
Step 5
Now we have the infrastructure to create a short and simple SwitchController.cs. This gets attached to the base of the switch, or the segment before the split.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace PathCreation.Examples
{
public class SwitchController : MonoBehaviour
{
public bool nextRail = true;
public int target = 1;
public GameObject option1;
public GameObject option2;
private RailController rail;
void Update()
{
rail = gameObject.GetComponent<RailController>();
if (nextRail)
{
if (target == 1)
{
rail.nextRail = option1;
}
else
{
rail.nextRail = option2;
}
}
else
{
if (target == 1)
{
rail.previousRail = option1;
}
else
{
rail.previousRail = option2;
}
}
}
}
}
Option 1 and 2 are the game objects of the two rails you want the switch to switch between, and target is a simple int to change the rail selected, and is scale-able up to as many rails as you want per junction. Disable nextRail if the switch is a merger, so that going backwards it still acts like a switch.
With this, you should be done. If you find any errors with this (besides nullpointer errors, because I haven’t even started to weed those out), please let me know. Also, this code is horrendous. Any suggestions for improved readability or just simplification would be greatly appreciated!