In the last tutorial we started adding the basic game features in our Bomberman game. Now, we are going to finish implementing the single player features, such as: dropping bombs, creating explosions and adding a number of lives to the player. Then, in the next tutorial we can add the multiplayer stuff and finish the series.

In order to follow this tutorial, you are expected to be familiar with the following concepts:

C# programming

Basic Unity concepts, such as importing assets, creating prefabs and adding components

Basic Tiled map creation, such as adding a tileset and creating tile layers

Assets copyright

The assets used in this tutorial were created by Cem Kalyoncu/cemkalyoncu and Matt Hackett/richtaur and made available by “usr_share” through the creative commons license, wich allows commercial use under attribution. You can download them in http://opengameart.org/content/bomb-party-the-complete-set or by downloading the source code.

Source code files

You can download the tutorial source code files here .

Don't miss out! Offer ends in Access all 200+ courses

Access all 200+ courses New courses added monthly

New courses added monthly Cancel anytime

Cancel anytime Certificates of completion ACCESS NOW

Dropping bombs

Until now our player can move around the battle map. However, it is still not able to drop bombs in the game. This is what we are going to add now.

The first thing we need to do is creating a Bomb prefab. So, create a new prefab and call it Bomb. This prefab will need a Sprite Renderer, an Animator (you can create one the same way you did for the Player Animator) and a Box Collider 2D. The Box Collider will be a trigger, so check the Is Trigger box. This way, it won’t push the player away from it when the bomb is just dropped. Also, as we did with the other Battle Scene objects, you need to properly set its scale and set the Sorting Layer in the Sprite Renderer to the Game layer.

Now we need to create the Bomb exploding animation. We already created the BombAnimator, so you can create a new animation called Explosion and drag it to the BombAnimator in the Animator editor.

The BombAnimator will be much more simple than the player one. It will only have the Explosion animation, so we don’t need to add any parameters or transitions. But, we still need to create this animation.

In order to easily edit the animation, you can temporarily add a Bomb to the game. So, with the Bomb object selected, open the Animation editor to edit the Explosion animation. Then drag all the bomb spritesheet frames to it, as shown below. Remember to properly set the number of Samples as well. After you finish editing the animation you can remove the Bomb object from the game.

Now that we have a working Bomb prefab, we are going to allow the player to drop bombs in the game.

In order to do that, we are going to create the BombDropping script as below. In the update method of this script we are going to check for the Space key (or any other key you prefer). If this key is pressed, the script is going to call a DropBomb method.

The DropBomb method will instantiate a new Bomb from its prefab (the prefab is an attribute of the script). The position of the new bomb will be the same as the player.

