Breakout: Blocks

Now that we can hit a ball with a paddle, we need something to aim for. Breakout style games have an array of blocks along the top of the board for the user to destroy. We will create some blocks, then create something called a “Prefab” that makes it easier to apply changes to multiple instances.

Continue following along with your existing “Breakout” project. Otherwise, you can start from this sample project here.

Create A Block

Create a new static rigid body, named “Block”. Set the Transform “Position” to X: -1.5, Y: 4, Z: 0 and to “Scale” of X: 1, Y: 0.2, Z: 1.

Block transform settings

As you might have guessed, we will need a bunch of Blocks spanning across the window, so duplicate the block 3 times, and move each to the side to fill a whole row. They will have x positions at: -0.5, 0.5, 1.5.

Edit Blocks

Now, let’s suppose you wanted all of the blocks to be yellow, instead of white. How do you go about it? You could edit them one by one, but that would not scale well, especially if you had dozens or more blocks to fix.

Unity does actually have a feature that allows you to perform a bulk edit. For example, you can select one Block in the Hierarchy window, and then shift + left-click the last block, and it will select all of the blocks in between. Now if you edit the “Color” of the Sprite Renderer, you will see that all of the selected blocks get the same change.

Unfortunately, some of the changes that you might want to make will not be supported by a bulk edit, such as in custom editors. Other changes may not be as convenient, such as if you need to edit some sort of nested hierarchy. A prefab is a nice solution to help overcome these challenges.

Create A Prefab

Left-click and drag the original Block from the Hierarchy window to the Project window, and drop it onto the “Assets/Prefabs” folder. This will both create a Prefab, and make sure it is organized in the correct location.

Create a block prefab

Note that the original Block now appears differently in the Hierarchy window. It has blue text, and the cube icon next to its name is filled. In addition, there will be a chevron ‘>’ to the right of its name.

Delete the other three non-prefab blocks that you had added. Then replace them using Prefab instances instead. You can do this either by duplicating the “Block”, or by dragging and dropping from the Prefab in the Project window, to either the Hierarchy or Scene windows.

Edit A Prefab

To edit a Prefab, either select it in the Project window, or click the chevron to the right of the name of a prefab in the Hierarchy window. Either way, the Inspector window will be showing values of a Project Asset, and any changes made, will effect all instances of the prefab. You do NOT need to have selected all of the blocks – the change will apply in bulk automatically.

If you began editing a prefab from the Hierarchy window, just click the back arrow to exit when you are done.

Exit prefab edit mode

Note that it is possible to have instances of a Prefab with unique settings. For example, select any one of the Blocks in your scene and edit its Sprite “Color” as you would for any other GameObject. The change you make in this way will override the default Color specified by the Prefab.

Now look in the Inspector window underneath the Object’s name. A new row of “Prefab” specific buttons are available. Tap the “Overrides” button and you will have options to either “Revert All” (reset the prefab to look like it originally did) or to “Apply All” (update the prefab asset itself from these changes).

Prefab overrides

Create A Script

