Creating Your First Unity Game – Rainy Knifes Part 6: Detecting Collision In Code And Restarting The Game After The Player Dies

Table of Contents

Creating Your First Unity Game – Rainy Knifes Part 6: Detecting Collision In Code And Restarting The Game After The Player Dies

Reading Time: 14 minutes
Level: Beginner
Version: 2020.3.1 LTS

Help Others Learn Game Development

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

In part 5 of this tutorial series we animated the Player through code, showed a neat trick with the Rigidbody 2D component and we cleaned up our code.

In in this part of the tutorial we are going to add some finishing touches and wrap up our game.

Tagging Game Objects

With the current set up, the Knifes are falling from the sky but they are stuck in our game and that blocks the Player from moving.
 
Further more, the goal of the game is to avoid the Knifes, and if the Player can’t even move because of the Knifes, then our game is unplayable.
 
The solution is to remove the Knifes from the game as soon as they hit the ground and we are going to detect that in our code.
 
But before we do that, we are going to tag the Ground Holder game object.
 
Click on the Ground Holder game object in the Hierarchy tab, and in the Inspector tab click on the drop down list where it says Untagged at the top left corner:
 
Img 1 - 85%

From the drop down list, click on Add Tag… at the bottom:

Img 2

This will open the Tags & Layers tab in the Inspector which we already saw when we created Sorting Layers for the Sprite Renderer component.

We are going to use the exact same method to create new tags for our game objects.

Click on the + button on the right side:

Img 3 - 85%

From there, add a new tag called Ground and press the Save button:

Img 4
Now you will see the new Ground tag inside the Tags list:
 
Img 5 - 85%
This and the Knife tag are the only custom tags we need for this game, but down the road when we create more complex games and add more tags, we will use the same method to create new tags and every tag that you add you will see it in the Tags list.
 
To tag a game object, select it, in our case select the Ground Holder, and from the drop down list where it says Untagged, select the new Ground tag:
 
Img 6

Now when you select the Ground Holder game object you will see that instead of Untagged Ground is displayed next to the Tag label:

Img 7 - 85%
This is how we create new tags and tag game objects with the appropriate tags.
 

Detecting Collision In Code

Inside the Assets -> Scripts -> Knife Scripts Folder create a new C# script and give it a name Knife.
 
Go inside the Prefabs folder and select the Knife Prefab and attach the Knife script on it.
 
There are built in functions that we inherit from MonoBehaviour that we can use to detect collision between game objects in the code.
 
Open the Knife script in Visual Studio and declare the following function:
 
				
					private void OnCollisionEnter2D(Collision2D collision)
    {
        
    }
				
			

The OnCollisionEnter2D function will allow us to detect the collision between the Knife, and any other game object that it collides with.

The Collision2D parameter will have the information about the object we have collided, and we can extract information from it such as the tag of the game object we have collided with.

See where I am going with this?

Inside the TagManager declare the following variable:

				
					public static string GROUND_TAG = "Ground";
				
			
Going back to the Knife script, inside the OnCollisionEnter2D add the following lines of code:
 
				
					private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == TagManager.GROUND_TAG)
        {
            Destroy(gameObject);
        }
    }
				
			
We can access the game object we have collided with from the collision parameter by calling collision.gameObject, and we can use .tag to access the tag property of the gameObject.
 
This is what we are doing on line. We are accessing the tag property of the gameObject we have collided with and we are comparing that tag if it’s equal to Ground.
 
As you can see we can compare two strings with each other the same way we can compare two numbers with each other by using the ==.
 
This will of course return true if the string on the right side is equal to the string on the left side.
 
The code on line 5 will destroy the current game object that is holding the script, in our case the Knife game object.
 
With destroy I mean that the Knife game object will be completely removed from the game.
 
Before we test the game, make sure that you activate the Knife Spawner game object which we have deactivated in the previous part of the tutorial.
 
Select the Knife Spawner and make sure that the checkbox next to it’s name in the Inspector tab is checked:
 
Img 8

Now when we run the game, we will see that every Knife who hits the ground e.g. Ground Holder game object will be destroyed and removed from the game:

A Better Way To Compare Strings

Currently we are using == to compare the tag of the collided game object with the GROUND_TAG from the TagManager script.
 
While this works it’s not the most optimized way to compare two string in Unity.
 
There is a built in function that we can use that is way more efficient and it’s called CompareTag:
 
				
					private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag(TagManager.GROUND_TAG))
        {
            Destroy(gameObject);
        }
    }
				
			
If we test our game the outcome will be the same, just that our code changed.
 
Inside the CompareTag function we pass the string that we want to compare the tag to, in our case TagManager.GROUND_TAG.
 
One thing that I want to emphasize is that this is a small game, so even if we used == to compare the tags it will not affect our game at all.
 
