Chapter 3（3）：The Breakout Game
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).
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.
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).
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.
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:
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.
接着所有的砖块被定义好；最重要的数组是blocks，告诉你现在哪些砖块是可用的。Blocks数组在每一关开始前被初始化，blockPositions 和 blockBoxes自由的构造器中被初始化一次。blockPositions用来决定砖块渲染的中心位置，blockBoxes定义了碰撞测试的范围框。注意这些列表或位置值没有一个使用的是屏幕坐标。所有的位置数据都储存为0～1的格式：0代表左边或顶部，1代表右边或底部。这个方式能让游戏不依赖于分辨率，让渲染和碰撞检验变得容易。
The levels are generated in the StartLevel method, which is called at the beginning of the game and every time you advance one level:
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:
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.
Thanks to the SpriteHelper class the Draw method of the Breakout game is short and easy:
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):
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:
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.
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.
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.