Let’s create a new script for our Block, named “Block”. This time we will add functionality to make the block change color as it gets hit, and then once it has run out of “health” it will be destroyed. Copy the following code, and then save your script.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Block : MonoBehaviour
{
    static Color[] colors = {
        Color.yellow,
        new Color(1f, 0.5f, 0f), // Orange
        Color.red
    };

    [SerializeField] int health = 2;

    void MatchColor()
    {
        var colorIndex = Mathf.Clamp(health, 0, colors.Length - 1);
        GetComponent<SpriteRenderer>().color = colors[colorIndex];
    }

    void Start()
    {
        MatchColor();
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        if (health > 0)
        {
            health--;
            MatchColor();
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

About The Script

The first field defined in this class looks pretty different from other fields you’ve seen so far.

static Color[] colors = {
    Color.yellow,
    new Color(1f, 0.5f, 0f), // Orange
    Color.red
};

First, there is a special key word “static” at the beginning. This means that the field isn’t owned by an “instance” of the class, but rather is shared by all of them. Why does that matter? Remember that computers have a finite amount of data they can hold. If each block had a “different” array of colors they could change through, then the field should NOT be static. If they always share the same value though, then there is no point in having multiple copies of this data.

The data type has brackets after it, “Color[]”. This means that we are creating an “Array” of the type of data that is mentioned. An array is a collection of items of the same type.

We provide the values of the array of colors inside a comma separated fashion, also inside of brackets (Note that they are squiggly, not square). There are actually several syntax options for initializing an array, this one is just my preference. Ultimately, I have created a collection of the three colors: yellow, orange and red.

[SerializeField] int health = 2;

Our next field looks more similar to other fields we have declared already, except that it also has a new data type. An “int” only holds whole numbers, such as that you count with. No decimals allowed.

The “health” variable will be used to indicate the number of times that a block can be hit before being destroyed. The use here may be slightly un-intuitive, because the block requires 3 hits to be destroyed. If it helps, think of it this way, once the health reaches “0”, then there are zero hits left where it can remain alive. The reason it is done this way is related to the way we access values in an array of colors. Array access is indexed from 0, in other words, the “first” item in an array is at location 0, not 1.

void MatchColor()
{
    // ...
}

Until now, the methods we have been adding to our class were all inherited, and invoked by Unity. This is our first custom method. There are several reasons to make your own methods, such as to make long chunks of code easier to understand. Even in this example, the two statements inside the method are not as easy to interpret as the name of the method itself.

Another reason to make your own method is because it is a good practice to keep code “DRY” – that means “Dont Repeat Yourself”. There are two cases where we want the block to perform logic that will cause it to change color so that the color stays in sync with its amount of health, and rather than repeating the same statements in both locations, we can more easily invoke the method in both places.

var colorIndex = Mathf.Clamp(health, 0, colors.Length - 1);
GetComponent<SpriteRenderer>().color = colors[colorIndex];

Inside the “MatchColor” method we first create a local variable named “colorIndex”. An “int” data type can technically hold values less than zero, as well as values much greater than the number of items in our array. If you ever try to access an item in an array using an “out-of-bounds” index, then you will crash your game. Therefore I use the “Clamp” method to clamp the “health” field of our class between ‘0’ (the first possible index of an array) and the last index of the colors array. Note that I provide the “max” parameter as a calculated result. The “colors.Length” will actually return 3 because there are 3 colors, but the last “index” of the array is 2 since arrays are indexed from zero.

void Start()
{
    MatchColor();
}

You should remember what a “Start” method is. This method will run when the scene begins, and will call our “MatchColor” method. Now, you could have blocks of varying amounts of health when the level starts, and they will still begin with the correct color.

void OnCollisionEnter2D(Collision2D collision)
{
    // ...
}

A MonoBehaviour has a lot more functionality than is included in the template by default. Here we have added another, “OnCollisionEnter2D”, which is called whenever the physics system detects a collision. The method accepts one parameter, which has a data type of “Collision2D”. This type of data holds a variety of potentially helpful information such as the velocity of the collision, and what other object was collided with.

If you wish to use the parameter in the method, you would use the parameter name, which in this case is “collision”. You can think of a parameter as being similar to a locally scoped variable. We wont be needing it in this example.

if (health > 0)
{
    // ...
}
else
{
    // ...
}

Just inside “OnCollisionEnter2D” we use something called a “condition” to control the flow of our code. This type of condition is an “if/else”. In this case, “if” the Block has a health value that is greater than 0 at the time of the collision, then we will perform the statements within the body of the “if”. Otherwise, we perform the statements within the body of the “else”.

health--;
MatchColor();

In the “true” case of the “if” condition, we do two things. First, we decrement the value of health. This syntax is a shorthand way of writing “health = health – 1;”

The second thing we do is call our “MatchColor” method, so that the color will update according to the new health value.

Destroy(gameObject);

In the “false” case of the “if” condition we delete the block from the Scene. This is accomplished with the “Destroy” method, while passing a reference to “gameObject”. Both “Destroy” and “gameObject” are “understood” thanks to our script inheriting from MonoBehaviour.

Connect The Script

Head back to Unity, then attach the Block script to the Block Prefab. Make sure to do this to the Prefab itself, not merely an instance of the Prefab. Now press Play and observe. All of the Blocks should Start with a red color. If they get hit, they will change to orange. If they are hit again, they will be yellow. If they are hit a final time, they will disappear.

When you are satisfied, Stop the simulation.

Save your scene if you haven’t already.

Summary

In this lesson we created Blocks as targets to hit with our Ball. We learned how to bulk-edit objects, and then learned how to create Prefabs from objects for even more control and better reuse. We continued to learn more about scripting, and this time worked with collision events, custom methods, new data types like array and int, and learned what static means.

If you’d like you can also download the completed project for this lesson here.

If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!

Leave a Reply

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