But I want to show you how to code the right way from the very start so I am introducing one way how to solve a problem, then I introduce a better way and explain the difference.
 

Deactivating Game Objects

There is also a better way how we can remove the Knifes from our game.
 
Because the Destroy function can also cause optimization problems if its overused.
 
Another way how we can remove the Knife from our game is to deactivate it.
 
We saw what deactivate means when we deactivated the Knife Spawner for our testing purposes.
 
I mentioned that when a game object is deactivated all the scripts attached on that game object will not run.
 
We can also deactivate the Knifes that fall on the Ground Holder instead of destroying them:
 
				
					private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag(TagManager.GROUND_TAG))
        {
            gameObject.SetActive(false);
        }
    }
				
			

Calling SetActive on a game object and passing the false parameter will deactivate the game object in the game.

That game object will stop running in the game until we activate it again, if needed of course.

For the current game we will not activate the Knifes again, but for future games we will reuse game objects by activating them again after they are deactivated.

Of course, to activate the game object just pass true as the parameter in the SetActive function.

If we run the game now the out come will be the same but there will be one difference.

Since the Knifes are being deactivated, they will still be in the game, but they will not be visible and they will not affect the game in their deactivated state.

Inside the editor, we can see all the deactivate Knifes in the Hierarchy tab:

As you can see all the deactivated game objects are visible in the Hierarchy tab and we can even move them in the level from the Scene tab and activate them manually as you saw from the video.

Of course, we can’t do any of this when the game is exported for your desired platform, but I just wanted to show you the difference when you are using Destroy vs SetActive.

When a game object is destroyed it is completely removed from the game and erased from the computer memory.

But when the game object is deactivated, it is still present in the game and computer memory.

It is not visible because any component attached on the deactivated game object will not run, but we can activate a deactivated game object any time in our game if we have a reference to it.

We will see examples of this a lot as reusing game objects is one of the ways how we optimize our games in Unity.

OnTriggerEnter2D

There is also another way how we can detect collision in our code.
 
So far we have been using OnCollisionEnter2D to detect collision, but we can also use OnTriggerEnter2D.
 
We can remove the OnCollisionEnter2D code and add OnTriggerEnter2D code:
 
				
					private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag(TagManager.GROUND_TAG))
        {
            gameObject.SetActive(false);
        }
    }
				
			

As you can see the code is exactly the same with a small difference that this time we are calling the CompareTag function on the collision parameter.

The reason for that is the Collision2D object doesn’t have a function CompareTag, so we need to call the gameObject property for the Collision2D object and then call the CompareTag.

But the Collider2D object has a function CompareTag and we can call it directly without the need to access the gameObject property of the Collider2D object.

If we run the game now this is what we will see:

The Knifes are still in the game even after the collide with the Ground Holder tag, what is the issue here?
 
Well in order for the OnTriggerEnter2D function to work, we need to denote that the collider of the Knife is a trigger collider.
 
To do that, select the Knife Prefab inside the Prefabs folder and check the Is Trigger checkbox on Box Collider 2D component:
 
Img 9

Now when we test the game, everything will work correctly:

Solid VS Trigger Colliders

Besides from using different functions for collision detection, there is another difference between solid and trigger colliders as you probably noticed from the preview above.
 
When the Is Trigger checkbox is not checked for a collider, that collider is solid, and when it collides with another collider it will simply stand on it, or it will hit it depending on from which side they collide.
 
To test this, comment out the OnTriggerEnter2D code:
 

Now uncheck the Is Trigger checkbox for the Box Collider 2D component of the Knife Prefab and run the game:

Since the Is Trigger checkbox is not checked, the collider of the Prefab game object is solid and it stands on the Ground Holder when it collides with it.

Now make sure that you check the Is Trigger checkbox for the Knife Prefab collider and run the game again:

Assuming that your OnTriggerEnter2D code is commented out as we showed in one of the previous steps, you will see how the Knife game objects fall through the Ground Holder.
 
This is because colliders who have the Is Trigger checkbox checked are not solid objects and they will pass through other colliders.
 
Even if the Ground Holder’s Box Collider 2D component had also the Is Trigger checkbox checked, the Knife would still fall through it.
 
This is how we create solid and non solid colliders.
 
One other thing that you noticed from the two video previews above is that if we make a change on the original Prefab game object, those changes will be applied to all copies created from that Prefab.
 
When we unchecked the Is Trigger checkbox for the original Knife prefab, all copies of the Knife Prefab we created in code were solid objects.
 
And when we checked the Is Trigger checkbox for the original Knife Prefab all copies of the Knife Prefab we created in code were not solid game objects and they passed through the Ground Holder.
 

Killing The Player And Restarting The Game

Now that we know how to create a solid and how to create an non solid physics game object in Unity I am going to leave the Knife Prefab to be a non solid object e.g. the Is Trigger checkbox will be checked.
 
