Monday, March 21, 2011

Breakout Week 4: Building a Wall (Part 1)

I had a pretty busy week last week, okay that was mostly due to the fact that Dragon Age 2 came out and I spent several nights playing and not sleeping. Despite that I did manage to find some time to work on Breakout. The goal for this week was to get the wall drawn and handle the collisions of the ball against the wall. I did manage to get that all working, which I was pretty happy about, but I also came to the conclusion that the way I am handling the collisions is not very good, particularly for the wall.

Because there is a lot to go through with the collision handling and I also want to cover how I setup and draw the wall I'm going to split this week's post in two.  This post will cover the setup and drawing of the wall and it should be relatively short. I'll write another post around mid this week where I'll cover handling the collision of the ball against the wall, the problems with my approach and how I'm going to improve it.  So for now, lets get stuck into the setup of the wall.

As usual, we have a Wall class that handles the setup and drawing of the wall. Just like the paddle and ball it is created by the Breakout class. Here are the main instance variables of the wall.

class Wall {
   private const int bricksPerRow = 20;
   private const double wallHeightRatio = 0.20;
   private const int numRows = 7;
   private static readonly Color[] colors = {
     Color.Red, Color.Orange, Color.Yellow, Color.Green, 
     Color.Blue, Color.Indigo, Color.Violet
   };

   private readonly int screenWidth;
   private readonly int screenHeight;
   private float brickHeight;
   private float brickWidth;
   private Rectangle wallBounds;
   private Texture2D sprite;
   private List<Brick> bricks = new List<Brick>();



There are some constants at the top of the class that define the number of rows of bricks we will show and the number of bricks per row, height of the wall as fraction of the screen height and the color of each row, in this case it will be a rainbow. We also store the screen and the brick size and wall bounds which will be calculated in the constructor. And lastly there is our brick texture, which is just a white square and our list of bricks that make up the wall.

This is what the constructor does:

public Wall(int screenWidth, int screenHeight) {
    this.screenWidth = screenWidth;
    this.screenHeight = screenHeight;

    double wallHeight = screenHeight * wallHeightRatio;
    wallBounds = new Rectangle(0, (int)(wallHeight / 2),
                screenWidth, (int)(wallHeight + wallHeight / 2));
    brickHeight = wallBounds.Height / numRows;
    brickWidth = screenWidth / bricksPerRow;
}

The important parts to point out here is how we calculate the wall height as the fraction of the screen height defined by wallHeightRatio, the brick height as the wall height divided by the number of rows of bricks and the brick width as the screen width divided by the number of bricks per row. This means that the wall size will scale with the screen size and we can tweak to constants to get different wall configurations.

Lets move on to the InitialiseBricks method. This method is responsible for filling the list of bricks that make up the wall using the parameters we calculated in the constructor. It is called by the LoadContent method after we load the texture for the bricks.

Actually, before we look at that, here is the Brick class:

class Brick {
    public enum BrickState { ALIVE, BROKEN };
    private readonly int column;
    private readonly int row;
    private Rectangle bounds;
    private BrickState state;

    public int Row {
        get { return row; }
    }

    public Rectangle Bounds {
        get { return bounds; }
    }

    public BrickState State { get; set; }

    public Brick(int row, int column, float x, float y, float w, float h) {
        this.state = BrickState.ALIVE;
        this.row = row;
        this.column = column;
        this.bounds = new Rectangle((int) x, (int) y, (int) w, (int) h);
    }
}

As you can see it is really simple. It just stores the brick's row and column position, its bounding box and its state, i.e. whether it is alive or has been "broken" by the ball.  So now, back to the InitialiseBricks method:

private void InitialiseBricks() {
    for (int i = 0; i < numRows * bricksPerRow; i++) {
        int row = i / bricksPerRow;
        int column = i % bricksPerRow;
        bricks.Add(new Brick(
                row, column,
                column * brickWidth, 
                wallBounds.Top + (row * brickHeight), 
                brickWidth, 
                brickHeight));
    }
}

So what happens here is that, for each brick we need to create, which is determined by the number of bricks per row multiplied by the number of rows, we figure out it is row and column and the then its screen position. The screen x-position is done by multiplying the column position by the brick width and the screen y-position is determined by adding its row position time the brick height to the walls top position. Once this is done, our bricks list is full of all the bricks we need to draw the wall, which, of course, is done in the wall's Draw method:

internal void Draw(GameTime gameTime, SpriteBatch spriteBatch) {
    foreach (Brick brick in bricks) {
        if (brick.State == Brick.BrickState.ALIVE) {
            spriteBatch.Draw(sprite, brick.Bounds,
                  colors[colors.Length - brick.Row - 1]);
        }
    }
}

So all we need to do here is loop through the bricks and for each brick that is alive we draw it. We use the overload of SpriteBatch#Draw that takes a rectangle and colour. We grab the colour from the wall's colour array, using the colour at the index of the brick's row, but reversed so the top row is draw with the last colour and the bottom row is draw with the first. Passing in the brick's bounds as the destination rectangle will ensure that the right place in the screen is filled for that brick and our texture will automatically get scaled and coloured to fill the brick's rectangle.  That's it, here is what it looks like:


Isn't that pretty, well my 3 year old daughter thinks so at least.

That's it for part 1 of week for.  It was pretty light but I think worth covering for completeness.  Later in the week I'll put up the post on wall collision detection which will probably take me longer to put together than this did.  I hope this was useful to someone though. As usual the code is up on the Github, if you want to see this part of week 4, the pre-collision detection stuff, checkout the week4p1 tag.

No comments: