Procedural Racetrack Generation
Introduction
Problem Statement
Designing racetracks can be a time-consuming activity. Game developers who want their players to have a unique experience every time they play would need to invest a significant amount of time in designing different layouts. A solution to this issue could be the use of procedural generation, a technique that allows for creating designs in seconds. This approach can help save time while ensuring that each track is unique and dynamic, offering a fresh experience every time.
Research Question
How can procedural content generation be used to generate racetracks on demand with dynamic components like barriers, corner types, and scenery?
Defining Track Requirements
To procedurally generate a racetrack, the first step is to establish the requirements that define the core structure of the track. These parameters will dictate the shape, complexity, and challenge of the generated track. Important track requirements include:
- Number of corners: The track should support a variable number of corners, from gentle curves to sharp turns.
- Track length: Total length of the track can be adjusted, which will influence the number of straight sections and curves.
- Corner sharpness/steepness: Corners should vary in intensity, offering both high-speed sweeping turns and tight, technical bends.
- Elevation changes: If desired, the track should include height variations, adding complexity to both the layout and driving experience.
- Track width: The width of the track can vary, depending on the type of race (e.g., wide tracks for F1, narrow tracks for rally).
The main goal is to make tracks that represent FIA grade quality, meaning that these are racetrack which in theory could be used for supporting classes in formula racing.

Initial Approach: Random points track generation
When experimenting with the generation of racetracks, I started with looking into the generation of random points which would later be connected by a line. This initial idea came from brainstorming and drawing in Paint. Later on I found resources like the article from Juangallostra, where they use a random set of points which are connected with a Convex Hull. They add some more restrictions and parameters to give the track more characteristics.
When implementing this I found a crucial problem for my project, the track crosses over itself. And in a lot of arcade racers or games where the focus is way more on other things than the true racing this would be fine. But because I really wanted to make a FIA graded circuit which is realistic, this is strictly forbidden.
I have tried to use different forms of validation whether the generated track would cross over itself but this left me incredibly sensitive for lack of computer power. Checking every frame for every bit of generated mesh if it collides with another piece is very very expensive.

| Description | Random set of points with a Convex Hull |
|---|---|
| Pros | - Easy to set up - Good for an arcade racer |
| Cons | - Little customization - Track that crosses over itself - Lack of control over the points |
Second Approach: Random points with boundaries
After I had some poor results with the first method, I tried to use some other technique used in this article by Gustavo Maciel. He uses a very similar approach but instead of just always drawing the convex hull, he checks whether points are too close right after they have been generated. Whenever two or more points are too close they are pushed out and away from each other.
When implementing this seemed like a good way of generating my racetrack. When implementing I got some descent results, but the one thing stopping me from exploiting this further was the fact that the algorithm which pushes points away from each other. It was really hard for me to keep control on the direction these points are pushed towards. I spent some time adding parameters to keep this from happening and to keep some logical patterns in the way the track flowed corners into one another, but this lead into a lot of problems.
Because there were so many parameters at some point, it was incredibly difficult to makle adjustments and to keep the program from messing up and throwing errors.
| Description | Random set of points with boundaries |
|---|---|
| Pros | - More control than without the boundaries - Possibilities for customization |
| Cons | - Will get overcomplicated quickly - Does not solve my problem of the track crossing over |
Third Approach: Point generation from scratch
At this point in time, right around the 3,5 week mark I was really lacking behind in my product. I had quite some knowledge regarding the possibilities of generating racetracks. But because the previous two options did not give me much to work with, I tried to simply start completely from scratch and try and make an algorithm with every I needed from this track generating program.
I started out with a random point, which had no parameters except for the fact that it should spawn within the desired area in which the full track should be placed. After the initial point I started drawing points from the last generated point. Using this, and a lot of other parameters I was able to draw some pretty nice tracks, and because I was able to control the angle that the point had in comparison to the previous point I could make sure that there would be no overlap in track segments… is what I hoped I could say. I am pretty certain that with the right knowledge and skill one is able to accomplish an algorithm that is able to perform the way I intended it to. But for me it was way to complicated and it led to more errors than there were lines in my code.
The calculations I was trying to make in order to prevent the crossing over got so complicated that there were an almost infinite amount of nested if-statements. My experience about this technique is that there are so many possibilities that an experienced programmer can absolutely make a very nice algorithm, but with my skill it was just too much.
| Description | Point generation from scratch |
|---|---|
| Pros | - Incredible level of customization - A lot of potential |
| Cons | - Very difficult to set up - Too complicated for my level - Difficult to find recources to help |
Final Approach: Custom circle generation
At this point in time it has been a few weeks since the start of the project and I am starting to seriously lack in my product-side of this 5 week challenge. When looking into articles regarding random scattering and brainstorming with a Guild mate, we got an idea on making my own algorithm to generate these tracks.
The problem was almost everytime that the track was crossing over itself. And in other solutions they kept trying to change the points. But we had a completely different perspective on this problem. Instead of changing the points’ position after the generation, we prevent the problem from ever happening.
We thought of it as a circle, and because the track always has to end at the start this was an easy solution to tackle the problem of having an awkward angle from the last corner to the first.
This circle should be generated, and the amount of corners should “cut” the circle into segments. With each segment having its own space where no other point can be generated made sure that it was impossible for the track to run back over itself, and it would make sure that there would always be a perfect loop.

