Game development using Silverlight 2

Sprites

This article was originally published in VSJ, which is now part of Developer Fusion.

In the majority of cases, your game will have elements (characters) that move about and interact with other elements. These elements are known as sprites, and they can be as complex or simple as you like. In fact, SilverCommand is a game that doesn’t use many sprites: the cities and missile bases are implemented as sprites (even though they don’t move), as is the aiming reticule, but the incoming missiles are just Line elements.

Tip #8: Sprite == UserControl

Constructing sprites in Silverlight is very simple: they are normally implemented as a UserControl, with their visual appearance defined in XAML and their behaviour defined in the code behind file. This is a simple and effective way to create a sprite. Of course, there’s nothing to stop you from adding extra functionality by providing an intermediate base class, as shown in the snippet below, or by having your sprite classes implement some interfaces (or use a combination of both):

public class Sprite : UserControl
{
   // common sprite behaviour goes here
}
public class MissileBase : Sprite
{
   // MissileBase specific overrides
   // / behaviour
}

Building the visuals for a sprite

Constructing the visuals for your sprite can get quite interesting. For example, Figure 6 shows an exploded view of the MissileBase sprite.

Figure 6: The MissileBase sprite exposed

Figure 6: The MissileBase sprite exposed

There are a couple of important parts to this structure. The first is that the UserControl has an explicit size set for both its Width and Height. The reason for this is that it enables us to perform crude hit-testing just by checking whether a point lies within the UserControl’s rectangular area.

The second is the use of clipping regions to give the sprite different states, which is the next tip.

Tip #9: Use clipping to show a sprite in different states

The listing below shows an extract of the XAML for the MissileBase sprite:

<UserControl Width="90" Height="60">
    <Canvas x:Name="root">
    	<Canvas x:Name="frame">
    		<!- The "normal" missile base ->
    		<Grid Width="90" Height="60">		
    			<Path ... />
    		<TextBlock ... />
    		</Grid>
    	<!- The "destroyed" missile base ->	
    		<Path Canvas.Left="90" ... />
    	</Canvas>
    </Canvas>
    <UserControl.Clip>
    <RectangleGeometry Rect="0,0,90,60" />
    </UserControl.Clip>
</UserControl>

The key thing to note is the use of a clipping rectangle, which clips the output to a 90 x 60 rectangle. When you look at the Path that is used to render the missile base in its destroyed state you will notice that it lays outside of the clipping region, and therefore isn’t rendered.

Toggling the visible state of the sprite can therefore be accomplished in one line of code, simply by adjusting the left hand position of the frame Canvas (which will move all of its children):

public bool State
{
    get { return Canvas.GetLeft(frame) == 0; }
    set { Canvas.SetLeft(frame, value ? 0 : -90); }
}

Tip #10: Animated sprites

This simple clipping technique can also be used to create animated “walk characters”, using a repeating animation composed of DiscreteDoubleKeyFrames to reposition the frame. This is highlighted in Figure 7, which shows a Canvas containing three (of many) frames of a walking character, along with the clipping area.

Figure 7: Walk animations

Figure 7: Walk animations

The animation that runs the walk is shown here:

<Storyboard x:Name="walkAnimation">
    <DoubleAnimationUsingKeyFrames
    	Storyboard.TargetName="frame"
Storyboard.TargetProperty="(Canvas.Left)"
    		BeginTime="00:00:00"
    		RepeatBehavior="Forever">
    	<DiscreteDoubleKeyFrame
    		KeyTime="00:00:00" Value="0"/>
    	<DiscreteDoubleKeyFrame
    		KeyTime="00:00:00.2000000"
    		Value="-100"/>
    	<DiscreteDoubleKeyFrame
    		KeyTime="00:00:00.4000000"
    		Value="-200"/>
    	... <!- other frames elided ->
    	<DiscreteDoubleKeyFrame
    		KeyTime="00:00:01" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

One advantage of this technique is that it enables designers to create, visualise and play the animations in Microsoft Expression Blend.

Tip #11: Using bounding boxes to perform collision detection

Whether it’s a missile impacting with an explosion, or a bat hitting a ball, most games involve objects that collide into each other. This is an area that you need to get right, because it can be frustrating for a player when they can visibly see when contact has occurred, but the game doesn’t appear to register it. This means that you often need to determine pixel-by-pixel whether two objects have collided. Given that you also need to do this for all of your objects every iteration through the game loop, performance can suffer very quickly. However, if you stick to using UserControls for your sprites and you ensure that every UserControl always has an explicit Width and Height, then you will find that implementing an efficient collision detection mechanism can be relatively straightforward.

Figure 8: Hit testing

Figure 8: Hit testing

