
When each year comes to an end people tend to reflect on the past year and their accomplishments. For a programmer this often results in a look through the previous years source code. Of all the projects that I did last year my favorite project was the asteriods silverlight clone that I built. The asteroids clone was my first attempt at a Silverlight game and it was really fun to build. The project was developed over the course of a weekend and it helped me to get acquainted with Silverlight. I will go through some of the source code in case you are considering building something similar and need some tips to on how to get started.
Getting Started
Like most games, you need to have a main loop where you can draw and redraw sprites based on some variables. This is accomplished by created a DispatcherTimer that invokes a drawing routine at a predefined interval. The drawing routine is responsible for looping through all the objects on the canvas and updating their positions. For example, when an asteroid appears on the screen, it already has been assigned a random angle, speed, and X, Y coordinates. When the timer ticks, the asteroid's X, Y values are recalculated using some basic math formulas. Here is an example:
public Page() {
InitializeComponent();
//create the timer used as the main loop
_mainLoop.Stop();
_mainLoop.Interval = TimeSpan.Zero;
//wire up the events
_mainLoop.Tick += new EventHandler(mainLoop_Tick);
StartGame();
}
Looking at the code snippet above, you can see that there is a private variable named _mainLoop. The _mainLoop is just like any standard .NET timer object. It has a Start() and Stop() method, and it also has an event named Tick. In order to redraw the objects on the screen, I wire an event handler to the Tick event.
In order for this game to function, I had to develop code to redraw the asteroids, stars, and an occasional UFO. Also, we have to redraw the ship and its bullets. To simplify things, I stored each object category in a separate object list. This way, I could create separate methods like DrawAsteroids which could enumerate through the asteroids object list and update their positions.
The DrawAsteroids method is shown below. As you can see, I am iterating over an object list and calling the MoveForward method of the Asteroid class. Also, within the loop, I am checking to see if the asteroid has moved off of the screen. If it does, I adjust the X or Y coordinate to make it re-appear on the exact opposite side of the screen. This is how the original asteroids game works.
void DrawAsteriods() {
for(int i = _asteroids.Count - 1; i >= 0; i--) {
Asteroid a = _asteroids[i];
a.MoveForward();
if(a.X >= (this.Width - a.Width))
a.X = 1;
else if(a.X <= 0)
a.X = this.Width - a.Width;
if(a.Y >= (this.Height - a.Height))
a.Y = 1;
else if(a.Y <= 0)
a.Y = this.Height - a.Height;
}
}
The MoveForward method of the asteroid is very simple. It brings back wonderful memories of my high school math class! Basically, the first step is to convert degrees to radians. Then, I update the X coordinate using the Sin method which takes the radian value as an input parameter. Then, I multiply the result by a speed constant. The Y coordinate is calculated the same way, except we use the Cos method.
public void MoveForward()
{
double radians = Math.PI * _angle / 180.0;
X += Math.Sin(radians) * SPEED;
Y -= Math.Cos(radians) * SPEED;
}
Handling Key Events
One of the problems I ran into while developing this game was the key event handling. I found out early on that I could not just rely on the standard KeyDown event because it was not firing properly. A game like this needs to be very responsive to the user pressing a key. The out of the box event handling simply did not cut the mustard. Luckily, after doing some Googling, I found others that had the same problem. At some point, I discovered the KeyState class. The KeyState class is a static class which basically is responsible for handling all the key up and key down events in the game. It stores the state of the keys that were pressed, and provides a much more responsive gaming experience. To check if a key was pressed, you call the GetKeyState method. In order to wire up the KeyState class, you just need to call the HookEvents method. Here is some code which is responsible for making the ship move around on the screen.
if(KeyState.GetKeyState(Key.Up) == true) {
ship.Thrust();
}
else {
ship.Drift();
}
In the snippet above, I check to see if the user pressed the up arrow on the keyboard. If they did, I call the Thrust method. The Thrust method is similar to the Asteroid's MoveForward method except that it displays a little flame behind the ship which gives the illusion of the rocket engine being ignited.
Dynamic XAML
In the original asteroids game, there are three different sized asteroids. I really did not want to have a separate class for a small, medium, and large asteroid, so I figured out a way to dynamically create the XAML at runtime. Unfortunately, it seems like Silverlight is not really designed well for this scenario, but nonetheless, I figured out a way to make it work.
Basically, the concept here is to dynamically build an asteroid by populating the Data attribute of the Path element. The Data attribute defines how to draw the asteroid by using a path markup syntax. It is basically a mini language which can be used to describe geometric paths. Explaining the Path Markup Syntax is not easily done. In my opinion, it is about as cryptic as Regular Expressions, but some good tutorials can be found on MSDN.
In order to facilitate the three different size asteroids, I created a constructor which takes the size as a parameter.
public Asteroid(AsteroidSize size, Canvas parent )
Now that the size is defined, I can use that as a guideline when creating the path data. Now, it is a simple case of using some random number generation to draw the lines and create the asteroid.
public string GetPathData()
{
int radius = (int)_size * BASE_RADIUS;
string pathData = String.Empty;
for (int i = 0; i < 18; i++)
{
float degrees = i * 20;
Point pt = CreatePointFromAngle(degrees,
radius * (rand.Next(70,99) * .01));
if (degrees == 0) {
pathData += string.Format("M{0},{1} L",
(int)pt.X + radius, (int)pt.Y + radius);
}
else{
pathData += string.Format("{0},{1} ",
(int)pt.X + radius, (int)pt.Y + radius);
}
}
pathData += "z";
return String.Format("<Path xmlns='http://schemas.microsoft.com/" +
"winfx/2006/xaml/presentation' xmlns:x='http://schemas." +
"microsoft.com/winfx/2006/xaml\' Data='{0}'/>",
pathData);
}
Collision Detection
The biggest challenge of this game was the collision detection. Fortunately, the tutorials at bluerosegames.com were very detailed, and gave me an excellent starting point. However, I did find that collision detection was largely dependent on how fast the client machine could repaint the screen. For example, if you are redrawing the screen too often, you can get in a situation where the client is not able to process the data fast enough and the collisions are not properly detected. Therefore, I ended up doing a lot of testing on different speed machines until I found a happy medium. In any case, collision detection works, but it is still far from perfect.
The theory behind the CheckCollision method is that it does a two pass test. First, it checks to see if the outer rectangle around object A intersects with object B. It helps if you visualize that each element is contained within a square box or rectangle. As a matter of fact, during some of my debugging, I actually modified my asteroids code so they all had a bright yellow border around them. This helped me to visualize what was actually going on.
If the outer rectangles of the objects intersect, then a second, more accurate check is done. Now, the individual detailed paths of those objects are checked to see if the individual pixels overlap. If they do, then we have a collision.
There may be more elegant or foolproof ways to do collision detection. If so, I would be happy to hear your ideas. This is my first attempt at a Silverlight game, so please be kind!
public static bool CheckCollision(FrameworkElement control1,
FrameworkElement controlElem1, FrameworkElement control2,
FrameworkElement controlElem2) {
// first see if sprite rectangles collide
Rect rect1 = UserControlBounds(control1);
Rect rect2 = UserControlBounds(control2);
rect1.Intersect(rect2);
if(rect1 == Rect.Empty) {
// no collision - GET OUT!
return false;
} else {
bool bCollision = false;
Point ptCheck = new Point();
// now we do a more accurate pixel hit test
for(int x = Convert.ToInt32(rect1.X); x <
Convert.ToInt32(rect1.X + rect1.Width); x++) {
for(int y = Convert.ToInt32(rect1.Y); y <
Convert.ToInt32(rect1.Y + rect1.Height); y++) {
ptCheck.X = x;
ptCheck.Y = y;
List<UIElement> hits = (List<UIElement>)
System.Windows.Media.VisualTreeHelper.
FindElementsInHostCoordinates(ptCheck, control1);
if(hits.Contains(controlElem1)) {
// we have a hit on the first control elem,
// now see if the second elem has a similar hit
List<UIElement> hits2 = (List<UIElement>)
System.Windows.Media.VisualTreeHelper.
FindElementsInHostCoordinates(ptCheck, control2);
if(hits2.Contains(controlElem2)) {
bCollision = true;
break;
}
}
}
if(bCollision)
break;
}
return bCollision;
}
}
The one thing that I noticed about Silverlight and XAML is that it really does not lend itself to things like inheritance and polymorphism. Therefore, I had to repeat blocks of code in many of my classes because I could not figure out how to make things reusable. Some of the problems are probably due to the fact that this is my first Silverlight app and that I haven't figured out all of the kinks yet.
Honorable mention goes to Andy Beaulieu who built a similar type of game which I ended up modeling some parts of my game after. I stole his XAML for his space ship, because I am completely illiterate when it comes to graphic design.
Finally, this same article with source code was posted on the codeproject earlier this year. If you are interested in trying this app out then please feel free to Download the source code from codeproject.