Infinite Roller Tutorial Seven – Random Terrain Height

Welcome to Edge of Code’s series of Infinite Roller tutorials. If you’d prefer a video tutorial, you can find one here.

In the previous tutorial, we created a menu using Unity’s UI system. This time, we’ll randomise the height of the terrain.

We could randomise the height of the terrain in various ways. We could vary the height of each piece of ground by a small amount to create a step like effect. We could randomise the height of each chunk so that each block of ground is at the same random height. Because of how we’ve created gaps in the terrain, this would mean each pair of visible ground pieces within a chunk would be at the same height. Instead of this, I’m going to write code to pick a different random height for each visible section within a chunk, and use the height of the second section as the height of the start of the next. To see what I mean more clearly, see the pictures below.

Unity tutorial 2d random infinite terrain diagram

Unity tutorial 2d random infinite terrain generation diagram

Open your Infinite Roller project and the scene, then open the Chunk script. Create a new method to randomise the height of a section of ground pieces

/** randomise the height of a section of pieces */
private int RandomiseSectionHeight(int currentRandomHeight)
{
}

The method takes the currentRandomHeight variable and returns the next random height, also an int. Before we continue with this method, create a new class variable

private int gapEndPosition;

that stores the position of the end of the gap in the ground pieces. Set this inside CreateGaps() after the first two lines so that

int numGroundToDeactivate = Random.Range (MIN_GAP_SIZE, MAX_GAP_SIZE);
int deactivatePosition = Random.Range (MIN_GAP_POSITION, MAX_GAP_POSITION);
		
gapEndPosition = numGroundToDeactivate + deactivatePosition;

The IsGap method uses this value, so replace numGroundToDeactivate + deactivatePosition with gapEndPosition in it. We no longer need to pass in numGroundToDeactivate, so remove it from IsGap:

private bool IsGap(int childIndex, int deactivatePosition)
{
	bool isGap = childIndex >= deactivatePosition && childIndex < gapEndPosition;
	
	return isGap;
} 

and from where IsGap is called in CreateGaps().

Inside the new RandomiseSectionHeight method, loop through the chunk’s child gameobjects

for (int i=0; i<CHUNK_SIZE; i++)
{
	//get the ith child
	Transform child = transform.GetChild(i);
}

Remember, each child is a ground piece. Next, add the following inside the for loop, after the existing code

//if the child's position is before the end of the gap, set it to currentRandomHeight
if (i < gapEndPosition)
{
	SetChildHeight(child, currentRandomHeight);
}

As the child is the ith one, we first check if it is located before gapEndPosition. If it is, we want to set the child’s height to be the currentRandomHeight variable that’s been passed into the method. This is the height of the previous chunk’s second/final visible section. Create the SetChildHeight method.

/* Set the height of the child*/
private void SetChildHeight(Transform child, int yPos)
{
	Vector3 newPosition = new Vector3 (child.position.x, yPos, child.position.z); 
	child.position = newPosition;
}

We can’t just set the y position using something like child.position.y = yPos; because the x, y and z components of the position cannot be set from outside the Vector3 class. Instead, we have to create a new position with the x and z values that the child already has, but with a new y value, and then assign that to the child’s position.

Next, we want to get a new random section height for the rest of the chunk (and the start of the next one). Create a new variable at the top of the RandomiseSectionHeight method

int nextRandomHeight = GetRandomSectionHeight(currentRandomHeight);

Then create the new method

/** randomise the height of a section of pieces */
private int GetRandomSectionHeight(int currentRandomHeight)
{
}

And create two new const class variables

private const int HEIGHT_LOWER_BOUND = 7;
private const int HEIGHT_UPPER_BOUND = 15;

These give an upper and lower bound to the height we will add or take away from the currentRandomHeight. Inside the GetRandomSectionHeight method, add

//set the height. Make sure it's not too different from the previous height to ensure player can jump it
int nextRandomHeight = currentRandomHeight + Random.Range (HEIGHT_LOWER_BOUND, HEIGHT_UPPER_BOUND);

We use Random.Range to get a random number between the lower and upper bounds. We want the new height to have the possibility of being either higher or lower that the currentRandomHeight, so add the following above this:

int sign = 1;
if (Random.value > 0.5)
{
	sign = -1;
}

The sign variable starts off with a value of 1. Then we use the Random class’s value variable which gives a value between 0 and 1. If it’s more than 0.5, we make sign negative. Otherwise, it stays positive. Multiply the new random part of nextRandomHeight by sign so that it becomes

