Keyboard Input for Silverlight 2 Games

Source code for this tutorial: http://silverlightrocks.com/cs/files/folders/silverlight_2_tutorials_source_code/entry342.aspx

Now that we can rotate the ship using the game loop, let's add a way to control this rotation using the keyboard. You main forms of human input in a Silverlight application will typically be the keyboard or the mouse, I'll write a tutorial on mouse input in the future, but the mouse buttons generally behave like the keyboard buttons, and are actually a bit simpler.

Trapping Keyboard Events

Keyboard handling in Silverlight 2 consists of handling the KeyDown and KeyUp events on a FrameworkElement that has focus. The needs of a game are typically a bit different than regular text input since what we're really interested in is whether a key is currently pressed or not.

To help with this, I have added another class to the BlueRoseGames.Helpers library.

I've got some comments about this, so let me elaborate. If you handle the KeyDown events yourself and move the sprite based on these events, you're at the mercy of the key repeat timer to move your sprite. This means that it will move once immediately, and then delay a bit before moving again, and once you hit the rapid repeat time, it will move quicker. This is probably desired for some games, but for most games, you're probably more interested in starting movement when you press a key, and ending that movement when the key is released. That's what this helper class is designed to do for you.

This class in the BlueRoseGames.Helpers.Keyboard.KeyHandler class and it keeps track of the KeyDown and KeyUp events so that all the caller has to do is call a IsKeyPressed method to see if a key is currently pressed.

Here is the source code for the KeyHandler class:

using System.Windows;
using System.Windows.Input;
using System.Collections.Generic;
 
namespace BlueRoseGames.Helpers.Keyboard
{
    public class KeyHandler
    {
        Dictionary<Key, bool> isPressed = new Dictionary<Key, bool>();
        FrameworkElement targetElement = null;
        public void ClearKeyPresses()
        {
            isPressed.Clear();
        }
 
        public KeyHandler(FrameworkElement target) 
        { 
            ClearKeyPresses(); 
            targetElement = target; 
            target.KeyDown += new KeyEventHandler(target_KeyDown);
            target.KeyUp += new KeyEventHandler(target_KeyUp); 
            target.LostFocus += new RoutedEventHandler(target_LostFocus);
        }         
        
        void target_KeyDown(object sender, KeyEventArgs e) 
        { 
            if (!isPressed.ContainsKey(e.Key)) 
            { 
                isPressed.Add(e.Key, true); 
            } 
        }         
        
        void target_KeyUp(object sender, KeyEventArgs e) 
        { 
            if (isPressed.ContainsKey(e.Key)) 
            { 
                isPressed.Remove(e.Key); 
            } 
        }
        
        void target_LostFocus(object sender, RoutedEventArgs e) 
        { 
            ClearKeyPresses(); 
        }         
        
        public bool IsKeyPressed(Key k) 
        { 
            return isPressed.ContainsKey(k); 
        }
    }
}

Basically how it works is that the constructor accepts a FrameworkElement and the KeyUp and KeyDown events are wired up for that FrameworkElement. Then as keys are pressed and released, the current list of pressed keys is kept in the Dictionary. We're only really using the key of the dictionary and not the value. This gives us an indexed quick lookup of the keys that are currently pressed. Then in your game loop, you would call the IsKeyPressed() method for each key that you care about. The LostFocus event is important since if a key is being pressed when you lose focus, you won't see the KeyUp event and the key will get "stuck" in the down position. So on LostFocus, KeyHandler clears all of the currently pressed keys.

So now to use this in the SpaceRocks game.

The hardest part of this whole thing was getting focus to the Silverlight control. If the Silverlight control doesn't have focus, then you're not going to see the key presses. There have been some discussions on the forums lately about how to force focus to the control, and there doesn't seem to be a good way that works cross browser so we're stuck with making the user click on the control to make it get focus.

First we need some text to indicate that the user has to click. We'll put this after the Canvas in the Page.xaml but still inside the Grid element:

<TextBlock x:Name="clickToStart" Width="250" Height="80" Text="Click to Start!" Foreground="White" VerticalAlignment="Top" Margin="50" FontSize="35" TextAlignment="Center"/>

The game should now look like this:

clicktostart

We'll need a using statement in the Page.xaml.cs so that we can access the KeyHandler class:

using BlueRoseGames.Helpers.Keyboard;

Now we won't start our timer until after the control has focus, otherwise the player won't be able to control the ship when the game starts. We'll also want to stop the timer if the control loses focus, which will pause the game. When the control receives focus, we'll want to hide the TextBlock that tells the user to click, and on a loss of focus, we'll display it again.

