Game development using Silverlight 2

Writing a game loop

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

The vast majority of games are based around a game loop. As its name suggests, this is a loop that runs for the duration of the game (or at least a level within the game), performing tasks such as responding to player input; moving sprites around the game; detecting collisions; and updating the score. Figure 3 highlights some of these tasks.

Figure 3: Tasks within the SilverCommand game loop

Figure 3: Tasks within the SilverCommand game loop

Of course, writing a game loop in Silverlight is slightly more complex than just writing a while {} statement. Silverlight has a very similar threading model to Windows Forms and WPF, which ensures that keyboard and mouse events, among others, are handled by a single UI thread. Additionally, any update to a UI element (technically anything deriving from DispatcherObject) must also occur on that same UI thread. This therefore precludes you writing a while {} statement, as doing so would prevent any messages from being handled and your game would be less than stellar in its user interaction. It also precludes you from running a game loop on a background thread, as this would require cross-thread marshalling to update the visual elements, which would in turn be incredibly slow. So the next tip in writing games in Silverlight is on writing game loops that are driven by timers.

Tip #5: Use a DispatcherTimer for your game loop

As their name suggests, timer-based game loops work on the principle of performing one game frame’s worth of activity in response to a timed event. In other words, you move all the pieces a little bit, check for collisions and handle input whenever the timer ticks. The important thing here is to choose the correct type of timer, which in the case of Silverlight is the DispatcherTimer.

DispatcherTimer is a thread-affinity aware timer, which raises its Tick event (based on an interval that you specify), on the UI thread. This listing shows the code that performs the setup and running of a level in SilverCommand:

public partial class GameLevel :
    UserControl
{
    private DispatcherTimer loopTimer;
    public GameLevel()
    {
    	InitializeComponent();
    	Loaded += OnLevelLoaded;
    }
    private void OnLevelLoaded(
    	object sender, RoutedEventArgs e)
    {
    	CreateCitiesAndBases();
    	CreateAttackingMissiles();
    	CreateGunsight();
    	SetupKeyboard();
    	RunGameLoop();
    }
    private void RunGameLoop()
    {
    	loopTimer = new DispatcherTimer();
    	loopTimer.Interval =
    		TimeSpan.FromMilliseconds(10);
    	loopTimer.Tick += DoGameFrame;
    	loopTimer.Start();
    }
    // This method is called approx.
    // every 10 milliseconds
    // until the timer is stopped
    void DoGameFrame(object sender,
    	EventArgs e)
    {
    	MoveDefensiveMissiles();
    	MoveAttackingMissiles();
    	PerformCollisionDetection();
    	PerformPlayerActions();
    	UpdateScore();
    	CheckForEndOfLevel();
    }
    // All remaining class members elided
    // for clarity
    ...
}

The first thing to notice is that the GameLevel type is actually a UserControl, and is responsible for both the Canvas on which the game elements are displayed as well as the code to interact with those elements. This makes it very simple to manage the creation and removal of items, such as the explosions and incoming missiles, in the code as it has direct access to the root Canvas element which is the placeholder for everything that is displayed on screen. You might choose to use greater levels of separation, especially if the game logic becomes complex.

In SilverCommand, a GameLevel object is created and shown by the main game engine for each level, so the Loaded event is raised once per level. I always prefer to program against visual elements once I know that they’ve been fully created and rendered, so I tend to place my creational code in the Loaded event handler rather than in the user control’s constructor. This pattern of creating a new object for each level makes state management much simpler within the game, even if the game is essentially repetitive in structure, as is the case with SilverCommand (or Space Invaders, Pac Man and a host of other games).

Once all of the assets have been instantiated, it’s time to begin the game loop; which is what the RunGameLoop method does. Notice that RunGameLoop merely instantiates and starts the DispatcherTimer: there is no more to it than that.

Starting the timer gets the “game loop” up and running, with (in this example) the DoGameFrame method being called every 10ms or so. This does exactly what its name suggests: it steps through all of the operations that need to be performed for each “iteration” of the game loop. What’s so appealing with this code is that pausing or terminating the game loop merely involves calling the timer’s Stop method, which is something that occurs in the CheckForEndOfLevel method if there are no more cities left intact or no more incoming missiles. The end-of-level situation arises sooner or later depending on how wisely the player launches their defensive missiles wisely. In the case of SilverCommand, this is done by pressing the buttons Z, X and C (for the left, centre and right missile bases respectively). So let’s take a look at the next tips that relate to handling the keyboard.

