The Breakout Game


Alright, this chapter talked a lot about helper classes and it is finally time to put them to some use. I will skip the concept phase here and basically just say that Breakout is an abbreviation of Pong for just one player to play against a wall of blocks. Breakout was initially created by Nolan Bushnell and Steve Wozniak and released by Atari in 1976. In this early version it was just a black and white game like Pong, but to make it more “exciting” transparent stripes were placed over the monitor to color the blocks (see Figure 3-13).

image from book
Figure 3-13

You will actually go a similar road by reusing some of the Pong components and using the helper classes you learned about in this chapter. Breakout is a more complex game than Pong; it can have many levels and can be improved quite a lot. For example, Arkanoid is a clone of Breakout and there were many games in the 1980s and 1990s that still used this basic game idea and added weapons, better graphic effects, and many levels with different block placements.

As you can see in Figure 3-14, the BreakoutGame class is structured in a similar way to the Pong class from the last chapter. The sprite handling is missing because it is done now with help of the SpriteHelper class. Some other internal methods and calls are also replaced by some of the helper classes. For example, StartLevel generates a random new level based on the level value, and to generate the random values you will use the RandomHelper class.

image from book
Figure 3-14

Please also note that many test methods are visible in the class. This will be improved similar to the helper classes in the next chapter, which introduces the BaseGame and TestGame classes that make handling the game class and especially unit testing a lot easier and more organized.

Take a look at Figure 3-15 for a quick overview of the Breakout game you are going to develop in the next few pages. It is quite a lot of fun and certainly has a greater replay value than Pong, which is only fun with two human players anyway. The Breakout game uses the same background texture and the two sound files from the Pong project, but you also add a new texture (BreakoutGame.png) for the paddle, ball, and blocks and you have new sounds for winning a level (BreakoutVictory.wav) and for destroying blocks (BreakoutBlockKill.wav).

image from book
Figure 3-15

Unit Testing in Breakout

Before you start copying and pasting code over from the last project, using your new helper classes, and drawing the new game elements, you should think about the game and the problems you might run into. Sure you can just go ahead and implement the game, but it will be much harder to test the collisions, for example, which are the hardest part of this game. The unit tests help you out and provide you with an easy way to at least check all the basic parts of your game, and they also help you to organize your code and force you to program only what is really required. As always start with the most obvious part of the game and unit test it, then add more unit tests until you are done, and finally put everything together and test the final game.

Here is a quick overview of the unit tests for the Breakout game; check the full source code for this chapter for more details. You don’t have the TestGame class yet, so you still use the same kind of unit testing you used in the last chapter. Check out the next chapter on a better way to do static unit tests. You only have three unit tests, but they were used and changed a lot as I implemented the game.

  • TestSounds - Just a quick test to check out all the new sounds for your project. Press space, Alt, Control, and Shift to play the sounds. I also added a little pause after playing the next sound to make it a little easier to hear the sounds. This test was used to check out the new XACT project I created for this game.

  • TestGameSprites - This test was initially used to test the SpriteHelper class, but then all the code was moved to the Draw method of the game. The test was also used to initialize all the blocks in the game; the code was moved to the constructor, which is shown at the end of this chapter. This test shows you that it is not important to have a complicated test at the end because it is now only four lines of code, but the important part is to make your life easier while you are coding the game. Copy and paste useful parts of unit tests to your code as often as you need. Static unit tests also don’t have to be intact like dynamic unit tests for the helper classes because you only use them to build and test your game. When the game works you don’t need the static unit tests anymore except for testing parts of the game at a later point in time.

  • TestBallCollisions - Like in the previous chapter testing the ball collisions is the most useful unit test. Here you check if the collisions happen with the screen borders and paddle as expected. Only minor changes were required to get this to work. Then you can go to the more complicated block collision code, which is explained in more detail a bit later. You might even be able to think of more ways to test the collision and improve the game if you like. For example, it would make sense to trap the ball behind the wall of blocks and see if it destroys all the blocks correctly.

