Game Jams How I developed a dynamically generated track
Game Jam Series 1, track design
_This is from a series of post mortems I'll be doing on my game from the Ludum Dare. _
For my game, I wanted to make something with racing. I was a little fixated on that, since I made an arcade racer with the theme sort of shoehorned in. The hardest part for me was figuring out how to make tracks, manage progress on the track, record lap times, etc. Fortunately, I had a really good place to start.
I watched this video from Sebastian Lange years ago and was enamored. The ability to dynamically generate road like curves on the fly in unity was exciting, because at the time, I really had no idea how to make roads for a racing game in unity other than hand make it. This showed that it was possible to do all sorts of fun designs and dynamic tracks. I wrote code that used his Unity plugin several years ago and made a little demo that allowed me to generate tracks from a JSON file.
It's great and all to make a mesh that looks like a track, but there are no rules for how that road should work. How do you know if you're cutting corners? how do you know if you completed a lap?
Checkpoints
I updated the code from Lange's generator to add a game object along the path created. I created a prefab with a trigger box collider and fence pieces and named them iteratively. ![[/blog-photos/ld55-1.png]]
This created a list of trigger box colliders along the route that I could then use to check the user's progress along the track. I track the player's progress using an OnTriggerEnter function.
void Generate()
{
if (pathCreator != null && prefab != null && holder != null)
{
DestroyObjects();
VertexPath path = pathCreator.path;
spacing = Mathf.Max(minSpacing, spacing);
float dst = 0;
int cout = 0;
while (dst < path.length)
{
Vector3 point = path.GetPointAtDistance(dst);
Quaternion rot = path.GetRotationAtDistance(dst);
var temp = Instantiate(prefab, point, rot, holder.transform);
if (isFromFile)
{
temp.transform.localScale = new Vector3(1, trackFromFile.roadWidth / 2, 1);
temp.gameObject.name = cout.ToString();
if (cout == 0)
{
var finish = Instantiate(finishLine, temp.transform.position, temp.transform.rotation, holder.transform);
finish.transform.localScale = new Vector3(0.02f, trackFromFile.roadWidth * 2, 0.2f);
}
}
dst += spacing;
cout++;
TotalCheckpoints = cout;
}
}
}
With the following script, the player can't progress unless they hit each trigger sequentially. In the future, it might be more fair to give a time penalty instead, but with only 48 hours, making the player hit every single one was all I could really do.
private void OnTriggerEnter(Collider other)
{
int checkpointName = int.Parse(other.gameObject.name);
if (currentCheckpoint == placer.getTotal())
{
//lap completed
Debug.Log("Lap Completed");
nextCheckpoint = 1;
currentCheckpoint = 0;
RecordLapTime();
}
if (checkpointName + 1 == nextCheckpoint)
{
nextCheckpoint++;
currentCheckpoint++;
Debug.Log("next: " + nextCheckpoint + " current: " + currentCheckpoint);
}
if (checkpointName == 0 && !startTimer)
{
startTimer = true;
}
}
This was how I created the tracks for the game. I inputted a JSON file with the coordinates for the bezier curve, drew the mesh, installed checkpoints, and viola! A racetrack.
If you like our content, please consider subscribing to our weekly newsletter. I'm biased, but it's pretty great.Sign Up