Breakout: Board

While you “could” create all of your game boards by manually placing row after row of blocks, manually editing each as needed, there is an easier way. Well, at least it’s easy once you are comfortable writing code. In this lesson we will continue to practice and learn new tricks so that the computer will do the “hard” work on our behalf.

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

Create A Script

Let’s start with the Script this time. Create a new C# Script named “Board” and then copy the following code, and then save your script.

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

public class Board : MonoBehaviour
{
    [SerializeField] GameObject blockPrefab;
    [SerializeField] int columns;
    [SerializeField] int rows;
    [SerializeField] Vector2 blockSize;

    void Start()
    {
        for (int row = 0; row < rows; ++row)
        {
            for (int column = 0; column < columns; ++column)
            {
                var blockInstance = Instantiate(blockPrefab);
                blockInstance.transform.SetParent(transform);
                blockInstance.transform.localPosition = new Vector3(column * blockSize.x, row * blockSize.y, 0);
            }
        }
    }
}

About The Script

Hopefully you are beginning to get a pretty good idea about a lot of what is in this script, but we will review it all again just in case.

[SerializeField] GameObject blockPrefab;
[SerializeField] int columns;
[SerializeField] int rows;
[SerializeField] Vector2 blockSize;

We begin with creating four new fields. First is a data type of GameObject named "blockPrefab". This will hold a reference to the Block Prefab we created in the previous lesson.

Next, we have created two integer fields named "columns" and "rows". These indicate how many blocks we want horizontally (columns) and vertically (rows) and will be used to create a grid of Blocks.

The last field is a Vector2 named "blockSize" and is used to help control the spacing of the bricks.

void Start()
{
    // ...
}

We have used a "Start" method many times now. It runs once when the Scene first starts playing. The contents of this method are new though.

for (int row = 0; row < rows; ++row)
{
    // ...
}

This is called a "for loop". It begins with the keyword "for" followed by three statements.

  1. The first statement is executed once, just before the loop begins. We declare a local variable named "row" and gave it an initial value of 0.
  2. The next statement is a condition that determines whether or not to run a pass of the loop. In this case, it will repeat as long as our iterator is less than the number of rows we want to create.
  3. The final statement is executed after each pass of the loop. We use this to keep track of the number of times we have made a pass in our loop, by incrementing our local variable.
for (int column = 0; column < columns; ++column)
{
    // ...
}

We have "nested" this loop inside of the other loop's body. This means that we will run ALL passes of this loop, for EVERY pass of the outer loop. In other words, if I have 4 columns and 3 rows, the body of this loop will be run 12 times. That is exactly what we want to happen, but it is important to be thoughtful of your use of loops so that they don't become too taxing.

var blockInstance = Instantiate(blockPrefab);
blockInstance.transform.SetParent(transform);
blockInstance.transform.localPosition = new Vector3(column * blockSize.x, row * blockSize.y, 0);

We use the body of the inner loop as a location to create, and place blocks to form our Board's layout. The first line uses the reference to our Block Prefab, to create a cloned instance in our Scene. We keep a reference to the created instance in the local variable "blockInstance".

Next, we set the "parent" object of the new block instance to be the "Board" object. This keeps the Hierarchy window a little more organized, but also serves another purpose. When we place the Blocks, we can place them in a coordinate space that is relative to the parent. Then we can place the parent wherever we want, and the Blocks will still be placed as we expected.

Finally, we set the instance's Transform's "localPosition" to a newly calculated Vector3. Note how we are using the "current" value of the "column" and "row" variables, whatever they would be at the current pass of the loop, as multipliers on the width and height of the size of a block, so that we know where to place each block.

Create A Board

Head back over to Unity. Since we will be creating our Blocks via code, we don't need any in the scene initially. Go ahead and delete all of the Blocks from the scene.

Create an Empty GameObject named Board.

Attach the "Board" script. Now we need to assign the Block Prefab as a reference on the Script. Click the target icon for that field, and in the window that appears, make sure to select the "Assets" tab. From there, select the Block.

Set Prefab

Set the number of Columns to 4.

Set the number of Rows to 3.

Set the Block Size to X: 1, Y: 0.2.

Play the game now, and you will notice that the Board successfully creates 3 rows of 4 blocks. However, it may not appear where you intended. In my scene, the Board is placed at the origin. Each Block is also centered around its own origin. That means that the lower left corner of our Board is slightly below left of the center of the screen, and the upper right corner extends outside of our desired play space.

Normally, I would caution anyone against making changes while a simulation is playing, often times because you forget that you are in simulation mode and that when you stop your changes will be reset. However, if you are intentional about it, you can find some ways to take advantage of this ability.

Pause the simulation. Now modify the Transform of the Board until it is placed where you want it. You will be able to place it while it has all of the blocks instantiated, so it is easier to tell where a good location will be. I like "Position" of X: -1.5, Y: 3.5. When it is placed where you want it, click the three vertical dots icon and choose "Copy Component".

Copy component

Now stop the simulation. The Board location will be reset to wherever it had been, but we were expecting that. Click the same 3-dot icon, and this time choose "Paste Component Values".

Paste Component Values

The position of the board will now be updated to the place you had it during the simulation.

Update The Block Script

Wouldn't it be nice if we could let our Board modify the "health" of the Blocks. Maybe the higher the row, the more the health? At the moment, the only way to accomplish this is to manually place our own blocks and set the health in the Inspector. Open the Block Script, and we will fix that.

Take a look at where we define our "health" field:

[SerializeField] int health = 2;

The tag, "[SerializeField]" allows the Inspector to make changes to the data of our instantiated components. However, the field itself is not visible to other code. As a general rule, this is desirable, because it limits the number of locations where "changes" can be made to our object.

In this case, we want the "Board" to have access so that it can perform additional configuration while instantiating blocks. Change the field definition to look like this:

public int health = 2;

You will still be able to see health in the Inspector, but now other scripts can also see and even change the value of this field. Save your script.

Update the Board Script

Now open up the Board script. We will add some additional statements inside of the nested "for loop" body, just after we set the instantiated blocks position.

var block = blockInstance.GetComponent<Block>();
block.health = row;

Here we obtain a reference to the Block component that is attached to the instanced object we created. Then we set the value for the "health" of the block based on the current row.

Finished board

Summary

In this lesson we continued to expand on our programming skills. We can now create instances of our prefabs at run time, and used "for" loops to create and configure a grid of blocks automatically. We also learned how editing during a simulation can be useful, if you are careful.

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!

3 thoughts on “Breakout: Board

  1. Hey! So glad you’re back.
    Noticed what might be a slight typo:

    In the last code snippet, the of “block” for GetComponent should be capitalized? Also, I don’t think “” is meant to be there.

    Maybe it’s an issue with WordPress’ formatting?

Leave a Reply

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