Upgrade Your Game: Space Blitz (Visual C#)
- Posted: Oct 31, 2006 at 3:23PM
- 9,353 views
- 9 comments
Loading user information from Channel 9
Something went wrong getting user information from Channel 9
Loading user information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
|This article enhances Space Blitz sample application by adding new features.|
Time Required: 1-3 hours
Visual C# Upgrade Your Game Series
Welcome to the Upgrade Your Game series of tutorials. There are four tutorials, each covering how to write a simple computer game using Visual Studio Express products. Though the games are simple, each one teaches some game development techniques and builds on the last to improve your skills. The skills can be applied to more complex games including 3D games using Microsoft DirectX.
You probably noticed that the animation in the previous game was not as smooth as you would have expected for such a simple game with so little going on. The main reason for this is because of my choice of picture boxes for the sprites. As I mentioned, this is not something that is recommended because there is a lot of overhead that comes with Windows controls. It really is overkill to use a Windows control just to draw a rectangle or any other graphic on the screen. However, it served its purpose for making a simple game with only a few lines of code.
For the third game you will take on another classic, a Space Invaders type game. Because this game has sprites that are very clearly not rectangles, and because there are a lot more of them to draw, this example will implement the sprites using bitmap graphics.
Since most developers are not graphically inclined, this is the time that your game development team needs to grow. Most games these days use graphics that are far more complex than the original Space Invaders or Pac-man and it's just not realistic to create them yourself. You may be lucky enough to find some royalty-free artwork on the Internet, but if you intend to sell your game, be sure to check the specific licensing for that artwork. The safest thing to do, if you are not rich enough to hire someone to produce your art, is to make friends with someone who can.
Note: This documentation assumes that you have a basic knowledge of programming concepts and the Visual C# environment. You can learn more about these topics in the product documentation by clicking Help on the menu bar, and then clicking Contents, Index, or Search. You can also access Help by positioning the mouse cursor on language keywords or user interface elements such as windows or dialog boxes, and pressing F1.
Once your project is loaded into the Visual C# environment, you can compile and run the program in one step.
To build and run SpaceBlitz:
The game launches and you can control the base ship using the left and right arrows. Use the spacebar to fire.
If you are new to 2D game development, check out the 2D Game Primer (C#).
Drawing a bitmap to the screen only requires a few lines of code. Using the code from the TinyTennis sample, you can add a new member variable to store the bitmap and a new constructor that takes the file name of the bitmap file. The bitmap constructor takes a file name, which means it's very easy to load and store.
protected Bitmap _frame;
public Sprite(GameState gameState, float x, float y, string filename)
//Load the bitmap
Drawing the bitmap is almost as trivial. Drawing to a Windows Form is done using the GDI graphics API, which is contained in the System.Drawing namespace. All GDI drawing is done to a Graphics object. This means that you have to provide the graphics object to the Sprite.Draw call. Fortunately, the Paint event gets the Graphics object for the window as part of its EventArgs, so this turns out to be simple to get hold of. Drawing the bitmap to the graphics surface is also a single line of code using the DrawImage method.
public override void Draw(Graphics graphics)
//Draw the first frame at the current point
graphics.DrawImage(_frame, Location.X, Location.Y, Size.Width, Size.Height);
All of the movement code comes right across from the TinyTennis sample. The aliens move based on a velocity assigned to them. The base ship moves based on a velocity set by the user pressing keys on the keyboard. Once a velocity is set, the Sprite base class will continue to animate as time goes by. In addition, the rectangle collision code can be used to check for collisions between missile, aliens, base ships, and saucers. Reuse is great!
Animation isn't just about moving things around the screen. Our aliens need to waggle their arms, legs, and antennae. If you look in the graphics folder, you will see that the artist provided two poses for each alien. To get the animation effect, you simply have to draw the images one after the other. Since this game only has two frames of animation, I have created a constructor that takes the two file names and stores them in a list of bitmaps. If you are new to .NET 2.0, generics may be new to you so you may want to go and read an introductory article. In the Draw call a decision has to be made which frame to draw and this is done by using the CurrentFrame member variable. CurrentFrame will be set in one of the Update() methods, usually in an inherited class, depending on how fast or slow the animation is to be. For this game, only the aliens need image flipping animation so the member is left at its default value of 0 for all of the other sprites.
Notice that just like the movement animation, the flipping speed is tied to the game time. Almost everything that happens in the Update() methods will be tied to the time, so that, no matter which machine you run on, the timing will stay exactly the same.
protected List<Bitmap> _frames = new List<Bitmap>();
public Sprite(GameState gameState, float x, float y, string filename1, string filename2)
//Load the 2 animation frames
public override void Draw(Graphics graphics)
//Draw the correct frame at the current point
graphics.DrawImage(_frames[CurrentFrame], Location.X, Location.Y, Size.Width, Size.Height);
For the bases, checking the rectangle as you did in TinyTennis will not be sufficient because the shapes need to slowly get destroyed by the base ships' bullets and the aliens' missiles. This means that the shape to collide against becomes non-rectangular very quickly. In this case, it's necessary to check the bitmap itself to see if the bullet has hit a non-black pixel. For a perfect collision, you need to check every pixel of the first sprite against the second sprite which, as you can imagine, gets slow very quickly. So, just like the bounding shape checks were an approximation to the perfect check, you can look for other ways to approximate this kind of collision. Since you know that our bullets only move in the Y direction, there is no need to check for anything other than the leading edge of the bullet, which cuts the pixel checks down to 6 pixels. But what if you just check the 2 leading corner pixels? You would miss any collisions that happen between them, but that's just 4 pixels and, for this game, this is an acceptable performance/accuracy trade off.
public bool CheckPixel(PointF point)
//Check this pixel and one 6 to its right (the size of missiles and bombs) to see if we have hit anything
return (isPixelOpaque((int)(point.X - Location.X), (int)(point.Y - Location.Y)) || isPixelOpaque((int)(point.X - Location.X + 6), (int)(point.Y - Location.Y)));
private bool isPixelOpaque(int x, int y)
//If the pixel is out of range that counts as black
if (x < 0 || x > _frames.Width - 1 || y < 0 || y > _frames.Height - 1) return false;
//Otherwise check for anything that is not black
return (_frames.GetPixel(x, y) != Color.FromArgb(0, 0, 0));
sprite.CurrentFrame = (int)(((gameTime * _speed) / 50f) % 2f);
Now that you can collide on a per pixel basis with the bases, the sprites need to be updated so that they appear to get damaged over time. It's important to do this not just because you draw the sprites in every frame, but because a user could switch applications or minimize the game. When the game is restored, you need to be able to redraw the whole screen.
Since each sprite stores its own copy of the bitmap, it's possible for the Base sprite to set certain pixels within itself to black so that they slowly get removed. Setting the pixels to black means that any future missile will not pass the per pixel test. For this game, I have chosen to draw a black circle of a random radius at the point of collision.
public void Erode(PointF point)
//Draw a black circle in the bitmap over the point of intersection
using (Graphics graphics = Graphics.FromImage(_frames))
graphics.FillEllipse(Brushes.Black, point.X - Location.X - _sizeX / 2 + 3, point.Y - Location.Y - _sizeY / 2, _sizeX, _sizeY);
The gameplay here, though not as advanced as in any modern game, is still something that needs to be managed properly. The game needs to be able to detect when all of the aliens are destroyed and recreate a new wave of aliens; it needs to randomly select an alien to drop a missile; and it needs to randomly add the bonus flying saucer to the screen.
If you are not careful with your code structure you will find that these sorts of tasks get randomly scattered around your code. For this game, I have chosen to encapsulate as much of this logic in one place: the GameState class. In the TinyTennis sample, this was used to store the scores, but in this game you will use it to store the scores, the level, whether you are currently dropping a missile, how many aliens are left, and several other things that pertain to the current state of the game.
You will also remember that for the collisions in TinyTennis you had to pass in references to the bats to the Ball object. Obviously this isn't something that is scalable beyond a handful of objects, so for this game the GameState class stores collections of all of the game objects and is passed into each object during creation. This means any object has access to any other object if it needs to query it for collisions or any other reason.
Take a look at the GameState object (there is too much code to post in the tutorial), and see how all the mechanics of the game play function.