In this tutorial series, we'll show you how to recreate the classic Arkanoid (or Breakout) game in Unity, using Unity's native 2D tools. In each post, we'll focus on a specific part of the game; in this post, we'll handle the block mechanics and the creation of Prefabs.
Where We Left Off
In the previous tutorials, you set up the project and coded the behaviors of the paddle and ball. If you haven't completed the previous tutorials yet, we strongly recommend you do so before continuing.
Final Preview
Take a look at this demo to see what we're aiming for across the whole series:
And here's what we'll have at the end of this post:
Creating the Block Script
Right now, almost all of the base mechanics are ready. The missing part is related to the block object. Since blocks are an important and active part of the game, we need to create a custom script for it.
The block script must contain information regarding:
- Total hits that it can handle.
- Points to award upon breaking.
(Note that the number of hits and points can change according to the block color.) Based on this, let's give the block three variables:
- A variable to store the number of hits the block can take.
- A variable to store the score value of the block.
- A variable to store the number of times the block has already been hit.
The first two will be public
, since you may want to modify their values in the editor. The third will be private
, since it is for internal use only.
Thus, create a new script called BlockScript
. Define those variables and initialise the number of hits to 0
:
public class BlockScript : MonoBehaviour { public int hitsToKill: public int points; private int numberOfHits; // Use this for initialization void Start () { numberOfHits = 0; } // Update is called once per frame void Update () { } }
The next step is to detect when the balls hit a block. When this collision occurs, we check whether the block has been hit enough times to be destroyed, or whether it's still intact. For that, we can use a specific method called OnCollisionEnter2D
, which is called every time that a collision occurs to your object.
To check whether this collision is between the ball and the block, we must tag the ball object. In the editor, select the ball from the Hierarchy tab and then the Inspector. On the top of the Inspector, just under the game object name, there is a field called Tag that is currently defined as Untagged
. Click the button, and a drop-down menu will pop up with the different tag possibilities:
We want a specific tag for the ball, so hit Add Tag to create a new tag. Once you press the option a new interface appears:
For this tutorial, we will only focus on the Element 0 property. This defines the name of the tag, so type the name Ball
into it.
Now that you have the new Ball
tag, change the ball object's tag to Ball
.
Open the BlockScript
file so we can code the OnCollisionEnter2D
method. To identify the ball game object, check whether the game object tag is Ball
; if so, then the collision was between the block and the ball. Add the following code to your script.
void OnCollisionEnter2D(Collision2D collision){ if (collision.gameObject.tag == "Ball"){ } }
Now you can detect collisions with the ball. Upon every collision, we want to increase the record of the number of times the block got hit and, if the number the times the block got hit is the same as the maximum number of hits the block can take, destroy the block. For the latter, we can use the Destroy method.
The updated OnCollisionEnter2D
to do this looks like the following:
void OnCollisionEnter2D(Collision2D collision){ if (collision.gameObject.tag == "Ball"){ numberOfHits++; if (numberOfHits == hitsToKill){ // destroy the object Destroy(this.gameObject); } } }
Now add the BlockScript
to the Ball object (Hierarchy > Inspector > Add Component):
It is now time to check that everything is fine. Change the Hits To Kill value in the editor to 1
and Play the game. When the ball hit the block, the block should disappear.
Creating a Prefab
Now that the block mechanics are ready, we will populate the level. Since you will use several blocks for this, this is a good time to introduce Unity's Prefabs.
A Prefab is a reusable game object that can be inserted several times in the same scene. Basically, this means that if you want, for example, to change the value of points of the blue blocks, you don't have to do it to every single blue block on the scene: if you have a Prefab of a blue block, you just adjust the Prefab value and all the blocks on the scene will be updated.
To create a Prefab for the blue blocks, start by renaming the Block object to Blue Block. Next, create a new folder in the Assets folder called Prefabs. Now, drag the Blue Blocks object inside the new folder.
As you may have noticed, the cube icon on the top of the Inspector is now blue. Also, the name of the game object in the scene Hierarchy is also blue. This means that the block is now a Prefab. Quite simple, isn't it? From now on, selecting the Prefab and changing its values will apply every change you make to the parameters of all our blue blocks.
To test the Prefab, drag it from the folder to our Hierarchy tab. As you can see, now we have two blue blocks, but they share the same position, which was defined by the Prefab.
To have the two blocks in different positions, just select one of them from the Hierarchy tab and move it around the scene. This way, you change the values of that specific copy of the block and not all of the blocks.
You can Play the game and test the Prefabs.
Recreating Prefabs
Now you will create the remaining Prefabs for the other blocks (green, yellow, and red). The main steps for each one are:
- Create a new Sprite.
- Add the corresponding image.
- Add a Box Collider 2D.
- Add the BlockScript.
- Name it accordingly.
- Create a Prefab.
In the end you should have four different Prefabs (one for each type of block):
In order to make the gameplay more interesting, change the Hits To Kill for each type of block like so:
- Blue:
1
hit. - Green:
2
hits. - Yellow:
3
hits. - Red:
4
hits.
Add some blocks and Play the game; check that everything is running as expected.
Level Design
It is now time to create your first level. Using the Prefabs, populate the game area with several blocks. If you have been placing as many blocks as we did, your Hierarchy tab is probably overpopulated with block game objects! In order to keep your project well organised, let's create empty game objects for the four types of blocks, and then group the blocks by color:
At this point, your game is almost ready, your basic mechanics are implemented, and it should look similar to the next figure:
Score and Lives System
The score and life system is one way to test the players and introduce new dynamics into games. At this point, your game don't have any way for players to progress or lose. Let's change that now.
Open the PlayerScript
and add two variables: one for score, and one for the number of lives the player has. We'll let the player start the game with three lives and no points:
private int playerLives; private int playerPoints; // Use this for initialization void Start () { // get the initial position of the game object playerPosition = gameObject.transform.position; playerLives = 3; playerPoints = 0; }
We need a method that increases the number of points the player has every time they destroy a block. Create a new method called addPoints
to do this:
void addPoints(int points){ playerPoints += points; }
Now we have the new method that is expecting a value—but how it will receive it? There are too many blocks to make references to the player's paddle object in each...
The best way to solve this is to send a message from the block object to the paddle object. How we do that? Well, first you need to tag the paddle (Hierarchy > Inspector > Tag) with the Player tag.
With the paddle tagged, it's time to move to the block script, where we will change the method OnCollisionEnter2D
to send the points to the player object: before the block is destroyed, we'll search for a game object with the Player
tag using the FindGameObjectsWithTag
method; this will return an array of matching object, ans since there's only one object with that tag, we know that the object in position 0 of the returned array is the player's paddle object.
Now that you have your player reference, you can send it a message using the SendMessage
method. With this, you can call a specific method of the player object—in this case, the addPoints
method.
The next snippet shows you how all this works:
void OnCollisionEnter2D(Collision2D collision){ if (collision.gameObject.tag == "Ball"){ numberOfHits++; if (numberOfHits == hitsToKill){ // get reference of player object GameObject player = GameObject.FindGameObjectsWithTag("Player")[0]; // send message player.SendMessage("addPoints", points); // destroy the object Destroy(this.gameObject); } } }
The next thing we need to do is edit the Prefabs and give specific point values to each block type. You can use the following values:
- Blue:
10
points; - Green:
20
points; - Yellow:
35
points; - Red:
50
points;
Now, let's show these points and lives in the game interface. In the player script, create a method called OnGUI
. This method will present the GUI of your game; it is one of the basic methods to present information in the gaming area. (Note the case-sensitive characters).
To present the points and lives, we need to create a Label
with the desired text. In the PlayerScript
, add the OnGUI
method, and create this label in it:
void OnGUI(){ GUI.Label (new Rect(5.0f,3.0f,200.0f,200.0f),"Live's: " + playerLives + " Score: " + playerPoints); }
You can now Play the game and the label will be presented in the top left part of the screen. However, you have not yet programmed the lives and points display to update accordingly!
Using the same PlayerScript
, add the following TakeLife
method. The method will only subtract one life from the player pool each time it is called:
void TakeLife(){ playerLives--; }
Finally, move to the BallScript
, and, in the section where you check if the ball has fallen off the screen, send a message to the player object with the TakeLife
method. The complete snippet is presented below:
// Check if ball falls if (ballIsActive && transform.position.y < -6) { ballIsActive = !ballIsActive; ballPosition.x = playerObject.transform.position.x; ballPosition.y = -4.2f; transform.position = ballPosition; rigidbody2D.isKinematic = true; // New code - Send Message playerObject.SendMessage("TakeLife"); }
Play your game and verify that the score and lives system works as expected.
Next Time
This concludes the third post in the series. The basic game mechanics are all in place, so next time we'll add audio and new levels, and deploy the finished game.
If you have any questions or feedback on what we've covered so far, feel free to leave a comment below.