August 2007 - Posts

Basic Collision Detection

Source code: http://silverlightrocks.com/cs/files/folders/slg101_tutorials/entry250.aspx

It's all well and good to move objects around, and create particles, but most games require some kind of collision handling. A collision is when two objects intersect. There are many different ways to determine if two objects collide, each with varying levels of complexity and accuracy. In 2D games, the most accurate and complex is probably "pixel perfect" collision which checks whether any pixel of the two objects overlap. You can also use bounding boxes, or polygons that approximate the outline, and calculate if the two polygons intersect. One of the simplest collision detection algorithms is to use bounding circles, or in 3D bounding spheres. The math for this type of collision is very straightforward, and can also be used as an intermediate collision detection step before more complex and CPU intensive algorithms.

Collisions can get very involved if you're dealing with multiple objects potentially colliding simultaneously, or dealing with gravity and other forces along with the collisions. It also adds a major level of complexity if the objects that collide need to bounce off of each other. One of the core features of most commercial game engines is a physics engine, which specifically handles these types of interactions.

Fortunately in our case, we don't need to handle objects bouncing off each other. When objects collide in our game, they explode. We also can approximate our objects with circles, and therefore use the simplest of collision detection routines.

The first collision we will want to handle is between the ship and the asteroids. Since there is such as small number of potential objects, we can simply loop through the list of asteroids and check if they collide with the ship. To check if two circles intersect, you can find the distance between the centers and if that value is smaller than the sum of the radii of the two circles, then the circles intersect. The distance between two points can be found using the pythagorean theorem, and the distance is the square root of the sum of the X distance squared and the Y distance squared. To make things easier, the Vector type has a Length method which can help with the calculation. So since the ship and asteroids inherit from the sprite class, it makes sense to put the logic in the Sprite class to calculate whether a collision occurs. Here is the Collides method for the Sprite class:

public static bool Collides(Sprite s1, Sprite s2)
{
    Vector v = new Vector(s1.Position.X - s2.Position.X, s1.Position.Y - s2.Position.Y);
    if (s1.CollisionRadius + s2.CollisionRadius > v.Length)
    {
        return true;
    }
    else
    {
        return false;
    }
}

