Click here to Skip to main content
15,867,765 members
Articles / Desktop Programming / XAML

How to build an asteroids inspired Silverlight game

Rate me:
Please Sign up or sign in to vote.
4.74/5 (16 votes)
8 Jan 2009CPOL7 min read 49.5K   1.2K   32   4
A good example of a Silverlight game using collision detection.

Image 1

Introduction

Recently, I have I heard so many great things about Silverlight. However, I really did not have any projects that required me to use it. In order to get familiar with it, I thought I would take on the task of building a small game. Being the 80's retro junkie that I am, I immediately thought about making an asteroids game.

Background

In order to develop this game, I first had to install the "Microsoft Silverlight Tools Alpha for Visual Studio 2008". The download page is here.

I was relieved that I could develop my Silverlight game completely in Visual Studio. I am very comfortable in the VS IDE, and I did not want to incur the learning curve of using Expression Blend or any of the other Silverlight "artist oriented" tools.

Using the code

In order to develop the game, I used the basic strategy of having a main loop where I would redraw sprites based on some variables. This was accomplished by created a DispatcherTimer that invokes a drawing routine in my code 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:

C#
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.

C#
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.

C#
public void MoveForward()
{
   double radians = Math.PI * _angle / 180.0;
   X += Math.Sin(radians) * SPEED;
   Y -= Math.Cos(radians) * SPEED;
}

Handling the 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.

C#
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.

C#
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.

C#
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!

C#
// <summary>
/// Determines if two elements are colliding,
/// using a 2-pass test (rect intersect, then HitTest)
/// </summary>
/// <param name="control1">The container control for the first element</param>
/// <param name="controlElem1">The first element</param>
/// <param name="control2">The container control for the second element</param>
/// <param name="controlElem2">The second element</param>
/// <returns>True if objects are colliding otherwise false</returns>
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;
  }
}

Points of interest

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.

While developing the game, I relied on two main websites for information which I want to make sure gets properly credited:

Andy Beaulieu 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.

Blue Rose Games is a great website for getting started with Silverlight game development. I highly recommend their website.

History

  • 1/8/2009 - First draft.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Concepts2Code
United States United States
Michael is the co-founder and master consultant for Concepts2Code, a software consulting company based in Buffalo, New York. He's been programming since the early 1990's. His vast programming experience includes VB, Delphi, C#, ASP, ASP.NET, Ruby on Rails, Coldfusion and PHP. Michael also is a Microsoft Certified Application Developer and a Certified Technology Specialist for SQL Server.

Visit his blog.

Comments and Discussions

 
Questionasteroids Pin
Member 1482894013-May-20 12:10
Member 1482894013-May-20 12:10 
GeneralAmazing Pin
shmela14-Jun-11 7:27
shmela14-Jun-11 7:27 
GeneralVery Interesting Pin
Member 177012919-Jan-09 23:20
Member 177012919-Jan-09 23:20 
GeneralRe: Very Interesting Pin
Michael Ceranski20-Jan-09 2:04
Michael Ceranski20-Jan-09 2:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.