In Figure 8, you can see three different collision states between two objects. It shows both the rectangular area of each UserControl (picked out in red), as well as the visual element that the player sees. You can clearly see that you only need to perform pixel-perfect collision detection if the bounding boxes overlap, and then only for the area of the overlap (as opposed to the whole object). The listing here shows a very simple implementation of this approach:

public static bool AreInContact(
    	Sprite s1, Sprite s2 )
{
    Rect r1 = new Rect(
    	Canvas.GetLeft(s1),
    	Canvas.GetTop(s1), s1.Width,
    	s1.Height );
    Rect r2 = new Rect(Canvas.GetLeft(s2),
    	Canvas.GetTop(s2), s2.Width,
    	s2.Height);
    // Bounding box collision
    r1.Intersect( r2 );
    if( r1.IsEmpty )
    	return false;
    Point ptToTest = new Point();
    int l = (int) r1.Left;
    int r = (int) r1.Right;
    int t = (int) r1.Top;
    int b = (int) r1.Bottom;
    // Now perform a pixel
    // perfection collision detection
    // by hit testing every point
    //	within the overlapping Rect
    for( int x = l; x <= r; x++ )
    {
    	for( int y = t; y <= b; y++ )
    	{
    		pt.X = x;
    		pt.Y = y;
    		if(((List<UIElement>)
    			s1.HitTest( pt )).Count != 0)
    		{
    			if(((List<UIElement>)
    				s2.HitTest(pt)).Count != 0)
    			{
    				return true;
    			}
    		}
    	}
    }
    return false;
}

In the first phase, bounding-box collision detection is performed by querying both sprites for their rectangular areas, which are then intersected. If this yields an empty Rect then we know that the sprites are not in contact. We only perform the pixel-perfect collision detection, which is achieved in Silverlight through the use of the UIElement.HitTest method, for those points that lie within the intersection rectangle. Note that the method shown in the listing above is not optimised for specific shapes, but will work for any two UserControls that have a properly specified Width and Height.

Tip #12: Bounding boxes are not the only way

Just for good measure, the SilverCommand game doesn’t use the techniques shown in Tip #11. This is simply because the game doesn’t need them. Simple rectangular collision detection is used for the incoming missiles hitting the cities and bases (further optimised by only performing hit testing if the missile is far enough down the screen). To see whether an incoming missile gets destroyed by an explosion, a simple Pythagoras calculation can be used as the explosions are circular. And speaking of Pythagoras, let’s finish with a quick look at movement and angles

Tip #13: Movement

Games involve movement. This is easy to achieve in Silverlight, especially if you use a Canvas as the root visual for your level as all you need to do to move an object is to update its Canvas.Top and Canvas.Left properties. You can do this using an animation, but given the large number of objects that you might be moving, this is not always the most efficient choice. Instead, you might need to calculate the new position yourself for each time through the game loop. This is easy when the object is moving horizontally or vertically, but it requires a tiny amount of maths when you want to move at an angle. Not only that, in a number of cases you will need to work out which angle you actually want to move the object along in the first place, as is the case when a missile is launched from a base at the aiming reticule. Both of these cases are highlighted in Figure 9.

Figure 9: Pythagoras is your friend

Figure 9: Pythagoras is your friend

So the final tip for this article is the small piece of code:

public struct MoveAmount
{
    public double DX;
    public double DY;
}
public static class MathHelper
{
    public static MoveAmount GetMovement(
    	double angle, double speed )
    {
    	double radians = angle *
    		(Math.PI/180.0);
    	MoveAmount ret = new MoveAmount() {
    		DX = Math.Cos( radians ) * speed,
    		DY = Math.Sin( radians ) * speed};
    	return ret;
    }
    public static double GetAngle(
    		double DX, double DY)
    {
    	return Math.Atan2(DY, DX) *
    		(180.0/Math.PI);
    }
}

The GetMovement method will create a MoveAmount object from an angle (in degrees, hence the adjustment using Pi/180 to convert it to radians) and a notional speed, which is the number of pixels to move the object each game loop frame. You can then apply the values of DX and DY to the sprite’s Canvas.Left and Canvas.Top properties respectively for each iteration through the game loop. The GetAngle method returns an angle (again in degrees, measured counter-clockwise from the horizontal X axis), given the difference in the X and Y coordinates of the two points. One thing to watch out for, of course, is that Atan2 takes the DY value as the first parameter.

Conclusion

Silverlight 2 is a neat platform for creating Web-based games. Hopefully this article will have given you some tips on how to implement some of the standard game programming features using Silverlight 2. And let’s be honest: writing games is just so much more fun than writing business applications, so why not head over to Silverlight.net and take a look at some of the community samples to get yourself started.

You might also like...

Comments

About the author

Dave Wheeler United Kingdom

Dave Wheeler is a freelance instructor and consultant who specialises in .NET application development. He’s a moderator on Microsoft’s ASP.NET and Silverlight forums and is a regular speaker at ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” - Martin Fowler