With random scattering from this article from Mick West in mind, which allows for the randomness of points within the assigned space.
If we take a look at the sketch that I made in Paint, we can see that corner number 1 can be randomly placed anywhere in the red area. If you were to do this for every corner, you would get a random track which is always a perfect loop and will never cross over itself. To prevent some extreme angles I added a parameter to keep the point at a certain distance from the middle point.
The algorithm works rather simple, in comparison to what I initially thought it would be.
I start out by defining the angleStep variable, which is just 360 divided by the number of points. This represents the angle at which each respective point should spawn.
for (int i = 0; i < numberOfPoints; i++)
| In a simple for loop I give all of the points their random place within their slice.
float startAngle = (i * angleStep) * Mathf.Deg2Rad;
float endAngle = ((i + 1) * angleStep) * Mathf.Deg2Rad;
float randomAngle = Random.Range(startAngle, endAngle);
// Random radius within the slice
float randomRadius = Random.Range(0, radius);
// Random position within the slice
randomPosition = new Vector3(Mathf.Cos(randomAngle) * randomRadius, 0, Mathf.Sin(randomAngle) * randomRadius);
| Description | Custom circle generation |
|---|---|
| Pros | - Quite easy to set up - Always a perfect loop - Will never cross over itself |
| Cons | - Track shapes will always be pretty similar - Has its limitations in the amount of corners - Difficult to add personality to this algorithm |
Adaptive Gravel Traps
Once I had a consistent method for generating track points and layout, I focused on adding adaptive gravel traps around the corners. The goal was for these gravel traps to dynamically adjust in size and shape based on the severity of each corner. For example, sharper turns would have larger gravel traps on the outside of the curve, while gentle bends would have minimal gravel coverage.
To achieve this, I used the angle between each pair of connected points to determine the corner’s sharpness. Based on this angle, the gravel trap’s stretch factor was adjusted to ensure larger coverage for sharper turns. To generate a mesh for the gravel traps I drawed the mesh for the main track, and stretched it dynamically with the corners. So I basically used it as a “cover” for the track. With more and less stretch based on the sharpness of the corner.
Here’s an example of how I created segments of gravel traps, using different techniques for sharp and gentle turns:
Determine Corner Sharpness: Calculate the angle between consecutive points to identify sharp turns. This allows us to scale gravel traps according to corner intensity.
float angle = Vector3.Angle(dirToPrev, dirToNext); float stretchFactor = Mathf.Lerp(minStretch, maxStretch, angle / 90.0f);Here,
anglerepresents the sharpness of the corner, andstretchFactordetermines how much the gravel should expand at that corner, based on the angle.Circular Approximation for Sharp Corners: For sharper corners (angles exceeding a threshold), a circular pattern of gravel points is used to achieve a rounded effect around the corner.
if (angle > 45.0f) // Sharp corner { List<Vector3> circularPoints = GenerateCircularPoints(prevPoint, currentPoint, nextPoint, stretchFactor); // Generate gravel trap segments along these points }The function
GenerateCircularPointscreates points in a circular pattern around sharp corners, creating a natural, rounded gravel trap.Bezier Curves for Smooth Curves: For smoother transitions (less sharp curves), Bezier curves are used to interpolate the gravel points, ensuring continuity and smoothness.
else { List<Vector3> bezierPoints = GenerateSmoothBezierCurve(prevPoint, currentPoint, nextPoint, stretchFactor); // Generate gravel trap segments along these points }
By dynamically adjusting gravel coverage in this way, the final track has a natural look with gravel traps that enhance the racetrack’s realism and functionality. As seen in the image I did not manage to generate super smooth gravel traps but was able to generate these dynamically around the track. With emphasis on certain corners.

Sources
https://catlikecoding.com/unity/tutorials/curves-and-splines/
https://medium.com/geekculture/random-walk-script-in-unity-c53c1f39dbc2
https://www.gamedeveloper.com/business/how-to-work-with-bezier-curve-in-games-with-unity
https://www.gamedeveloper.com/business/random-scattering-creating-realistic-landscapes
https://www.statox.fr/posts/2021/10/race_generator/
https://github.com/juangallostra/procedural-tracks
https://www.geeksforgeeks.org/convex-hull-using-divide-and-conquer-algorithm/
https://www.geeksforgeeks.org/convex-hull-algorithm/
https://www.gamedeveloper.com/programming/generating-procedural-racetracks