Create A 2D Game With Unity Engine Part 8: Restarting The Level When The Player Dies And Wrapping Up Our Game

Table of Contents

Create A 2D Game With Unity Engine Part 8: Restarting The Level When The Player Dies And Wrapping Up Our Game

Reading Time: 8 minutes
Level: Beginner
Version: Unity 2020.3.1 LTS

Help Others Learn Game Development

Share on facebook
Share on twitter
Share on reddit
Share on linkedin

In part 7 of this tutorial series we learned how to interact with UI buttons, how to load scenes, and we learned about the singleton pattern, one of the most important concepts in Unity game development.

In this part we are going to restart the game when the player dies and finish the project.

Creating The Gameplay UI

When the monsters collide with the player character he gets destroyed, but we have no way to play the game again except by restarting it from the editor.
 
To fix that, in the Hierarchy tab in the Gameplay scene, Right Click -> UI -> Button.
 
By default, this will create a new Canvas object which I will rename to Gameplay Canvas. I am also going to set the Render Mode to Screen Space – Camera and attach the Main Camera object in the Render Camera field:
 
Img 1

For the UI Scale Mode select Scale With Screen Size and use 1920×1080 as the Reference resolution and for the Match set 0.5:

Img 2

Next, select the UI button and rename it to Restart Game Button and delete the UI text child object:

Img 3

From the Assets -> Sprites folder, attach the Restart sprite to the Source Image field in the Image component for the Restart Game Button:

Img 4
Set the anchor at the top left for the Restart Game Button:
 
Img 5
For the position and size of the Restart Game Button, set the following values:
 
Img 6
If we take a look how our UI looks line in the Game tab we will not see the UI button:
 
Img 7

The reason for that is because the Gameplay Canvas is set on the Default Sorting Layer:

Img 8

When we first introduced the Canvas object we mentioned that it also has a Sortin Layer and the Order in Layer option and they work the same way as they do for normal sprites.

In order to make the UI elements visible we need to set them on a Sorting Layer that will be rendered on top of the Background sorting layer.

Although we can set the Canvas on any sorting layer that is higher than the Background, or even set it on the Background and use a higher Order in Layer number, I always prefer to create a separate Sorting Layer for the UI:

Img 9
Now set the Gameplay Canvas on the UI Sorting Layer:
 
Img 10
And this will make the UI button visible in the Game tab:
 
Img 11

Duplicate the Restart Game Button, rename it to Home Button, move it below the Restart Game button and from the Assets -> Sprites folder, drag the Home sprite in the Source Image for the Image component of the Home Button:

Img 12


Gameplay UI Script

To interact with the buttons that we created, we need to create a new script. In the Assets -> Scripts -> Controller Scripts, Right Click -> Create -> C# Script and name it GameplayUIController.
 
In the Hierarchy tab, Right Click -> Create Empty and name the game object Gameplay UI Controller and attach the GameplayUIController script on it:
 
Img 13
In the GameplayUIController script first import SceneManagement library by typing above the class declaration the following line of code:
 
				
					using UnityEngine.SceneManagement;
				
			
Next, create the following functions:
 
				
					public void RestartGame()
    {
        SceneManager.LoadScene(TagManager.GAMEPLAY_SCENE_NAME);
    }

    public void BackToMainMenu()
    {
        SceneManager.LoadScene(TagManager.MAIN_MENU_SCENE_NAME);
    }
				
			
The functions are self explanatory and you can assume from their name what they will do.
 
In regards to reloading the same scene, we can also get the name of the current scene we are in, instead of passing the name of the scene:
 
				
					SceneManager.LoadScene(SceneManager.GetActiveScene().name);
				
			
So for the RestartGame function, you can use the line of code above, or you can use the one from the first example.
 
Attach the Gameplay UI Controller object to the Restart Game Button and Home Button listener list and select the appropriate functions for the appropriate buttons.
 
Run the game and let’s test it out:
 


Adding The Level Timer In Our Game

To spice things up with our game we are going to add a timer that will count the time since the game starts and that way you will see how long can you stay alive before the enemies kill you.

In the Gameplay scene in the Hierarchy tab, Right Click -> UI -> Text. Rename the text to Timer Text, and set it’s anchor to top middle:

Img 14
For the text size and position, set the following values:
 
Img 15

For the initial text, Font, Font Size, Alignment and Color set the following values:

Img 16
After we finish that setup, this is how the Timer Text looks like in the game:
 
Img 17
Inside the GameplayUIController is where we are going to do the magic that will display the time count on the screen. Since we are going to edit the UI text, we need to import Unity’s UI library, so above the GameplayUIController class declaration add the following line of code:
 
				
					using UnityEngine.UI;
				
			
Now below the GameplayUIController class declaration add the following lines of code:
 
				
					
    private Text timerText;

    private int timerCount;
				
			

Now we need to get a reference to the Timer Text, and again, in programming there are multiple ways how we can solve a problem.

