Library tutorials & articles

Game development using Silverlight 2

Sprites

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.

Comments

  1. 26 Feb 2009 at 02:55
    Best Adobe Flex Developer and Flash Programmer 1. Please visit www.busycode.com or www.flexdeveloper.com.cn 2. Low cost high quality. 3. Best Adobe Flex outsourcing service provider. 4. More than 60 full time in-house Flex developers. 5. Our company Busycode Inc. was registered in San Francisco, Beijing and Nanning. 6. Our skill set is "Adobe Flex/AIR/Flash + .NET/Java/PHP + SQL Database". 7. Our keywords are Flex developer, Flex coder, Flex programmer, Flex expert, Flex engineer, Flex specialist and Flex outsourcing service provider.
  2. 06 Dec 2008 at 22:03
    Regarding game loop, you can now use event, which fires once every frame CompositionTarget.Rendering += new EventHandler(OnRendering); You can control max frame rate: Application.Current.Host.Settings.MaxFrameRate = 25;
  3. 06 Dec 2008 at 21:57
    Nice article! My suggestion is use native to Silverlight coordinate system - clockwise, with 0 degrees at top. In MathHelper, that is double radians = (angle - 90) * (Math.PI/180.0); and return Math.Atan2(DX, -DY) * (180.0/Math.PI);

Leave a comment

Sign in or Join us (it's free).

Dave Wheeler 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 Dev...
AddThis

Related podcasts

  • LineRider - Porting a Flash Game to Silverlight 2

    In this episode, Scott talks to Rick Barraza, an Experience Architect from Cynergy with a background in Flash, and Bryan Perfetto, a Developer from Inxile writing his first Silverlight application. They chat about how and why they ported the popular Flash Game LineRider.com to Silverlight 2.

Events coming up

  • Dec 18

    Louisville .NET Meetup - December Meetup

    Louisville, United States

    We will meet on Thursday December 17th @ Quilogy. They are located at 10350 Ormsby Park Place. Doors will open at 6:30. This month we will feature a speaker meeting. The topic being: Introduction to WPF[B-)] Abstract: Windows Presentation Foundation has been positioned as the platform of choice for Windows Client development.

We'd love to hear what you think! Submit ideas or give us feedback