Before we proceed to code the Player’s behaviour when the Knife hits him make sure that you uncomment the OnTriggerEnter2D code in the Knife script:
 
				
					private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag(TagManager.GROUND_TAG))
        {
            gameObject.SetActive(false);
        }
    }
				
			

Inside the PlayerMovement script we are going to test if the Player collides with the Knife object, but before we do that, we need to tag the Knife object.

Create a new tag Knife using the same steps we explained at the start of this lecture. After you finish you will see a Knife tag in your list as well:

Img 10 - 85%

Select the Knife Prefab located in the Prefabs folder and set his tag to Knife.

Inside the TagManager add the following line of code:

				
					public static string KNIFE_TAG = "Knife";
				
			

Now inside the PlayerMovement script test for the collision between the Player and the Knife:

				
					private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag(TagManager.KNIFE_TAG))
        {

        }
    }
				
			

Now you are probably wondering how can we test for collision using OnTriggerEnter2D inside the PlayerMovement script because the Player game object is a solid object e.g. the Is Trigger checkbox is not checked on his collider:

Img 11 - 85%

When it comes to OnTriggerEnter2D, it is not required that both game objects that are colliding with each other to have the Is Trigger checkbox checked.

Only one of the colliding game objects can the Is Trigger checkbox checked and the OnTriggerFunction2D will detect that collision even if it’s called on a game object who doesn’t have the Is Trigger checkbox checked.

Which is the case in our current situation.

The collider of the Player is not a trigger, but the collider of the Knife is a trigger, yet we are calling the OnTriggerEnter2D code from the Player’s script.

Now when the Knife hits the Player, the Player game object should die. To simulate that, we have multiple options.

We can reposition the Player game object outside the Camera view by settings his position vector to something like:

				
					private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag(TagManager.KNIFE_TAG))
        {
            transform.position = new Vector2(100f, 100f);
        }
    }
				
			
This can simulate that the Player has died since the Camera will not see him in the game, and after that we can restart the game.
 

Disabling The Sprite Renderer Component

We can also use the Sprite Renderer component and disable it so that it will not render the Player sprite on the screen and we will not see it:

				
					 private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag(TagManager.KNIFE_TAG))
        {
            sr.enabled = false;
        }
    }
				
			
But using this method we would also need to have a bool variable that we will use to allow the Player to move and detect collision.
 
Because when we disable the Sprite Renderer component the only thing that will happen is that the Player sprite will not be rendered or drawn on the screen.
 
All other functionalities of the Player such as his movement, the collider, collision detection, all of that will work, so we would need a bool variable to determine when the Player can move and detect collision.
 
Even though this solution is a little longer than the first one suggested I am going to use this one.
 
Above the Awake function in the PlayerMovement script declare the following variable:
 
				
					private bool isDead;
				
			
Now inside the Update function first test if the Player is dead, if that is true then we will not execute the code in the Update function:
 
				
					 private void Update()
    {
        // if the player is dead
        // return e.g. exit out of this function
        if (isDead == true)
            return;

        HandleMovement();
        HandleFacingDirection();
        HandleAnimation();
    }
				
			

On line 5 we are testing if isDead is equal to true. We can also perform this test by removing the == and just adding the isDead inside of the if statement:

				
					private void Update()
    {
        // if the player is dead
        // return e.g. exit out of this function
        if (isDead)
            return;

        HandleMovement();
        HandleFacingDirection();
        HandleAnimation();
    }
				
			

Both of these tests on line 5 in the example above and the previous example are testing for the same thing – if isDead is equal to true.

If that is the case we call the return statement on line 6.

We learned that a function that returns a value must have a return statement to return that value, but the Update function is a void function and void functions don’t have a return statement.

Well, a return statement in a void function simply means stop the execution when return is executed.

In our case, if isDead is true, and line 6 is executed e.g. the return statement is executed, then the function will stop its execution there and it will not continue to execute the code that is left inside that function.

This is how it would look like in an image:

Img 12 - 85%

This test that we are performing in the Update function will not allow the Player game object to move after the Knife hits it.

But we also need to program that inside the OnTriggerEnter2D:

				
					private void OnTriggerEnter2D(Collider2D collision)
    {

        if (isDead)
            return;

        if (collision.CompareTag(TagManager.KNIFE_TAG))
        {
            sr.enabled = false;
            isDead = true;
        }
    }
				
			
Notice how we are performing the same test inside the OnTriggerEnter2D on line 4.
 
This is because if the Player is already killed by the Knife once, there is no need to do it again.
 
When the line 9 executes and the Sprite Renderer component is disabled it will not render the Player sprite, we don’t need to call it multiple times for that to happen, once is enough.
 