Breakout Levels

Because you are using many of the existing Pong ideas, you can skip the code that is similar or identical. You should focus on the new variables for now:

  /// <summary> /// How many block columns and rows are displayed? /// </summary> const int NumOfColumns = 14,   NumOfRows = 12; /// <summary> /// Current paddle positions, 0 means left, 1 means right. /// </summary> float paddlePosition = 0.5f; /// <summary> /// Level we are in and the current score. /// </summary> int level = 0, score = -1; /// <summary> /// All blocks of the current play field. If they are /// all cleared, we advance to the next level. /// </summary> bool[,] blocks = new bool[NumOfColumns, NumOfRows]; /// <summary> /// Block positions for each block we have, initialized in Initialize(). /// </summary> Vector2[,] blockPositions = new Vector2[NumOfColumns, NumOfRows]; /// <summary> /// Bounding boxes for each of the blocks, also precalculated and /// checked each frame if the ball collides with one of the blocks. /// </summary> BoundingBox[,] blockBoxes = new BoundingBox[NumOfColumns, NumOfRows]; 

First you define how many columns and blocks you can have at maximum; in the first levels you will not fill all the lines and only use 10% of the blocks. The paddle position is also a little bit easier than in Pong because you have just one player. Then you store the current level and the score, which is new. In Pong each player just had three balls and the game was over if all balls were lost. In Breakout the player starts at level 1 and works his way up until he finally loses a ball. You don’t have a high score here or any game font, so the level and score data is just updated in the title of the window.

Then all the blocks are defined; the most important array is blocks, which just tells you which block is currently used. The blocks are initialized before each level starts, whereas the blockPositions and blockBoxes are initialized only once in the constructor of the game; blockPositions is used to determine the centered position of the block for rendering and blockBoxes defines the bounding box of the block for collision testing. It is important to note that none of these lists or position values use screen coordinates. All position data is stored in the 0-1 format: 0 is left or top, and 1 is right or bottom. This way the game stays resolution-independent and makes both rendering and collision checking easier.

