Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Asteroids without DirectX

0.00/5 (No votes)
27 Mar 2002 1  
An implementation of the classic asteroids game using 2D Drawing classes

Sample Image - asteroids.gif

Introduction

Good news everybody! I have invented an asteroids game with a slight twist; one that doesn't rely on Direct X coding.

The zip now contains all the necessary files, my apologies to those that downloaded the incomplete version.

This is my first article, so please excuse any untidiness. First off, a little background about why I wrote this code and article, and my coding style. I have always loved games, and awhile ago, decided that writing a version of the classic space invaders game would be a good way of teaching my fellow Visual Foxpro programmers about object oriented design and coding. I also came to the conclusion some time ago that perhaps the best way of learning a new coding language was to separate the learning process out. A typcial path into learning a new language consists of about 3 parts:

  1. learning the IDE
  2. learning the syntax
  3. coding the solution to some problem.

If you can remember which of these three elements you are performing when you use a package to write code, learning becomes easier. To make things easier still, if you don't have to figure out the solution to a problem, you can maximize on points 1 and 2. Therefore, I decided that to learn C# and its IDE, it would be best to reinvent the wheel that is space invaders, since I already knew at least one solution.

Well, I had already cracked a rudimentary space invaders, so half way through, I broke my own rule and converted it to be asteroids instead. Sometimes, you just can't resist changing the scope ;-)

The purpose of the project was to get a good introduction to C# - the aim was not to write perfect code or OO design. That is my disclaimer. My production code (for money) is always better formatted and structured that this, honestly. No, really. Heh heh.

The purpose of my article is thus: to show the world my child. I am very pleased with it, especially since this time around I have cracked keyboard handling for games (thanks to Lutz Roeder and his port of the old game Digger) and that with the exception of Hello World, this is my first real C# code experience. Secondly, sharing is good. Thirdly, people may learn from it and finally, I would like other people to suggest / code alterations for the dodgy aspects of this code. I would very much appreciate constructive feedback - preferably with code examples. Don't pull any punches guys, I want to learn.

For those observant enough, the slight twist mentioned above is this: the game has a 1 in 10 chance of the game play changing each time all the asteroids are destroyed - asteroids may bounce of the edges of the screen instead of wrapping to the other side. I call this pinball or dodgem mode. Next, there is also a 1 in 10 chance of asteroids being able to destroy each other (suicide mode), so a player needs to be quick to accumulate points.

