Infinite Roller Tutorial Five – Random Terrain Gaps and Character Death

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 made the camera follow the character and added in the functionality to make the character jump. Today, we’ll add gaps to the terrain, and make the character die when it falls through. We’ll also stop the character’s ability to jump multiple times.

Open your Infinite Roller project and the scene, then open the Chunk script. To add gaps, we’re going to deactivate certain blocks. We’ll add some randomness in so that it’s not the same gap size each time. First create two const variables

//min and max size of the gaps in terrain
private const int MIN_GAP_SIZE = 5;
private const int MAX_GAP_SIZE = 10;

We might want to change these values later, but this will do for now. We’ll pick a random number between these values as the number of ground pieces to deactivate.

Create a new method

public void CreateGaps()
{	
}

It’s public as we’ll want to access it from the TerrainGenerator script. Inside the method, add

int numGroundToDeactivate = Random.Range (MIN_GAP_SIZE, MAX_GAP_SIZE);

This gives a random integer between MIN_GAP_SIZE and MAX_GAP_SIZE. Using Range to get an integer is inclusive of the lower value, and exclusive of the higher one. So at the moment, Random.Range will give back one of 5, 6, 7, 8, or 9. We also want to vary where the gap is, so create two more consts

private const int MIN_GAP_POSITION = 5;
private const int MAX_GAP_POSITION = CHUNK_SIZE – MAX_GAP_SIZE;

The minimum gap position means we’ll have at least 5 visible squares at the start. The maximum gap is the total chunk size minus the maximum gap size so that all of the blocks we want to disable are disabled. Below the existing code in CreateGaps(), add

int deactivatePosition = Random.Range (MIN_GAP_POSITION, MAX_GAP_POSITION);

to calculate the position to start deactivating squares at. As the ground pieces are children of the Chunk gameobject that the Chunk script is attached to, we can use the following to do this:

for (int i=0; i<CHUNK_SIZE; i++)
{
	//get the ith child
	Transform child = transform.GetChild(i);
			
	//set the child to be active to remove any previous gaps
	child.gameObject.SetActive(true);
	
	//create a gap if it should be one
	if (IsGap(i, numGroundToDeactivate, deactivatePosition))
	{
		child.gameObject.SetActive(false);
	}
}

This for loop starts at i=0. Each time through the loop, we get the ith child of the parent Chunk object, then set it as active to remove any previous gaps. Next, we call a method we haven’t created yet called IsGap. We’ll create this in a moment, but it will check if the child is in a gap position, and will return true if it is. If it is in a gap position, the code goes into the if block and sets the child gameobject to be inactive. Passing false into the SetActive method disables all of the gamobject’s components including its renderer, so it can no longer be seen, and its collider so the character can pass through it.

Create the IsGap method

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

This method is private as we’ll only need to access it from inside this class. We pass in the childIndex and the random numbers we’ve selected for the number of squares to deactivate and the position to deactivate from. The bool isGap is true when the childIndex is at or after the deactivatePosition and while it’s within the number of pieces of ground to deactivate.

Unity tutorial 2d random terrain gap

This code just needs to be called in the appropriate place now. Open the TerrainGenerator script and at the end of the Start method, add

//create gaps in the current and next chunks
currentChunk.CreateGaps();
nextChunk.CreateGaps();

Next, add

//create gaps in the next chunk
nextChunk.CreateGaps();

in the Update method, inside and at the end of the if statement to add a new, different gap to the next chunk as the character rolls forward. Save both scripts and go back to Unity. Zoom out a bit in the scene view so that you’ll be able to see a good portion of the ground as it’s created, then press play. To me, the gaps look too similar, so go back to the Chunk script and change MIN_GAP_SIZE to 3 and MAX_GAP_SIZE to 12, or whatever you prefer, then check you’re happy with it in Unity.