One last thing we'll need to do is actually handle checking for which keys are pressed. We'll check for A or the Left Cursor Key to turn left, and D or the Right Cursor Key to turn right. This is the updated Page class in Page.xaml.cs:

public partial class Page : UserControl
{    
    StoryboardGameLoop gameLoop;    
    //   DispatcherTimerGameLoop gameLoop;    
    KeyHandler keyHandler;     
    double rotationSpeed = 150;    
    
    public Page()    
    {        
        InitializeComponent(); 
        this.GotFocus += new RoutedEventHandler(Page_GotFocus);
        this.LostFocus += new RoutedEventHandler(Page_LostFocus);
        this.MouseLeftButtonDown += new MouseButtonEventHandler(Page_MouseLeftButtonDown);
        gameLoop = new StoryboardGameLoop(this);
        //  gameLoop = new DispatcherTimerGameLoop();        
        gameLoop.Update += new GameLoop.UpdateHandler(gameLoop_Update);
        keyHandler = new KeyHandler(this);    
    }
 
    void gameLoop_Update(object sender, GameLoopUpdateEventArgs e)
    {
        // do your game loop processing here
        if (keyHandler.IsKeyPressed(Key.A) || keyHandler.IsKeyPressed(Key.Left))
        {
            ship.RotationAngle -= rotationSpeed * e.Elapsed.TotalSeconds;
        }
        else if (keyHandler.IsKeyPressed(Key.D) || keyHandler.IsKeyPressed(Key.Right))
        {
            ship.RotationAngle += rotationSpeed * e.Elapsed.TotalSeconds;
        }
    }     
    
    void Page_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)    
    {        
        clickToStart.Visibility = Visibility.Collapsed;
        gameLoop.Start();    
    }     
    
    void Page_LostFocus(object sender, RoutedEventArgs e)    
    {        
        clickToStart.Visibility = Visibility.Visible;
        gameLoop.Stop();
    }     
    
    void Page_GotFocus(object sender, RoutedEventArgs e)    
    {        
        clickToStart.Visibility = Visibility.Collapsed;
        gameLoop.Start();    
    }     
}

We're done for now, you should now be able to control the rotation of the ship using the A and D or cursor keys.

Published Monday, March 31, 2008 11:08 AM by Bill Reiss

Comments

# re: Keyboard Input for Silverlight 2 Games

Hey Bill -- Is there any known way yet around the fact that when you go fullscreen, you lose Keyboard support in Silverlight?  Game developers using Silverlight will surely be lamenting this security limitation: "When a Silverlight plug-in is displayed in full-screen mode, keyboard events are prevented from being passed to keyboard event handlers in the application. The only valid keyboard input that is acted upon is the set of keystrokes that return the Silverlight plug-in to embedded mode. This limitation of keyboard input during full-screen mode is a security feature, and is intended to minimize the possibility of unintended information being entered by a user."

Monday, March 31, 2008 8:24 PM by robburke

# re: Keyboard Input for Silverlight 2 Games

Good question. I've asked about this a few times, and at one point it sounded like they were considering providing a way to do this. I really don't know the status of this and I think it's safer to assume it won't be available.

   For my Dr. Popper game when I added high scores and the ability to enter your name, I toggle back to windowed mode when showing that dialog. This of course isn't acceptable if you want to use the keyboard for game input, like in this sample.

   I was thinking that if they were to implement keyboard in full screen, they would probably pop up a warning and force the user to click ok to confirm that they know they are going into fullscreen with keyboard enabled. I'll try to find out the status of this when I go to Redmond in a couple of weeks, and if I can share any info I will.

Monday, March 31, 2008 9:27 PM by Bill Reiss

# re: Keyboard Input for Silverlight 2 Games

Hi Bill,

I'm working on a very basic breakout clone, I'm handling key events for left and right cursors directly with the KeyDown event handlers where I set the Canvas.LeftProperty for my paddle player, and it works ok but the experience is not very good (check the link). There's some sort of delay when shifting left to right.

Do you think this effect would dissappear if using your BlueRoseGames.Helpers class? or that's as good as it gets?

thanks,

gabo

Tuesday, June 03, 2008 11:25 PM by gabouy

# re: Keyboard Input for Silverlight 2 Games

Yes that's really what it's all about. Using KeyDown, you're at the mercy of the repeat rate of the keyboard. With the keyboard helper, you can check each frame whether the key is currently pressed and act accordingly giving smooth movement and immediate response.

Wednesday, June 04, 2008 6:40 AM by Bill Reiss

# re: Keyboard Input for Silverlight 2 Games

Thanks for the helpers!

Tuesday, July 01, 2008 11:01 AM by chrisaswain