This is one way how we can do it:

				
					private void Start()
    {
        // finds a game object by the given NAME in the scene
        timerText = GameObject.Find("Timer Text").GetComponent<Text>();

        // finds a game object by the given TAG in the scene
        timerText = GameObject.FindWithTag("Timer Text").GetComponent<Text>();
    }
				
			

Both of these functions do the same thing, except one finds a game object in the scene by its name, and the other by its tag, and then it gets a reference to the Text component of the game object.

If you want to use one of these ways, then make sure that you define the name, or the tag, depending on which one you use, of the game object in the TagManager.

I would always prefer to use tag over name, because as far as I know, it is more efficient, even though it would not make an impact on your game if you use the name to get a reference to the game object.

Another way is using SerializeField:

				
					[SerializeField]
    private Text timerText;
				
			
As we already know, SerializeField will make the variable visible in the Inspector tab, and we can drag the game object from the Hierarchy tab to get a reference to it.
 
This is the way how we are going to do it so make sure you that you declare the timerText variable the same as you see above on lines 1 and 2.
 
Now drag the Timer Text object in the timerText empty field in the Inspector tab for the GameplayUIController class:
 
Img 18
This way is the most efficient way when it comes to game optimization because we drag the game object directly into the reference field and we don’t need to do it from code.
 
But depending on the situation you will not be able to attach game objects to reference fields always, so keep that in mind.
 
Now we need to create a function that will increase the timerCount value and display that to the user. In the GameplayUIController class, add the following lines of code:
 
				
					void CountTime()
    {
        timerCount++;
        timerText.text = "Time: " + timerCount;
    }
				
			

First we increase the value of timerCount by 1, which is what ++ does. Then we call the text property of the timerText variable and pass a string concatenated with our timerCount value.

Since we are displaying seconds, we can use the InvokeRepeating function to call the CountTime function every second and display the current time to the user.

In the Start function add the following lines of code:

				
					private void Start()
    {
        InvokeRepeating("CountTime", 1f, 1f);
    }
				
			

The InvokeRepeating function will call the function by the name that we pass as a string repeatedly.

The first time it will call it after waiting for the value of the first parameter in seconds, then it will call the function repeatedly after waiting for the value of the second parameter.

In our case, the InvokeRepeating will call the CounTime function first time after 1 second, then it will call it repeatedly after every 1 second.

Let’s run the game and see the outcome:


Stopping The Timer Count After Player Dies

The level timer is working but when the player dies it continues to
count time which is not what we want so we need to stop the timer count
when the enemies kill the player.

As always there are multiple ways how we can achieve this. One of the
ways is to get a reference to the player character game object in the
GameplayUIController and test if the player dies, then we can use
CancelInvoke to stop calling the CountTime function.

On the other hand, we can get a reference to the GameplayUIController
in the Player script, and when the player dies we can call a function
that we would define in the GameplayUIController script that will use
CancelInvoke to stop the CountTime function.

We are going to take a different approach and use the time scale to stop the time.

In the Player script, in the OnCollisionEnter2D and OnTriggerEnter2D, when we test if the player object collides with one of the enemies, before we destroy the player game object, we can call the time scale to stop the time:

				
					private void OnCollisionEnter2D(Collision2D collision)
    {

        // detecting collision with the ground
        if (collision.gameObject.CompareTag(TagManager.GROUND_TAG))
            isGrounded = true;

        // detecting collision with the enemies
        if (collision.gameObject.CompareTag(TagManager.ENEMY_TAG))
        {
            Time.timeScale = 0f;
            Destroy(gameObject);
        }

    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag(TagManager.ENEMY_TAG))
        {
            Time.timeScale = 0f;
            Destroy(gameObject);
        }
    }
				
			
On lines 11 and 21, we call Time.timeScale = 0, which will stop the time in the game and everything that is time scale dependent will stop working, InvokeRepeating is one of the functions that is dependent on time scale and when the player dies, the timer count will stop counting.
 
Let’s run the game to see it in action:
 
When the player dies the time indeed stops, but when we restart the game, everything is frozen.
 
The player stands in one place, the timer is not working and nothing is happening in the game.
 
What is going on?
 
Since we set the time scale to be equal to 0, which freezes the game time, we need to set the time scale back to 1 when want to play the game again.
 
So inside the GameplayUIController script, in the functions that we use as listeners for the Restart Game Button and the Home Button set the time scale back to 1:
 
				
					public void RestartGame()
    {
        Time.timeScale = 1f;
        SceneManager.LoadScene(TagManager.GAMEPLAY_SCENE_NAME);
    }

    public void BackToMainMenu()
    {
        Time.timeScale = 1f;
        SceneManager.LoadScene(TagManager.MAIN_MENU_SCENE_NAME);
    }
				
			
Let’s run the game now and see the outcome:
 

We need to set the time scale back to 1 even in the Home Button because if the user returns back to the main menu after the player has died, and he selects a character to play the game, it will be frozen again because the time scale is still at 0.

The important lesson here is that every time you set the time scale to 0, you need to set it back to 1 when you want to continue playing your game, so make sure that you remember that.

And with this our game is finished. Congratulations for making it till the end and sticking with me while we created this cool game.

Leave a Comment