Next, we’ll add character death. Create a new script (Right click in the Scripts folder in the project window, go to Create -> C# script), and name it GameController. We’ll add logic in here to control the game. At the moment, that means ending the game when the character falls through a gap.

Open the GameController script, then delete the Start and Update methods. Add a new const for the height the character must fall below for the game to be over.

//if the player falls below a certain height, the game is over
private const int GAME_OVER_HEIGHT = -5;

We’ll need a reference to the position of the character. As the position is part of the transform, we only need the Transform component.

//reference to the character
public Transform character;

It’s public as we want to assign it in the inspector – save the script and go back to Unity. Create a new empty gameobject in the hierarchy named GameController and drag the GameController script onto it. Now drag the character onto the character slot in the inspector. Go back to the script and add an Update method

void Update()
{
	//if the player's height is below the minimum, bring up the menu
	if (character.position.y < GAME_OVER_HEIGHT)
	{
	}
} 

The if statement checks whether the character has fallen off the terrain by testing whether its y position is below the height we’ve set for GAME_OVER_HEIGHT. When it has fallen off, we want to pause the game and be able to press R to restart. In the next tutorial we’ll make menus to allow us to restart, but for now we’ll use the R key. Add

//pause it
Time.timeScale = 0;

inside the if block. This changes the game’s timescale to zero which stops the game’s time from moving forwards, effectively pausing the game. Go back to Unity, press play and let the character fall into a gap to see it pause. Go back to the scripts and add

//the players original position
private Vector3 characterInitialPosition;

to cache the character’s initial position, and then assign it in the Awake method.

void Awake()
{
	characterInitialPosition = character.position;
}

Create another new variable

//reference to the terrain generator
public TerrainGenerator terrainGen;

Save the script and go back to Unity, then drag the TerrainGenerator gameobject into the terrainGen slot in the inspector. Go back to the script and create a new method to allow us to restart the game

/** Restart the game. Called when play button is pressed*/
private void RestartGame()
{
	//reposition the character
	character.position = characterInitialPosition;
	
	//reset the terrain
	terrainGen.Restart();
	
	//unpause
	Time.timeScale = 1;
}

First, this repositions the character to our cached initial version, then we reset the terrain by calling a Restart method in the terrainGen object, which we’ll create in a minute. Finally, the game is unpaused. In the Update method, add

if (Input.GetKeyDown (KeyCode.R)) 
{
	RestartGame();
}

This is temporary, and we’ll change to a button press on a menu screen in the next tutorial, but for now, Input.GetKeyDown detects when we’ve pressed, in this case, R. When we do, RestartGame is called.

Unity tutorial restart game

Open the TerrainGenerator script and create a new method

public void Restart()
{
}

Inside, we’re going to position the chunks back to their initial positions. We’ve already written the appropriate code in this script’s Start method, so we’ll extract the duplicate code into a new method,

private void RepositionChunks()
{
	//get width of ground prefab
	float widthPrefab = previousChunk.WidthGroundPrefab;

	//reposition chunks
	previousChunk.RepositionChunk(new Vector3(-widthPrefab*Chunk.CHUNK_SIZE, 0, 0));
	currentChunk.RepositionChunk(Vector3.zero);
	nextChunk.RepositionChunk(new Vector3(widthPrefab*Chunk.CHUNK_SIZE, 0, 0));
}

Then delete the duplicate code in Start and replace it with a call to RepositionChunks(). Add another call to RepositionChunks to the Restart method

public void Restart()
{
	RepositionChunks ();
}

Save the scripts. Now when you play the game, pressing R restarts it. Test this works in Unity.

Open the RollingCharacterController script. We’ll add in some logic to stop the character from jumping more than twice. First, add a new variable

//bool for if the character is allowed to jump
private bool canJump = true;

It’s set to be true initially as the character should be able to jump straight away. Next, add a new method

void OnCollisionEnter2D(Collision2D coll) 
{
}

This is one of Unity’s methods and is called when a collision with the gameobject the script is attached to occurs. Inside, add the code

//check if character is on the ground. 
if (coll.gameObject.CompareTag("Ground"))
{
	canJump = true;
}		

The if statement checks the Collision2D variable coll to see if it’s tagged as “Ground”. If it is, it sets the canJump bool to be true so that once the character lands on the ground, it can jump again. The ground isn’t tagged as “Ground” yet, so save and go back to Unity, and click on the camera in the hierarchy. In the inspector, above the transform component, there’s a Tag drop down menu. The camera automatically has a tag “MainCamera”. If you click on the drop down menu, you can see a list of other pre-existing tags. Click on Add Tag… then in the Tags section, click on the + icon to create a new one. Click in the box that says “New Tag” and type in “Ground”.

Unity tutorial tags

Now click on the ground prefab in the Prefabs folder in the project window. Click on the Tags drop down menu and select the new “Ground” tag. Go back to the RollingCharacterController. Now when the character collides with the ground, the canJump boolean will be set to true.

Next, add canJump to the if statement condition in FixedUpdate, and set it to be false inside:

if (jump && canJump)
{
	rigidbody2D.AddForce(new Vector2(0, jumpUpForce));
	jump = false;
	canJump = false;
}

Save and go back to Unity to test it out. You should now only be able to jump once. For this game, I want the character to be able to jump twice before needing to land. To do this, add two new variables

//the number of times we can jump before hitting the ground
private const int MAX_JUMPS = 2;

//count how many times we've jumped
private int numJumps = 0;

And delete all of the canJump references – we won’t need them anymore. Next, alter the FixedUpdate method to

void FixedUpdate()
{
	rigidbody2D.velocity = new Vector2(forwardSpeed, rigidbody2D.velocity.y);
	
	if (jump && numJumps < MAX_JUMPS)
	{
		rigidbody2D.AddForce(new Vector2(0, jumpUpForce));
		jump = false;
		numJumps++;
	}
}

When the character has jumped once or twice after its previous landing, we go into the if block and jump. numJumps then increments. Next, we need to reset numJumps to zero inside the if statement in OnCollisionEnter2D:

numJumps = 0;

Now, the character will only jump if it’s jumped twice or less after landing on the ground.

Unity tutorial jump

Go back to Unity and test this works. Save the scene and the project.

In this tutorial, we’ve added random gaps to the terrain, added character death, and fixed the jump functionality of the character controller so that it can jump a maximum of twice before landing. Next time, we’ll create a menu system to start the game, and start creating a HUD.

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

PreviousTutorialButtonNextTutorialButton

Leave a Reply

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