Handling the keyboard

Choosing how to handle the keyboard is a critical part of any Silverlight game. Consider the two games in Figure 4.

Figure 4: How you handle the keyboard is crucial

Figure 4: How you handle the keyboard is crucial

In the Silverlight Breakout game, the bat should move and continue to move while the user holds down a key (perhaps Z for left and X for right). In SilverCommand, on the other hand, missiles shouldn’t be launched while the user holds down one of the three fire keys (their limited stock would be depleted too quickly). Instead, the user should press and release the relevant key to fire a missile.

The Windows SDK has APIs that let you query the precise state of the keyboard at any time, as well as providing various keyboard events. Unfortunately, the only option in Silverlight is to handle the KeyDown and KeyUp events that are raised whenever the user presses (and holds) or releases a key. There is one piece of good news, though: these events bubble up through the element tree, which means that you can centralise the keyboard processing within the level’s UserControl.

Understanding the KeyDown and KeyUp events

The KeyDown event is raised whenever the user presses a key, which is what you would expect. If the user continues to hold down that key then KeyDown will continue to be raised on a regular basis. They KeyUp event is raised, of course, when they release the key. There is one thing to be aware of, though. If the user presses a second key whilst holding down the first key, then all KeyDown events for the first key stop. This behaviour is shown in Figure 5.

Figure 5: Pressing and holding multiple keys

Figure 5: Pressing and holding multiple keys

This makes it quite difficult to introduce diagonal movement into your game, as this is often achieved by holding and pressing two keys at once.

The other factor to consider is that the KeyDown and KeyUp events occur “outside” of the game loop. This can pose problems, especially if you want to check for keyboard input “inside” of the game loop and perform actions based on what you find.

Tip #6: Basing an action on a KeyUp event

In the case of SilverCommand, I wanted the user to press and release the key to launch a missile. This is, of course, straightforward to accomplish by ignoring the KeyDown events completely. Instead, when a KeyUp event is raised, a method is called on the appropriate MissileBase object to indicate that it should create a new Missile object as part of its processing next time through the game loop.

This tip is therefore very simple: if you want single-click keyboard action, handle KeyUp rather than KeyDown events.

Tip #7: Building a KeyState mechanism

It wasn’t needed for SilverCommand, but one of the things that you will want to do if you need to support multi-key actions such as moving diagonally, or “hold down and repeat” actions such as moving the bat in a Breakout clone is to build an object to track the state of keys. This can be incredibly basic, such as the one shown here:

public static class KeyState
{
    // Only support the "standard" keys
    private const int MAX_KEYS = 256;
    // The state of each key is held in
    // this array
    private static bool[] states =
    	new bool[MAX_KEYS];
    public static bool GetKeyState(
    	Key key )
    {
    	int index = (int) key;
    	if( index < 0 ||
    		index >= MAX_KEYS ) return false;
    	return states[index];
    }
    public static void SetKeyState(
    	Key key )
    {
    	int index = (int) key;
    	if( index < 0 ||
    			index >= MAX_KEYS) return;
    	states[index] = value;
    }
    public static void HookEvents(
    	UIElement elem )
    {
    	elem.KeyDown += OnKeyDown;
    	elem.KeyUp += OnKeyUp;
    }
    public static void UnhookEvents(
    	UIElement elem )
    {
    	elem.KeyDown -= OnKeyDown;
    	elem.KeyUp -= OnKeyUp;
    }
    private static void OnKeyUp(
    	object sender, KeyEventArgs e)
    {
    	states[e.Key] = false;
    }
    private static void OnKeyDown(
    	object sender, KeyEventArgs e)
    {
    	states[e.Key] = true;
    }
}

All you need to use the code in this listing is to call the KeyState.HookEvents method when the level UserControl is loaded (calling KeyState.UnhookEvents when the level is finished), passing in a reference to the user control itself. Thereafter, at any point in time you can determine whether a key is pressed (or not) by calling KeyState.GetKeyState, passing in the standard Key enum value for the specific key.

Note that this implementation ignores all the complexity of platform key codes, and thus is designed to work with all the common keys on the keyboard. In general, I’d always advise you to stick with simple numeric and letter keys, and the space bar, rather than use “advanced” keys such as the Windows, Control or Shift keys for your game input.

So now that you’ve got the keyboard under control, let’s consider how you construct the various graphical objects that the user will see on the screen.

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.

“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” - Brian Kernighan