- Levels / Scoring and Lives
- Polishing the Paddle Bouncing / Where to Go from Here
Polishing the Paddle Bouncing
If you've been testing the game for a while now, you may have noticed that there are a couple of issues with the ball bouncing off the paddle.
Sometimes the ball will bounce so it goes nearly straight vertically or horizontally, which is not a desirable behavior. You want to limit the range of the ball direction so that it's constrained between the grey arcs in the following figure:
To do this, you'll need to use more vector math; specifically, you'll use the dot product. I won't go into full detail on the dot product here, but one of the big uses of the dot product is that it can be used to find the angle between two vectors.
In this case, you want to see whether you're within 25 degrees of a particular vector, which roughly corresponds to a dot product of 0.9 (provided both vectors are unit vectors).
As with other vector operations, there is a built-in Vector2.Dot function in MonoGame. So you could check whether a ball's direction is too close to the (1,0) vector, and if it is, set the direction to be a steeper vector (I selected one that's rotated approximately 25 degrees from (1,0)).
The code for this is the following:
float dotResult = Vector2.Dot(ball.direction, Vector2.UnitX); if (dotResult > 0.9f) { ball.direction = new Vector2(0.906f, -0.423f); }
You could then do something very similar for the -UnitX axis, with the modification that you'll use (-0.906f, -0.423f) as the new direction. There's an additional issue for the -UnitY axis, however.
Although the dot product can tell you if you're within 25 degrees, it can't tell you whether it is clockwise or counterclockwise. To figure out which way to rotate, you need to use the cross product.
Because the cross product only works with 3D vectors, you have to construct two 3D vectors that have their x/y components corresponding to the 2D vectors in question, and a z component of zero.
If the result of the cross product has a negative z component, it means that the rotation is clockwise and vice versa.
Putting this together, you can write code like this that will select the correct direction to travel in:
dotResult = Vector2.Dot(ball.direction, -Vector2.UnitY); if (dotResult > 0.9f) { // We need to figure out if we're clockwise or counter-clockwise Vector3 crossResult = Vector3.Cross(new Vector3(ball.direction, 0), -Vector3.UnitY); if (crossResult.Z < 0) { ball.direction = new Vector2(0.423f, -0.906f); } else { ball.direction = new Vector2(-0.423f, -0.906f); } }
Once this code is in, if you run the game you should no longer be able to make the ball move at too steep an angle, which definitely improves the feel of the paddle bouncing pretty substantially.
Even though Breakernoid is a relatively simple game, you still ended up using a decent amount of vector math. This should give you an idea of how fundamental vector math is to game programmers—it's a very important part of the toolkit that you need to understand to consider yourself a competent game programmer.
Where to Go from Here
The code for the completed game is available here: Breakernoid_final.zip. It's a very fun game, but there are a few additional things you could do to further build out the game further.
Add More Power-Ups
From a code standpoint, adding more power-ups isn't that hard. You have an enum that tracks all the power-up types, so you could add more to that.
One power-up that's fun is a laser power-up that lets you shoot lasers to take out blocks. However, keep in mind that if you want to use new images/sound effects, you'll need to have Visual Studio 2010 and XNA 4.0 in order to build new .xnb files.
If you want to make modifications to existing assets (such as a different color power-up pill), the original source PNG and WAV files are in the "assets" directory in the code link.
Add a High Scores List
Much like the way you serialized the levels, you could serialize a high score list to keep track of the top X scores. Then on game over, if the player scores high enough to make the list, you could ask them to type in their initials.
At a minimum, you'd want to display the high scores on game over, but you could potentially add a high scores menu that the player can access.
Create a Level Editor
The XML level file format is pretty simple, but manually editing XMLs to create new levels is time-consuming and a chore. What would be much more efficient is to create a level editor mode that let you lay out the blocks in the level.
MonoGame can respond to mouse input, so it would be possible to use the mouse to select and place blocks. You can then serialize this level to a new XML file, so it can be loaded by the game.
And Beyond!
As I mentioned at the start of this series, MonoGame can be used for 3D games as well as 2D games. So even though this tutorial was for a 2D game, there are quite a few projects out there that show off more advanced features.
One such game is a 3D tower defense game I made as one of the sample games in my book. You can get more information on downloading the source code as well as build instructions on my website here.
Conclusion
I hope you enjoyed this tutorial on creating Breakernoid. I tried to cover most of the fundamental things you need to know to begin a journey in programming games, so hopefully you learned a lot, too!
If you liked what you learned from this series and want a more in-depth discussion of game programming, you should check out my book, Game Programming Algorithms and Techniques.