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:
- learning the IDE
- learning the syntax
- 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.
- To fulfill the observer role (Observer Pattern, from Design Patters, Gamma,
Helm, Johnson & Vlissides) and handle the collision detection
- 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
FileStream
s 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.