int nextRandomHeight = currentRandomHeight + sign*Random.Range (HEIGHT_LOWER_BOUND, HEIGHT_UPPER_BOUND);

To make sure the ground doesn’t move too high up or down, add

//clamp the values to make sure it doesn't go too high up/down. This means can use a constant value to check if game is over in GameController
nextRandomHeight = Mathf.Clamp(nextRandomHeight, -RANDOM_HEIGHT_CLAMP_VAL, RANDOM_HEIGHT_CLAMP_VAL);

The Clamp method in Mathf clamps the value of nextRandomHeight between +/- RANDOM_HEIGHT_CLAMP_VAL. Using this stops the ground from moving too far up or down, and means we can use a constant value to check if the character has fallen through a gap, and the game should be over. Now create the corresponding class variable

//clamp the height at this value. 
private const int RANDOM_HEIGHT_CLAMP_VAL = 14;

At the end of the GetRandomSectionHeight method, return the now clamped nextRandomHeight variable,

return nextRandomHeight;

Unity tutorial 2d random infinite terrain section height

Go back to the RandomiseSectionHeight method. Now that we have the nextRandomHeight, set it as the height of the children after the gap by adding an else to the if statement:

//if the child's position is before the end of the gap, set it to currentRandomHeight
if (i < gapEndPosition)
{
	SetChildHeight(child, currentRandomHeight);
}
//when reach the end of the gap, use the new random height instead
else
{
	SetChildHeight(child, nextRandomHeight);
}

Create another new method,

/* Randomise a section */
public int RandomiseSection(int currentRandomHeight)
{
	//first create gaps
	CreateGaps();

	//then randomise heights
	int nextRandomHeight = RandomiseSectionHeight(currentRandomHeight);

	return nextRandomHeight;
}

This is public because we need to call it from TerrainGenerator. We always need to create the gap first, before setting the height, because gapEndPosition is set in CreateGaps(). Open TerrainGenerator and create a new class variable

private int currentRandomHeight = 0;

We need to keep track of the current height of the ground pieces so that it can be used for the next chunk’s height to be calculated. It’s initialised to zero as that’s the initial height we want. Next, replace the nextChunk.CreateGaps(); line in start with

//create random gaps and randomise the height
currentRandomHeight = nextChunk.RandomiseSection(currentRandomHeight);

Do the same thing in the Update method: replace the nextChunk.CreateGaps(); line with

//randomise the next chunk's gaps and height
currentRandomHeight = nextChunk.RandomiseSection(currentRandomHeight);

So we are passing in currentRandomHeight, and getting back the next one, which becomes the new currentRandomHeight.

Unity tutorial 2d random infinite terrain generator

Save both scripts and go back to Unity. Press play to check everything works correctly. There are a few things to change. First, we need to alter the game over height, so open the GameController script and change GAME_OVER_HEIGHT to -15. Save, and go back to Unity to test it. When the game is over, the heights of the terrain are not reset, so go to the TerrainGenerator script and create a new method

/* Reset each child section*/
private void RepositionSections()
{
}

Inside, first reset the positions of each chunk (we’ll create the ResetChildPositions method in a moment)

previousChunk.ResetChildPositions();
currentChunk.ResetChildPositions();
nextChunk.ResetChildPositions();

We also need to reset the currentRandomHeight variable and randomise nextChunk:

//reset the currentRandomHeight to zero
currentRandomHeight = 0;
	
//randomise the height of nextChunk
currentRandomHeight = nextChunk.RandomiseSection(currentRandomHeight);

Call the RepositionSections method from the end of Restart(). Save the script, open Chunk and create the ResetChildPositions method

public void ResetChildPositions()
{
	for (int i=0; i<CHUNK_SIZE; i++)
	{
		//get the ith child
		Transform child = transform.GetChild(i);
		
		//reset the height to zero
		SetChildHeight(child, 0);
	}
}

We go through each child game object and set the height to zero.

Make sure both scripts are saved and go back to Unity to check everything worked. There are too many visible blocks in a row now, so go to Chunk and change CHUNK_SIZE to 25 (then save). Also, the character is rolling annoyingly slowly. Go back to Unity, click on character in the hierarchy and change the Forward Speed in the inspector to 30. Test it again and tweak the values if you want to.

Unity tutorial 2d random infinite terrain height

Save the scene and the project.

In this tutorial, we’ve made the terrain height random to make the game more interesting. Next time, we’ll add scoring and keep track of the player’s highscore.

Remember you can download the files for this tutorial on the Downloads page. See you next time!

PreviousTutorialButtonNextTutorialButton

One Comment:

  1. Keep on working, great job!

Leave a Reply

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