The following is a list of issues outstanding and suggestions for moving the game forward in a learning context.

  • Use of regions for collision detection.
  • Class hierarchy is a bit of a kludge (or shonky as our colonial brethren would say). In need of some refactoring, moving bits and bobs up or down the hierarchy.
  • Re-jig the class members from mainly public declarations to mostly private scope.
  • Refactor playingField to be three new classes: canvas, observer (to include collision detection), sound system
  • Text message subclass of Player to cope with score and other messages.
  • XML High score table
  • Database high score table
  • Serialisable high score class
  • Better understanding of ActiveX wrapper class along with Multimedia control.
  • Implement better movement physics of the form y=mx+c, in order to have better control of the movement speed of game elements.
  • Recode the existing missile physics to work using real numbers instead of just whole numbers (thus increasing accuracy)
  • Rename the player class to be something like gameCharacter to avoid confusion with human player.
  • I may be using the concept of static methods / fields incorrectly. This is the first time I have encountered them (Visual Foxpro doesn't have them).
  • Random UFO character that can destroy player / asteroids.
  • Network two player version (coop or one at a time).
  • Configurable keyboard controls
  • Game over code is very gadgy.
  • Get rid of the clich�d triangular space ship (heh heh)

Running the code

Before building and running the code, you will need to provide the following .wav files:
  • bigasteroid.wav // for death of big asteroid and missile explosion
  • mediumasteroid.wav // for death medium asteroid
  • smallasteroid.wav // guess
  • player_missile.wav // for launch of missile
  • acid bass drum.wav // for start of each new level (make_asteroids)

and alter the following field in the player class:

protected string soundPath = "d:\\my documents\\Visual Studio Projects\\asteroids\\Asteroids\\"; // root path to the sound files

I have found that borrowing wav files from the OS version of Pinball can be quite satisfactory for sound effects.

Controls for play

  • z - rotate left
  • x - rotate right
  • j - thrust
  • k - fire
  • L - dead stop
  • p - pause game

Overview

A form contains a playing field and a timer. The playing field renders and observes all the game characters The timer triggers movement and animation

Description of Classes

PlayingField Class

This class achieves at least 2 objectives.

  1. To fulfill the observer role (Observer Pattern, from Design Patters, Gamma, Helm, Johnson & Vlissides) and handle the collision detection
  2. To render the characters to the screen

In retrospect, it would be a good idea to separate this functionality as they are pretty much unrelated.

Collision Detection

Overall, this looks more complicated that it should. There are a lot of null checks in the code due to my use of arrays instead of arraylists, which I did not know about to start with. An array list will resize and reindex itself when elements are added / removed, so this would have help reduce the need for null checking, but would probably have added complexity to working out which controls have been checked for collision and rendering, as elements indexes would be altered transparently by the arraylist class.

The collision detection is reasonably optimized, and there is a flag that can be set to reduce this optimization for experimental purposes. At the top level of detection, each character's bounding rectangle is checked for intersection (and this is the layer that may be turned off, it makes a significant difference when there are lots of asteroids). The next layer of detection uses point arrays and graphics paths (graphicsPath.isvisible) to work out whether a point in a targetA intercepts the graphicsPath of targetB. Traditionally, I would guess this is usually achieved using Regions, however, due to ignorance and lack of help, I could not get the beta c# version to work properly with Region specific code. For any collidable element, there must therefore be a graphics path member and an array of points that delimits the outline of the object.

Drawing the characters

My initial concept was for each character in the game to draw itself. When I tried this, I found that could not get rid of the characters' background area, and thus any time a character overlapped with another, part of one would be obscured. This looked far to primitive, so I tried various means of removing the background, including setting back colours to transparent etc etc. I failed, so decided that a possible solution was to have a single control responsible for drawing all characters, and thus, all characters would be drawn on a large, single background.

In the code, I have called the class PlayingField, but Canvas would have been more appropriate. PlayingField has an array called PlayerArray. This is used to store all the elements of the gameplay (except the score) that require drawing / animation. For each tick of a timer, this array is iterated and its elements drawn to the screen. Each element for drawing must have an animation array - this array is used to store graphics paths and the pens or brushes used to render them.

A simple character such as an asteroid typically only has 2 elements in its array, a graphics path constructed from a polygon, and a brush or pen to fill it. A complex character such as a missile explosion may have more than 2 elements, in this case, an explosion consists of a circle to be drawn with a pen (outline only), and a smaller circle to be drawn with a brush (filled).

Further coding could produce a text message class, and this could work along the same lines. It could then be used for displaying the score or any other messages to be written / animated on the screen, for example, when in suicide mode or pinball / dodgem mode.

The Player Class

The parent class for any game element that requires movement or animation or collision detection. As stated earlier, this needs alteration so that some of the methods are moved down the hierarchy tree (for example, there is no point an asteroid having a launch_missile method, and only a human player needs to care about lastKeyPressed).

Most of the fields are self-explanatory and / or heavily commented in the code, so I won't bother with them on the whole. Those of more a complex nature are as follows:

public object[,] animationArray; This will store a graphics path and pen or brush for rendering with. Each instance may have more than one graphicsPath and Pen/Brush pair. This way it is possible to layer up various colours and shapes for your animations. For example, the missile explosion consists of an expanding filled circle outlined with an expanding circle outline in a different color, thus having a [2,2] array (ie two pairs, compared with an asteroid's [1,2] array).

public Point[] shapeArray; An array of points that serves 2 purposes: to define the shape used to populate the graphics path, to take part in collision detection. When somebody comes up with region intersection code for the collision, this array may become redundant.

make_move() and animate(): There is a fine line between these definitions, but have separated them out to two methods, since it seemed worthwhile. make_move() deals with the physics of movement (whether items bounce, screen wrap, speed and direction). animate() deals with colour, size and shape changes. This is where other systems would perhaps use and array of preloaded bitmaps.

The sound system

There are two versions of this, one that accommodates the beta2 version of c#, and the other accommodates the final released code for c#.

The beta version is commented out in the code. Unfortunately, I could find no documentation for the final version of the Microsoft multimedia control that I use, so again, a bit of an experiment, but at least it works. Thanks to Jerzy Peter and Peter Stephens for helping me with the initial sound system which used the winmm.dll for playing sounds. I have left some of that code in the source files for reference. Unfortunately, I could not make this play more than one sound at a time (but it did force me to dabble with threads to try and make it work), so came up with the idea of using an array of ActiveX multimedia players to achieve this.

To make the sound player class, I just dumped a multimedia player on a form and looked at the code generated by the IDE, I don't fully understand what is happening with the ActiveX wrappers used by C#, but again, it works, so good enough for now.

Playing a sound effect

You can set up the sound player array to have more players if you want more sounds at one time. The code for that is self explanatory. I have left it at 6.

When a multimedia player plays a sound, it does not allow sharing of the wav file in use. To get around this, I create a copy of the passed in file and use the copy instead. The file is then deleted when the player sends its done_event. I initially thought that copying a file would be a little inefficient, so I wrote some code to copy the file using FileStreams instead of the static File.Copy methods. My tests show that there was little difference, and so long as the files are small enough, the system works.

In order to delete the temporary sounds files, I had to force the players to release them, I achieve this by throwing the player into error by asking it to load an empty filename. I know this is gadgy coding, but send your kludge medals to the above email address anyway. Unfortunately, this error seems to make the sound effects slip out of synch with the game slightly.

Conclusion

Finally, I hope you have enjoyed my code and article, I wanted it to be beginner level (step by step) but that is such a huge job, it would have taken too long given my current circumstances. Kudos to those that do write 'em, they are time consuming.

My next article will be about a tree simulator I have written that produces a simple tree like image with sweeping colours. Its very pretty.

Many thanks to all at Codeproject, especially those that suggested methods for removal of background and the sound system.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here