[Tutorial] – Create a Procedurally Generated Terrain for a 2D Infinite Runner Game in Unity


Have you ever been thinking of creating infinite runner game with the Unity? This kind of games can be quite challenging even for experienced developers. One of the things you most probably want to have in your game, is a procedurally generated terrain. Here we will show you how to achieve such feature using the Unity game engine.

Tiny Wings for iOS

Tiny Wings for iOS

The theory

We will write a script that will split the level to segments. Each segment is a constant size object including a Mesh. When the camera is about to render a segment, the Mesh is generated and set on the target position. Segment that is no longer visible is released back into the pool.

Sounds simple? Then let’s get started…

Set up

First, we will need a prefab with Mesh Filter and Mesh Renderer. We will use these to render segments.

Mesh Renderer and Mesh Filter prefab.

Mesh Renderer and Mesh Filter prefab.

Now let’s start writing our script. Let’s call it MeshGen.

Next, when the script is awaken, we want to initialize its fields and build the mesh pool. We’re using a pool to minimize the garbage collection.

One thing that definitely needs an explanation is a triangle order thing. You can see that it is kind of messed up. For unity meshes you need to define triangle indices. These indices are the indices of vertices passed just before. Each triangle has three vertices. There are two ways you can pass these vertices – clockwise or counter-clockwise. Since most Unity built-in shaders (and any shaders) are rendering counter-clockwise ordered triangles, and discarding (culling) clockwise ordered triangles, we have to follow the rule.


Here’s an example of 4-vertices shape. It can be displayed using two triangles. If vertices are defined as above (0, 1, 2, 3 in order), then the triangles should be defined as follows:

  • 0-2-1 (alternatives: 2-1-0 or 1-2-0)
  • 3-1-2 (alternatives: 1-2-3 or 2-3-1)

Height function

What we will need is a height function. This should be a Pure Function and can be modified freely to get different interesting results. For our case we made a combination of two sine functions.

Generating segment function

When we have the height function, we need a function that will generate a mesh based on the returned value.

We’re computing as many vertices as defined by SegmentResolution field value. Also, we’re using _vertexArray,  because it is already allocated and it is not used by any other object (assigning the array to the mesh will copy it instead of passing the reference, but this does not generate any garbage). Vertex positions are relative, but the position passed to GetHeight() must be absolute.

Checking if segment is seeable by the camera

You have to check if segment is about to be rendered by the camera. This is done using this method:

Storing data about visible segments

If a segment will be displayed, we have to store this information in some way. We will need segment index and also we need to know what MeshFilter has been used to draw that segment. Then, we can put it back into the pool when the segment is no longer visible. We will create a helper struct within MeshGen class:

Then, within the MeshGen class there will be one more private field:

We’re using struct instead of class because creating new struct does not generate any garbage.

Checking if segment is currently visible

We will need to check if a segment is currently visible, so we don’t use more than one MeshFilters to render the single segment.

The name of SegmentCurrentlyVisibleListIndex can be a little confusing. It’s looking for a segment of given index, and if found, it returns an index of this segment within _usedSegments list.

Making the segment visible

Now, it’s the most important part, making the segment visible! To do this, we created the EnsureSegmentVisible() method. It takes segment index and makes sure that given segment index will be visible after executing this method. If this segment is already visible, it does nothing.

Hiding the segment

When the segment is no longer visible by the camera, it should be removed and the MeshFilter should be given back to the pool. We did that with  EnsureSegmentNotVisible() method. It is opposite of the previous method.

Connecting it all

Now, the sweet part. The update function! It should hide all the segments that are no longer visible and display segments that should be visible. The order is important here, because otherwise we can run out of free MeshFilters.

Procedurally generated terrain result

It is working? Let’s move the camera position and check it out.

generated terrain gif

The package

Here you can download the unitypackage suitable for Unity 5.3.1 and above. Feel free to modify it to your needs! If you have any questions, please add those as a comment to this post. We will be more than happy to help you!

5 thoughts on “[Tutorial] – Create a Procedurally Generated Terrain for a 2D Infinite Runner Game in Unity

    • Piotr Korzuszek says:

      That wouldn’t work in this case. The method IsSegmentInSight() is testing if segment should be visible by the camera. Segments are pure virtual thing and if I am testing for a segment that is not in the position yet, I won’t get any callback from OnBecameVisible().

      There’s a possibility to change the code to use OnBecameVisible() and OnBecameInvisible() though. To use those, I would need to create one extra segment on the right and one extra segment on the left, so if any of these will became visible I will know that I need to create another extra segment on that side. If two segments will became invisible on any side then I can remove the far segment, but still I would need to leave the second invisible segment.

  1. Steve says:

    Hi friends, how can i add a 2d sprite car onto the mesh genereated?

    ie how can i add a car to the terrain?

    Thanks and keep up the great work!!

  2. Michael Zhao says:

    Thanks for this tutorial! I was wondering how you can add a polygon collider to these segments?

    Thank you,

Leave a Reply

Your email address will not be published. Required fields are marked *