The CollisionRadius is a field that has been added to the Sprite class to store the collision radius, I know, very creative naming... Now for a useful game programming tip. To account for irregularities in the object (for example, the ship isn't really round, and the asteroids are bumpy) make your collision radius smaller than the radius that would encompass the entire sprite. This also makes the gameplay a little more forgiving and actually feels a little more natural.

So now in the gameLoop_Update method, we need to call this collision method. If the objects collide, the ship will explode and the asteroid will explode. If the asteroid has a radius of more than 10, we'll split it into two new asteroids and remove the existing one. To make it easier to remove items from a collection, it is helpful to loop backwards. This is because if you're looping forwards and remove an item, the indexes will be off. Also any new objects that are added will be processed immediately, which isn't what we want. 

for (int i = asteroids.Count - 1; i >= 0; i--)
{
    if (Sprite.Collides(ship, asteroids[i]))
    {
        if (asteroids[i].Radius > 10)
        {
            Asteroid a1 = new Asteroid(asteroids[i].Radius / 2);
            asteroids.Add(a1);
            this.Children.Add(a1);
            a1.Position = asteroids[i].Position;
            Asteroid a2 = new Asteroid(asteroids[i].Radius / 2);
            asteroids.Add(a2);
            this.Children.Add(a2);
            a2.Position = asteroids[i].Position;
        }
        ship.Explode();
        asteroids[i].Explode();
        this.Children.Remove(asteroids[i]);
        asteroids.RemoveAt(i);
    }
}

Then for the Explode method, this is also added to the Sprite class, since both a ship and an asteroid can explode. The Explode method generates a set of particles that fly out in all directions from the current position. It's pretty similar to the AddExhaust method and looks like this:

public void Explode()
{
    ParticleFactory p = new ParticleFactory();
    p.FromOpacity = 1;
    p.ToOpacity = .3f;
    p.LifeSpanSeconds = 1f;
    p.StartPosition = this.Position;
    for (int i = 0; i < 20; i++)
    {
        Ellipse ellipse = XamlReader.Load(explodeXaml) as Ellipse;
        Vector velocity = MathHelper.CreateVectorFromAngle(rand.Next(360), rand.Next(60, 100));
        p.AddParticle(ellipse, velocity);
    }
}

Now I haven't gone through all of the code changes, some things were moved around, and other fields added, but I have covered the main code related to collision handling for the game. The rest of it is similar to things we have already covered, or is just basic programming and not related to game programming or Silverlight specifically. Please look through the latest code to get an idea for the other changes.

collision

Posted by Bill Reiss | 2 comment(s)

A Simple Particle System

Source code: http://silverlightrocks.com/cs/files/folders/slg101_tutorials/entry248.aspx

Particles are a key part of game programming. When it comes to explosions, smoke effects, and many other types of effects, it is often easier and gives a better effect to compose the full effect out of several smaller particles. These particles can vary in velocity, position, color, and opacity, among other properties, based on the effect desired. In some games, the particles need to behave according to the game physics, such as gravity. Particles typically have a short life span and then disappear. Since there can be a large amount of particles being created and destroyed on a regular basis, a particle engine is typically employed to manage them.

For our particle system, we will keep it simple and give the particles a constant velocity in a constant direction. By doing this, we can take advantage of some simple but powerful animation classes built into Silverlight. We have seen one of these classes before, when we created our game loop, namely the Storyboard class.

A Storyboard typically contains one or many animation objects, with each animation object acting on a single property of an object, such as Canvas.Left to control the left position of the object. The animation object will also contain such values as the start and end values of the property, and the duration of the animation. Then once Begin is called on the Storyboard object, the current value of the property will be calculated for each frame while the Storyboard is active, and the property will be modified to contain that value.

The first particles we will add to the game is the exhaust trail coming out of the back of the ship when the thrust key is pressed. To do this, we will create a new particle in each frame, and calculate a velocity opposite to the current direction of the ship. The particles will fade from full opacity to partial opacity over the life of the particle, and then disappear at the end of its life span.

Since particles are such an important part of game development, I have added a ParticleFactory class to the SLG101Utilities library to make creating particles simple. To use the ParticleFactory class, set the StartPosition, the LifeSpanSeconds, and optionally the StartPositionDeviation and FromOpacity and ToOpacity. Then simply call AddParticle, passing in the visual element that you would like to use as your particle, and the velocity of the particle.

In the case of the exhaust trail, we will use ellipses (actually circles), but your particle could be a canvas with many children, an image, or any other FrameworkElement. By adding this flexibility to the ParticleFactory, you should be able to easily modify the code to create whatever effect is appropriate for your game. You may want to modify ParticleFactory to allow you to modify the scale of the object, or the rotation, or the color. Hopefully the included class will give you a good starting point.

Let's take a look at the ParticleFactory.AddParticle method:

public void AddParticle(FrameworkElement particle, Vector velocity)
{
    string particleName = "slg101particle" + currentCount.ToString();
    currentCount++;
    particle.SetValue<string>(FrameworkElement.NameProperty, particleName);
    StringWriter sw = new StringWriter();
    sw.Write("<Storyboard xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" x:Name=\"{1}_animation\" Duration=\"{0}\">");
    if (velocity.Length > 0)
    {
        sw.Write("<DoubleAnimation Storyboard.TargetName=\"{1}\" Storyboard.TargetProperty=\"(Canvas.Left)\" From=\"{2}\" To=\"{3}\" Duration=\"{0}\"/><DoubleAnimation Storyboard.TargetName=\"{1}\" Storyboard.TargetProperty=\"(Canvas.Top)\" From=\"{4}\" To=\"{5}\" Duration=\"{0}\"/>");
    }
    if (FromOpacity != 1 || ToOpacity != 1)
    {
        sw.Write("<DoubleAnimation Storyboard.TargetName=\"{1}\" Storyboard.TargetProperty=\"(Opacity)\" From=\"{6}\" To=\"{7}\" Duration=\"{0}\"/>");
    }
    sw.Write("</Storyboard>");
    Point start = StartPosition;
    Vector offset = MathHelper.CreateVectorFromAngle(rand.Next(360), rand.Next((int)(StartPositionDeviation * 100)) / 100.0);
    start += offset;
    TimeSpan tsDuration = TimeSpan.FromSeconds(LifeSpanSeconds);
    string duration = string.Format("{0}:{1}:{2}.{3:000}", tsDuration.Hours, tsDuration.Minutes, tsDuration.Seconds, tsDuration.Milliseconds);
    string animationXaml = string.Format(sw.ToString(), duration, particleName, start.X, start.X + velocity.X * LifeSpanSeconds, start.Y, start.Y + velocity.Y * LifeSpanSeconds, FromOpacity, ToOpacity);
    Storyboard sb = XamlReader.Load(animationXaml) as Storyboard;
    sb.Completed += new EventHandler(sb_Completed);
    HostCanvas.Resources.Add(sb);
    HostCanvas.Children.Add(particle);
    sb.Begin();
}

You'll see that it uses some of the same dynamic creation of XAML used in the previous tutorial. Note that all Storyboards need to have a unique name before they are added to the Canvas.Resources collection, so we generate a name here. Then it's a matter of adding the particle to the host canvas' children collection, the storyboard to the host canvas' resources collection, and beginning the Storyboard. Also note that we wired up a handler for the Storyboard's Completed event. This is where we'll remove the particle from the canvas, and remove the storyboard from the resources collection. If you don't do this, you'll run out of memory pretty quickly. Here is the cleanup code:

void sb_Completed(object sender, EventArgs e)
{
    Storyboard sb = sender as Storyboard;
    HostCanvas.Resources.Remove(sb as DependencyObject);
    string particleName = sb.Name.Substring(0, sb.Name.Length - 10);
    FrameworkElement fe = HostCanvas.FindName(particleName) as FrameworkElement;
    HostCanvas.Children.Remove(fe);
}

So now that we have a general purpose particle factory, it's time to call it. I have added the following fields to the Ship class:

ParticleFactory exhaustFactory;
static Random rand = new Random();
string exhaustXaml = "<Ellipse Fill=\"White\" Width=\"4\" Height=\"4\"/>";

In the Ship's constructor, we'll set some fields in the particle factory to specify how we want the particles to behave:

exhaustFactory = new ParticleFactory();
exhaustFactory.FromOpacity = 1;
exhaustFactory.ToOpacity = .3f;
exhaustFactory.LifeSpanSeconds = .2;
exhaustFactory.StartPositionDeviation = 5;

So the particles will start at full opacity and fade to 30% opacity, and will live for .2 seconds. The StartPositionDeviation specifies a number of pixels to randomly vary the starting point from the one specified. This helps to scatter the particles a bit so they don't all form a straight line. This value is a maximum radius for the deviation from the starting position specified.

And then also in the Ship class, I have added an AddExhaust method, which creates one particle moving opposite to the ship's direction:

public void AddExhaust()
{
    Ellipse ellipse = XamlReader.Load(exhaustXaml) as Ellipse;
    Point p = Position;
    p.X -= 2;
    p.Y -= 2;
    Vector v = MathHelper.CreateVectorFromAngle(RotationAngle, 1);
    Vector exhaustVelocity = -v;
    p += exhaustVelocity * 17;
    exhaustVelocity *= rand.Next(150, 250);
    exhaustFactory.StartPosition = p;
    exhaustFactory.AddParticle(ellipse, exhaustVelocity);
}

and then at the end of the end of the gameLoop_Update method in the Page class, if the ship is currently thrusting, I call ship.AddExhaust. I do this after the ship.Update is called, so that the ship's new position is taken into account before creating the particle, otherwise the exhaust didn't look right because it overlapped the ship. For a thicker trail, you could call AddExhaust twice per frame.

EDIT: I forgot to mention that we need to tell the ParticleFactory class which canvas to add its visual elements to. To make it so that you can create particles on any canvas, there is a static HostCanvas field on the ParticleFactory class. In the Page.Page_Loaded method, set the HostCanvas field to the page's canvas, like this:

ParticleFactory.HostCanvas = this;

Since it's a static field, you can only have one host canvas per application. If you were to change the HostCanvas to a nonstatic field, then you would be able to put each PartcleFactory's particles on a different canvas if you wanted to.   

spacerocks_exhaust

Posted by Bill Reiss | 3 comment(s)

All Tutorials Updated for Silverlight 1.1 Alpha Refresh

I have gone back through all of my posts and updated the ZIP files that contain the full source, and made any changes to the source and the tutorials required by the Silverlight 1.1 Alpha Refresh. The only breaking change that really impacted the tutorials so far was that all Storyboard objects now need to be named. I look forward to continuing this series soon, with the next post focusing on simple Particles.

Please let me know if there is anything I missed while bringing everything up to date.

Posted by Bill Reiss | 2 comment(s)
More Posts