public class BombDropping : MonoBehaviour { [SerializeField] private GameObject bombPrefab; // Update is called once per frame void Update () { if (Input.GetKeyDown ("space")) { DropBomb (); } } void DropBomb() { Instantiate (bombPrefab, this.gameObject.transform.position, Quaternion.identity); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class BombDropping : MonoBehaviour { [ SerializeField ] private GameObject bombPrefab ; // Update is called once per frame void Update ( ) { if ( Input . GetKeyDown ( "space" ) ) { DropBomb ( ) ; } } void DropBomb ( ) { Instantiate ( bombPrefab , this . gameObject . transform . position , Quaternion . identity ) ; } }

In the end, we add this script to the Player prefab.

And with that, you can already try playing the game and dropping bombs. However, you will notice that the Player is not colliding with the bombs since we configured their colliders as trigger. We are going to fix them now, as we allow the bombs to explode.

Creating explosions

As long as the Bomb collider is a trigger, the Player won’t collide with it. However, we uncheck the Is Trigger box, the Player will be pushed aside when it drops a bomb, since they will collide with each other.

One way of solving this is creating the bomb with a trigger collider. But, once the player is not colliding with the bomb anymore, we change the bomb collider to not be a trigger anymore. From this point the player and the bomb will collide normally.

We are going to do that in a new script called BombExplosion. This script will be used to explode the bomb as well, but for now we are only going to do as below: we are going to implement the OnTriggerExit2D method and, when it is called, it will change the Bomb collider to not be a trigger anymore. The OnTriggerExit2D method is called when another object’s collider leaves the Bomb trigger. So, it will be called once the player is not over the bomb anymore.

public class BombExplosion : MonoBehaviour { void OnTriggerExit2D(Collider2D other) { this.collider2D.isTrigger = false; } } 1 2 3 4 5 6 public class BombExplosion : MonoBehaviour { void OnTriggerExit2D ( Collider2D other ) { this . collider2D . isTrigger = false ; } }

Our next step is to explode bombs when their animation ends. Let’s start by creating an Explosion prefab. Then, we can add a Explode method in the BombExplosion script that creates explosions.

So, create a new prefab and call it Explosion. This prefab will have a Sprite Renderer (in the Game Sorting Layer), a Box Collider 2D and a Rigidbody2D. Notice that the Explosion collider will also be a trigger. That’s because we don’t want the explosion to physically interact with other objects, such as the player. However, it needs a Rigidbody2D in order to check for collisions with the walls in the game, because Unity only check for collisions when at least one of the objects has a Rigidbody (another option would be to add a Rigidbody to the walls). However, notice that the Explosion Rigidbody is a Kinematic one, because we don’t want the explosions to move in the game.

Now that we have the Explosion prefab, we can create explosions in the BombExplosion script. So first, let’s create an Explode method as below. First, it will create an explosion in the bomb position. Also, this explosion should be destroyed after some time, so it will call the Destroy method for it with a given explosion duration. Finally, it is going to create explosions in the four directions (left, right, up and down) and destroy itself. The explosions creation is done using a CreateExplosions method, which we are going to implement now.

[SerializeField] private GameObject explosionPrefab; [SerializeField] private float explosionDuration; public void Explode() { GameObject explosion = Instantiate (explosionPrefab, this.gameObject.transform.position, Quaternion.identity) as GameObject; Destroy(explosion, this.explosionDuration); CreateExplosions (Vector2.left); CreateExplosions (Vector2.right); CreateExplosions (Vector2.up); CreateExplosions (Vector2.down); Destroy (this.gameObject); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [ SerializeField ] private GameObject explosionPrefab ; [ SerializeField ] private float explosionDuration ; public void Explode ( ) { GameObject explosion = Instantiate ( explosionPrefab , this . gameObject . transform . position , Quaternion . identity ) as GameObject ; Destroy ( explosion , this . explosionDuration ) ; CreateExplosions ( Vector2 . left ) ; CreateExplosions ( Vector2 . right ) ; CreateExplosions ( Vector2 . up ) ; CreateExplosions ( Vector2 . down ) ; Destroy ( this . gameObject ) ; }

The CreateExplosions method is shown below. This method will create explosions in a given direction. The number of explosions is given by a explosionRange attribute of the script. So, it is going to iterate from 0 to the number of explosions creating them. However, once it finds a wall or a block, it should stop creating explosions. If it finds a block in this process, the block should be destroyed.

We can do that using the Physics2D.OverlapBox method from Unity. This method receives as parameter a Box, an angle, a ContactFilter and a list of Colliders. Then, it will populate this list of Colliders with all colliders that overlap the queried box. This way, we can check if the region where the explosion will be is already occupied by a wall or a block. In our case, the angle will be 0 and we are not going to use any special ContactFilter, so we should only worry about the box.

The box will be the region the explosion is going to occupy, so we can use the explosionPosition and explosionDimensions values. Once we have the colliders list, we iterate through it looking for walls or blocks. If we find a wall or block, we set a variable as true and break the loop. If we find a block, we destroy it. Finally, in the outer loop, if we have found a block or a wall, we also break this loop. If not, we create a new explosion from the explosionPrefab and set it to be destroyed after the explosion duration.

[SerializeField] private int explosionRange; private void CreateExplosions(Vector2 direction) { ContactFilter2D contactFilter = new ContactFilter2D (); Vector2 explosionDimensions = explosionPrefab.GetComponent<SpriteRenderer> ().bounds.size; Vector2 explosionPosition = (Vector2)this.gameObject.transform.position + (explosionDimensions.x * direction); for (int explosionIndex = 1; explosionIndex < explosionRange; explosionIndex++) { Collider2D[] colliders = new Collider2D[4]; Physics2D.OverlapBox (explosionPosition, explosionDimensions, 0.0f, contactFilter, colliders); bool foundBlockOrWall = false; foreach (Collider2D collider in colliders) { if (collider) { foundBlockOrWall = collider.tag == "Wall" || collider.tag == "Block"; if (collider.tag == "Block") { Destroy(collider.gameObject); } if (foundBlockOrWall) { break; } } } if (foundBlockOrWall) { break; } GameObject explosion = Instantiate (explosionPrefab, explosionPosition, Quaternion.identity) as GameObject; Destroy(explosion, this.explosionDuration); explosionPosition += (explosionDimensions.x * direction); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 [ SerializeField ] private int explosionRange ; private void CreateExplosions ( Vector2 direction ) { ContactFilter2D contactFilter = new ContactFilter2D ( ) ; Vector2 explosionDimensions = explosionPrefab . GetComponent < SpriteRenderer > ( ) . bounds . size ; Vector2 explosionPosition = ( Vector2 ) this . gameObject . transform . position + ( explosionDimensions . x * direction ) ; for ( int explosionIndex = 1 ; explosionIndex < explosionRange ; explosionIndex ++ ) { Collider2D [ ] colliders = new Collider2D [ 4 ] ; Physics2D . OverlapBox ( explosionPosition , explosionDimensions , 0.0f , contactFilter , colliders ) ; bool foundBlockOrWall = false ; foreach ( Collider2D collider in colliders ) { if ( collider ) { foundBlockOrWall = collider . tag == "Wall" | | collider . tag == "Block" ; if ( collider . tag == "Block" ) { Destroy ( collider . gameObject ) ; } if ( foundBlockOrWall ) { break ; } } } if ( foundBlockOrWall ) { break ; } GameObject explosion = Instantiate ( explosionPrefab , explosionPosition , Quaternion . identity ) as GameObject ; Destroy ( explosion , this . explosionDuration ) ; explosionPosition += ( explosionDimensions . x * direction ) ; } }

The last thing we need to do is adding a callback in the end of the Explosion animation to call the Explode method. You can do that by opening the Explosion in the animation editor, then right clicking on the last frame and selecting and selecting Add Animation Event. The animation event function will be the Explode method.

And with that, you can try playing the game and letting the bomb to explode. See if the explosions are being correctly created (stopping in the walls and blocks). The next thing we are going to do is allow the explosions to damage other objects, such as the Player and bombs.

Exploding stuff

Explosions should interact with the player and bombs. If an explosion touches the player, it should kill the player. If an explosion touches a bomb, the bomb should explode immediately.

Let’s start by adding the Player lives, so that it can die to a bomb. This will be done using the PlayerLife script below. The script will be responsible for updating the current number of lives, but also for making the player invulnerable for a short time right after it has been damaged. This prevents the player to be exploded by multiple bombs at once.

So, the LoseLife method starts by checking if the player is not currently invulnerable. If it is vulnerable, it will reduce the current number of lives and check if this was the last life. If so, it will also destroy the player object. After decreasing the number of lives, it will make the player invulnerable, and invoke the BecomeVulnerable method after the invulnerability duration time. The BecomeVulnerable method, by its turn, will simply make the player vulnerable again.

public class PlayerLife : MonoBehaviour { [SerializeField] private int numberOfLives = 3; [SerializeField] private float invulnerabilityDuration = 2; private bool isInvulnerable = false; public void LoseLife() { if (!this.isInvulnerable) { this.numberOfLives--; if (this.numberOfLives == 0) { Destroy (this.gameObject); } this.isInvulnerable = true; Invoke ("BecomeVulnerable", this.invulnerabilityDuration); } } private void BecomeVulnerable() { this.isInvulnerable = false; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class PlayerLife : MonoBehaviour { [ SerializeField ] private int numberOfLives = 3 ; [ SerializeField ] private float invulnerabilityDuration = 2 ; private bool isInvulnerable = false ; public void LoseLife ( ) { if ( ! this . isInvulnerable ) { this . numberOfLives -- ; if ( this . numberOfLives == 0 ) { Destroy ( this . gameObject ) ; } this . isInvulnerable = true ; Invoke ( "BecomeVulnerable" , this . invulnerabilityDuration ) ; } } private void BecomeVulnerable ( ) { this . isInvulnerable = false ; } }

Now we are going to create a script for the Explosion which will call the LoseLife method when colliding with the player. This script will be called ExplosionDamage, and it is shown below. We are going to implement the OnTriggerEnter2D method, which will be called every time another object collides with the explosion. When this happens, we are going to check the type of the object by its tag (remember to properly set the prefabs tags). If it is a “Character” tag, it will call the LoseLife method in the PlayerLife script. If it is a “Bomb” tag, it will call the Explode method in the BombExplosion script. This Explode method is the same we were already using, so we don’t need to implement a new one.

public class ExplosionDamage : MonoBehaviour { void OnTriggerEnter2D(Collider2D collider) { if (collider.tag == "Character") { collider.gameObject.GetComponent<PlayerLife> ().LoseLife (); } else if (collider.tag == "Bomb") { collider.gameObject.GetComponent<BombExplosion> ().Explode (); } } } 1 2 3 4 5 6 7 8 9 10 public class ExplosionDamage : MonoBehaviour { void OnTriggerEnter2D ( Collider2D collider ) { if ( collider . tag == "Character" ) { collider . gameObject . GetComponent < PlayerLife > ( ) . LoseLife ( ) ; } else if ( collider . tag == "Bomb" ) { collider . gameObject . GetComponent < BombExplosion > ( ) . Explode ( ) ; } } }

Then, we add those scripts to their respective prefabs.

By now, you can already try playing and checking if the explosions interacting correctly with the Player and the Bombs. However, we still can not see in the game the current number of player lives. This is the next thing we are going to do.

Showing the player lives

Similarly to how we showed the title and text in the title screen, we are going to create a Canvas to show the player number of lives. Let’s start by creating a new Canvas (right-click on the Hierarchy, then UI->Canvas) and calling it HUDCanvas. This Canvas will be configured the same way we did with the BackgroundCanvas:

Set Render Mode to Screen Space – Camera

Select the Main Camera as the Render Camera

Select the Sorting Layer as Game

Set UI Scale Mode as Scale With Screen Size

Now we are going to create an object which will represent a grid to show the player lives. We can do that by creating an empty object as a child of the Canvas, and then adding a Grid Layout Group component to it. Then, we need to properly configur this Grid Layout Group to show the player lives the way we want.

Basically, what we need to do is changing the Constraint field to Fixed Row Count, and Constraint Count to 1. This will make the grid to show the lives in a single row. Also, we need to change the Cell Size to 20×20. Figure below shows the PlayerLivesGrid object.

Now we need to create objects to represent the player lives, and manage them in the lives grid. So, let’s create a new Image as a child of the HUDCanvas, calling it PlayerLifeImage and making it a prefab. In this image, we just need to set the Source Image to be the heart sprite.

Now, we are going to change the PlayerLife script to manage the player lives in the grid. First, we need to add two new attribute: one attribute for the player life image prefab, and another one with a List, storing all player life images. Then, in the Start method we create a PlayerLifeImage object for each one of the player lives. Notice that we need the PlayerLivesGrid object so that we can create the images as children of it. After creating each image, we add it to the lifeImages list.

We also need to change the LoseLife method to destroy a PlayerLifeImage when the player loses a life. We can do that by retrieving the last element from the lifeImages list, destroying it, and then removing it from the List.

public class PlayerLife : MonoBehaviour { [SerializeField] private int numberOfLives = 3; [SerializeField] private float invulnerabilityDuration = 2; private bool isInvulnerable = false; [SerializeField] private GameObject playerLifeImage; private List<GameObject> lifeImages; void Start() { GameObject playerLivesGrid = GameObject.Find ("PlayerLivesGrid"); this.lifeImages = new List<GameObject> (); for (int lifeIndex = 0; lifeIndex < this.numberOfLives; ++lifeIndex) { GameObject lifeImage = Instantiate (playerLifeImage, playerLivesGrid.transform) as GameObject; this.lifeImages.Add (lifeImage); } } public void LoseLife() { if (!this.isInvulnerable) { this.numberOfLives--; GameObject lifeImage = this.lifeImages [this.lifeImages.Count - 1]; Destroy (lifeImage); this.lifeImages.RemoveAt (this.lifeImages.Count - 1); if (this.numberOfLives == 0) { Destroy (this.gameObject); } this.isInvulnerable = true; Invoke ("BecomeVulnerable", this.invulnerabilityDuration); } } private void BecomeVulnerable() { this.isInvulnerable = false; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class PlayerLife : MonoBehaviour { [ SerializeField ] private int numberOfLives = 3 ; [ SerializeField ] private float invulnerabilityDuration = 2 ; private bool isInvulnerable = false ; [ SerializeField ] private GameObject playerLifeImage ; private List < GameObject > lifeImages ; void Start ( ) { GameObject playerLivesGrid = GameObject . Find ( "PlayerLivesGrid" ) ; this . lifeImages = new List < GameObject > ( ) ; for ( int lifeIndex = 0 ; lifeIndex < this . numberOfLives ; ++ lifeIndex ) { GameObject lifeImage = Instantiate ( playerLifeImage , playerLivesGrid . transform ) as GameObject ; this . lifeImages . Add ( lifeImage ) ; } } public void LoseLife ( ) { if ( ! this . isInvulnerable ) { this . numberOfLives -- ; GameObject lifeImage = this . lifeImages [ this . lifeImages . Count - 1 ] ; Destroy ( lifeImage ) ; this . lifeImages . RemoveAt ( this . lifeImages . Count - 1 ) ; if ( this . numberOfLives == 0 ) { Destroy ( this . gameObject ) ; } this . isInvulnerable = true ; Invoke ( "BecomeVulnerable" , this . invulnerabilityDuration ) ; } } private void BecomeVulnerable ( ) { this . isInvulnerable = false ; } }

Now, we can already play the game and see the player lives in the screen. The last thing we are going to do in this tutorial is adding a Game Over message when the player loses all its lives.

Game over message

The game over message will be a text shown in the HUD Canvas when the player loses all its lives. So, let’s start by creating those texts in the HUD Canvas.

First, in order to easily enable and disable texts, we are going to group them in an object. So, start by creating an empty object called GameOverPanel. Then, we are going to attach the StartGame script to it. The StartGame script is the same we used in the Title Scene, and we are going to use it again here since we want to restart the game after the game is over.

Now we add two Texts (UI->Text) as children of the GameOverPanel. The first one will be called GameOverMessage and will show the Text “Game Over!”. The second one will be called RetryMessage and will show the Text “Press any key to retry”. You can change the font size to be the way you prefer.

Also, in order to make sure the HUDCanvas will appear above everything else, you can create another Sorting Layer called HUD and assign the Canvas to it.

Now, we disable the GameOverPanel by unchecking the box next to its name. We do this because we we want the GameOverPanel to appear only when the player loses the game.

Finally, we change the PlayerLife script to show the GameOverPanel when its game over. We do this by adding the GameOverPanel as an attribute of the script. Then, in the LoseLife method, when the current number of lives is equal to 0 we set the panel as active.

[SerializeField] private GameObject gameOverPanel; public void LoseLife() { if (!this.isInvulnerable) { this.numberOfLives--; GameObject lifeImage = this.lifeImages [this.lifeImages.Count - 1]; Destroy (lifeImage); this.lifeImages.RemoveAt (this.lifeImages.Count - 1); if (this.numberOfLives == 0) { Destroy (this.gameObject); this.gameOverPanel.SetActive (true); } this.isInvulnerable = true; Invoke ("BecomeVulnerable", this.invulnerabilityDuration); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ SerializeField ] private GameObject gameOverPanel ; public void LoseLife ( ) { if ( ! this . isInvulnerable ) { this . numberOfLives -- ; GameObject lifeImage = this . lifeImages [ this . lifeImages . Count - 1 ] ; Destroy ( lifeImage ) ; this . lifeImages . RemoveAt ( this . lifeImages . Count - 1 ) ; if ( this . numberOfLives == 0 ) { Destroy ( this . gameObject ) ; this . gameOverPanel . SetActive ( true ) ; } this . isInvulnerable = true ; Invoke ( "BecomeVulnerable" , this . invulnerabilityDuration ) ; } }

Now, you can try playing the game and dying, to see if the game over message is being correctly displayed. Also, check if you can restart the game.

And this concludes the single player features of our game. In the next tutorial we are going to make our game multiplayer, adding two players in a battle!