The levels are generated in the StartLevel method, which is called at the beginning of the game and every time you advance one level:

  void StartLevel() {   // Randomize levels, but make it more harder each level   for (int y = 0; y < NumOfRows; y++)     for (int x = 0; x < NumOfColumns; x++)       blocks[x, y] =         RandomHelper.GetRandomInt(10) < level+1;   // Use the lower blocks only for later levels   if (level < 6)     for (int x = 0; x < NumOfColumns; x++)       blocks[x, NumOfRows - 1] = false;   if (level < 4)     for (int x = 0; x < NumOfColumns; x++)       blocks[x, NumOfRows - 2] = false;   if (level < 2)     for (int x = 0; x < NumOfColumns; x++)       blocks[x, NumOfRows - 3] = false;   // Halt game   ballSpeedVector = Vector2.Zero;   // Wait until user presses space or A to start a level.   pressSpaceToStart = true;   // Update title   Window.Title =     "XnaBreakout - Level " + (level+1) +     " - Score " + Math.Max(0, score); } // StartLevel 

In the first for loop you just fill the whole block array with new values depending on the level. In level 1 the level value is 0 and you will only fill 10% of the blocks. RandomHelper.GetRandomInt(10) returns 0–9, which is smaller than 1 in only 10% of the cases. In level 2 this goes up to 20% until you reach level 10 or higher, where 100% of the level is filled. The game actually has no limit; you can play as long as you want.

Then you clear the lower three lines for the first levels to make the first levels easier. At level 3 only two lines are removed and at level 5 just one line is removed until you reach level 7 where all the lines are used.

Unlike Pong the ball speed vector is not immediately started for a new game. The ball stays on the paddle until the user presses space or A. Then the ball bounces off the paddle to a random location and the ball goes between the wall blocks, the screen borders, and the player paddle until either all blocks are removed to win a level or the player loses by not catching the ball.

Finally the window’s title is updated to show the current level number and the score the player has reached so far. In this very simple game the player only gets one point for every block he destroys; reaching a score of 100 is really good, but as I said before, there is no limit. Try to go higher and have fun with the game.

The Game Loop

The game loop in Pong was quite easy and contained mostly input and collision code. Breakout is a little bit more complicated because you have to handle two states of the ball. It is either still on the paddle and awaits the user to press space or you are in the game and have to check for any collisions with the screen borders, the paddle, or any of the blocks in the game.

Most of the Update method looks the same way as in the previous chapter; the second player was removed and a little bit of new code was added at the bottom:

  // Game not started yet? Then put ball on paddle. if (pressSpaceToStart) {   ballPosition = new Vector2(paddlePosition, 0.95f - 0.035f);      // Handle space   if (keyboard.IsKeyDown(Keys.Space) ||     gamePad.Buttons.A == ButtonState.Pressed)   {     StartNewBall();   } // if } // if else {   // Check collisions   CheckBallCollisions(moveFactorPerSecond);      // Update ball position and bounce off the borders   ballPosition += ballSpeedVector *     moveFactorPerSecond * BallSpeedMultiplicator;        // Ball lost?   if (ballPosition.Y > 0.985f)   {     // Play sound     soundBank.PlayCue("PongBallLost");     // Game over, reset to level 0     level = 0;     StartLevel();     // Show lost message     lostGame = true;   } // if      // Check if all blocks are killed and if we won this level   bool allBlocksKilled = true;   for (int y = 0; y < NumOfRows; y++)     for (int x = 0; x < NumOfColumns; x++)       if (blocks[x, y])       {         allBlocksKilled = false;           break;       } // for for if          // We won, start next level   if (allBlocksKilled == true)   {     // Play sound     soundBank.PlayCue("BreakoutVictory");     lostGame = false;     level++;     StartLevel();   } // if } // else 

First you check if the ball was not started yet. If not update the ball position and put it on the center of the player’s paddle. Then check if space or A was pressed and start the ball then (just randomizes the ballSpeedVector for you and bounces the ball off to the wall of blocks).

The most important method is CheckBallCollisions, which you will check out in a second. Then the ball is updated like in the Pong game and you check if the ball is lost. If the player did not catch the ball, the game is over and the player can start over at level 1.

Finally you check if all blocks were removed and the level is complete. If all blocks are killed you can play the new victory sound and start the next level. The player sees a “You Won!” message on the screen (see Draw method) and can press space to start the next level.

Drawing Breakout

Thanks to the SpriteHelper class the Draw method of the Breakout game is short and easy:

  protected override void Draw(GameTime gameTime) {   // Render background   background.Render();   SpriteHelper.DrawSprites(width, height);   // Render all game graphics   paddle.RenderCentered(paddlePosition, 0.95f);   ball.RenderCentered(ballPosition);   // Render all blocks   for (int y = 0; y < NumOfRows; y++)     for (int x = 0; x < NumOfColumns; x++)       if (blocks[x, y])         block.RenderCentered(blockPositions[x, y]);   if (pressSpaceToStart &&     score >= 0)   {     if (lostGame)       youLost.RenderCentered(0.5f, 0.65f, 2);     else       youWon.RenderCentered(0.5f, 0.65f, 2);   } // if   // Draw all sprites on the screen   SpriteHelper.DrawSprites(width, height);   base.Draw(gameTime); } // Draw(gameTime) 

You start by rendering the background; you don’t have to clear the background because the background texture fills the complete background. To make sure everything is rendered on top of the background, you draw it immediately before rendering the rest of the game sprites.

Next you draw the paddle and the ball, which is very easy to do because of the RenderCentered helpermethod in the SpriteHelper class, which works like this (the three overloads are just for a more convenient use of this method):

  public void RenderCentered(float x, float y, float scale) {   Render(new Rectangle(     (int)(x * 1024 - scale * gfxRect.Width/2),     (int)(y * 768 - scale * gfxRect.Height/2),     (int)(scale * gfxRect.Width),     (int)(scale * gfxRect.Height))); } // RenderCentered(x, y) public void RenderCentered(float x, float y) {   RenderCentered(x, y, 1); } // RenderCentered(x, y) public void RenderCentered(Vector2 pos) {   RenderCentered(pos.X, pos.Y); } // RenderCentered(pos) 

RenderCentered takes a Vector2 or x and y float values and rescales the positions from 0 to 1 (the format you use in your game) to 1024×768. The Draw method of SpriteHelper then rescales everything to the current screen resolution from 1024×768. It may sound complicated, but it is really easy to use.

Then all the blocks of the current level are rendered and again that is very easy thanks to the position you have calculated in the constructor of your game. Take a look at the code on how to initialize all block positions at the upper part of the screen:

  // Init all blocks, set positions and bounding boxes for (int y = 0; y < NumOfRows; y++)   for (int x = 0; x < NumOfColumns; x++)   {     blockPositions[x, y] = new Vector2(       0.05f + 0.9f * x / (float)(NumOfColumns - 1),       0.066f + 0.5f * y / (float)(NumOfRows - 1));     Vector3 pos = new Vector3(blockPositions[x, y], 0);     Vector3 blockSize = new Vector3(       GameBlockRect.X/1024.0f, GameBlockRect.Y/768, 0);     blockBoxes[x, y] = new BoundingBox(       pos - blockSize/2, pos + blockSize/2); } // for for 

The blockBoxes bounding boxes are used for the collision testing discussed in a second. The calculation of the position is also no big deal; the x coordinate goes from 0.05 to 0.95 in as many steps as you have columns (14 if I remember correctly). You can try to change the NumOfColumns constant to 20 and the field will have many more blocks.

Finally a small message is rendered on the screen with a scaling factor of two in case the player has won a level or lost the game. Then you just call the Draw method of SpriteHelper to render all game elements on the screen. Check out the unit tests in the game for how the rendering of the blocks, paddle, and game messages was developed. I started with the unit tests again and then wrote the implementation.

Collision Testing

The collision testing for the Breakout game is a little bit more complicated than just checking the paddles and the screen border in Pong. The most complicated part is to correctly bounce off the blocks the ball hits. For the complete code check the source code for this chapter.

Like the last game you have a ball with a bounding box, screen borders, and the paddle. The blocks are new and to check for any collisions you have check all of them each frame. See Figure 3-16 for an example collision happening with a block in the game.

image from book
Figure 3-16

Take a closer look at the basic collision code with the blocks. The collision with the screen border and paddle is pretty much the same as in the Pong game and is checked with the help of the TestBallCollisions unit test. To check for collisions with the blocks you iterate through all of them and check if the bounding box of the ball hits the bounding box of the blocks. The actual game code is a little bit more complicated to check which side of the bounding box you hit and in which direction you have to bounce off, but the rest of the code and the general idea is still the same.

  // Ball hits any block? for (int y = 0; y < NumOfRows; y++)   for (int x = 0; x < NumOfColumns; x++)     if (blocks[x, y])     {       // Collision check       if (ballBox.Intersects(blockBoxes[x, y]))       {         // Kill block         blocks[x, y] = false;     // Add score     score++;     // Update title     Window.Title =       "XnaBreakout - Level " + (level + 1) + " - Score " + score;     // Play sound     soundBank.PlayCue("BreakoutBlockKill");     // Bounce ball back     ballSpeedVector = -ballSpeedVector;       // Go outta here, only handle 1 block at a time       break;     } // if } // for for if 




Professional XNA Game Programming
Professional XNA Programming: Building Games for Xbox 360 and Windows with XNA Game Studio 2.0
ISBN: 0470261285
EAN: 2147483647
Year: 2007
Pages: 138

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net