On line 10 we set the isDead to be equal true, because if the tag of the collision game object is equal to the Knife, then we collided with the Knife and the Player has died in the game.
 
Now we can run the game to test our code:
 
As soon as the Knife hit the Player, the Player disappeared. This is because we have disabled the Sprite Renderer component.
 
We also saw in the preview above if check the checkbox to enable the Sprite Renderer component that the Player is visible again.
 
But we are not able to move since in our code we set the isDead to be equal to true when the Knife hits the Player.
 

Restarting The Game When The Player Dies

The only issue we have now is that our game still runs, the Knifes are falling from the sky even though the Player is not in the game.
 
To fix this, we will restart the game when the Player dies and for that we are going to create a new function above the OnTriggerEnter2D:
 
				
					void RestartGame()
    {
        UnityEngine.SceneManagement.SceneManager.LoadScene("Gameplay");
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {

        if (isDead)
            return;

        if (collision.CompareTag(TagManager.KNIFE_TAG))
        {
            sr.enabled = false;
            isDead = true;

            Invoke("RestartGame", 2.5f);
        }
    }
				
			

On line 3 in the RestartGame function we are calling the SceneManager from SceneManagement package to load a scene with the given name.

The SceneManagement is a set of classes already prepared for us to use for the purpose of loading new levels e.g. scenes or getting information about the scene we are currently in.

We use the SceneManager from that set of classes and we call its LoadScene method passing the name of the scene we want to load.

Currently we passed the name Gameplay, but we don’t have that scene in our game.

This can be easily fixed by going to Assets -> Scenes folder and rename the SampleScene that we have by default to Gameplay.

When prompted by the pop up screen click the Reload button:

Img 13
It is also important to note that if you want to load a scene using SceneManager the scene that you want to load needs to be added to the build.
 
By default the first default scene that is created when we create a new project is always added to the build.
 
To verify this go to File -> Build Settings:
Img 15
In the new pop up window you will see the Gameplay scene in the list of scenes that are added to the build:
 
Img 16

To add a new scene in the build, first you must open that scene from the project e.g. from the Scenes folder double click the desired scene and open it.

After that you need to click on the Add Open Scenes button from the Build Settings:

Img 17
We will see a lot of examples of creating new scenes, adding them to the build and loading them in our code.

On line 17 we use the Invoke function to call the RestartGame after 2.5 seconds which we denoted with the second parameter.
 
This is the final set up of the PlayerMovement script:
				
					using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    [SerializeField]
    private float moveSpeed = 5f;

    private float moveX;

    private Vector2 tempPos;

    private SpriteRenderer sr;

    [SerializeField]
    private float min_X = -2.28f, max_X = 2.28f;

    private Animator anim;

    private bool isDead;

    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
    }

    private void Update()
    {
        // if the player is dead
        // return e.g. exit out of this function
        if (isDead)
            return;

        HandleMovement();
        HandleFacingDirection();
        HandleAnimation();
    }

    // function responsible for moving the player
    void HandleMovement()
    {
        moveX = Input.GetAxisRaw(TagManager.HORIZONTAL_AXIS);

        tempPos = transform.position;

        tempPos.x += moveX * moveSpeed * Time.deltaTime;

        // call the handle bounds function before
        // you re-assign the position back to
        // transform.position
        HandleBoundsRestriction();

        transform.position = tempPos;
    }

    // function for handling the bounds restriction
    void HandleBoundsRestriction()
    {
        if (tempPos.x > max_X)
            tempPos.x = max_X;
        else if (tempPos.x < min_X)
            tempPos.x = min_X;
    }

    // function for handling the facing direction
    void HandleFacingDirection()
    {
        if (moveX > 0)
            sr.flipX = false;
        else if (moveX < 0)
            sr.flipX = true;
    }

    // function for handling the animation
    void HandleAnimation()
    {
        if (moveX != 0)
            anim.SetBool(TagManager.WALK_ANIMATION_PARAMETER, true);
        else
            anim.SetBool(TagManager.WALK_ANIMATION_PARAMETER, false);
    }

    void RestartGame()
    {
        UnityEngine.SceneManagement.SceneManager.LoadScene("Gameplay");
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {

        if (isDead)
            return;

        if (collision.CompareTag(TagManager.KNIFE_TAG))
        {
            sr.enabled = false;
            isDead = true;

            Invoke("RestartGame", 2.5f);
        }
    }

} // class
				
			

Now we can run and play our game until we get bored, which is never he he he he.

You can also change the time interval for the spawning of the Knifes by selecting the Knife Spawner and changing the min and max spawn time values in the Inspector tag:

Img 14 - 85%

Now run and test the game:

The lower we set the values for the spawning time for the Knifes the more challenging and fun our game becomes.
 
And with that I want to congratulate you for making till the end and creating this super cool and fun game.
 

Leave a Comment