There are two different areas where we
might consider reading some form of keyboard input from the user: for
controlling a game (by using the cursor keys, for example) and for text
input (perhaps to enter a name in a high-score table).
The first of these requires the presence of a
hardware keyboard. Keyboards can make a huge difference to some
applications such as when taking notes or writing e-mail, and they can
be useful for gaming, too.
Some of your users will have such a keyboard, and
others (probably the majority) will not. For this reason, it is
strongly advised not to make having a hardware keyboard a requirement
of your game. By all means allow the keyboard to enhance the gaming
experience, but please do ensure that it still works for those users
who have only a touch screen for control.
For text input, users can type on a hardware
keyboard if they have one, or use the onscreen keyboard known as the
Soft Input Panel (SIP) if they do not. The methods both produce the
same end result from the perspective of your game: it can ask for some
text input, which it receives from the user. Exactly how the user
enters it is not something that your game needs to worry about.
Let's take a look at how to interact with hardware keyboards and how to get the user to enter some text into your game.
1. Using a Hardware Keyboard
Just as XNA provides the TouchPanel object to allow us to read input from the touch screen, it also provides the Keyboard object to allow keyboard input to be read. It provides a single method, GetState, which provides a snapshot of the current keyboard activity. Just as with the TouchPanel,
this object allows us to poll the keyboard state rather than use an
event-based model such as the one you might be familiar with if you
have spent time in WinForms development.
GetState returns a KeyboardState
object from which we can read whatever information we need to control a
game. There are three methods that can be called on the KeyboardState object:
GetPressedKeys returns an array of Keys
values from which the complete set of current pressed keys can be read.
If you want to allow a large range of keys to be used (such as to read
the input when the user is typing, for example), this is probably the
best method for querying the keyboard. Note that the array contains
simple keycodes and nothing more: no information about pressed or
released states is contained within this data.
IsKeyDown returns a boolean indicating whether a specific key (provided as a parameter) is currently pressed down.
IsKeyUp is the reverse of IsKeyDown, checking to see whether a specific key is not currently pressed.
All these functions operate using the XNA-provided Keys
enumeration. This enumeration includes a huge range of keys that might
potentially be pressed, even though some of them won't exist on any
given target device. The alphabetical characters have values in the
enumeration with names from A to Z; because the
enumeration deals only with pressed keys rather than typed characters,
there is no provision for lowercase letters. The numeric digits are
represented by the names D0 to D9 (enumerations do
not allow names starting with digits, so a prefix had to be applied to
these items to make their names valid). The cursor keys are represented
by the values named Up, Down, Left, and Right.
If you are unsure about which enumeration item corresponds to a key on the keyboard, add some code to your Update function that waits for GetPressedKeys to return one or more items and then set a breakpoint when this condition is met. You can then interrogate the contents of the Keys array to see which keycode has been returned.
|
|
1.1. Direct Keyboard Polling
The example project KeyboardInput provides
a very simple implementation of moving a sprite around the screen using
the cursor keys. The code to perform this, taken from the Update function, is shown in Listing 1.
Example 1. Using the keyboard to move a sprite
// Move the sprite? if (Keyboard.GetState().IsKeyDown(Keys.Up)) sprite.PositionY -= 2; if (Keyboard.GetState().IsKeyDown(Keys.Down)) sprite.PositionY += 2; if (Keyboard.GetState().IsKeyDown(Keys.Left)) sprite.PositionX -= 2; if (Keyboard.GetState().IsKeyDown(Keys.Right)) sprite.PositionX += 2;
|
If you are lucky enough to have a device with a
hardware keyboard, you can try the example and see how it responds. If
you don't have such a device, you can use the emulator to experiment
instead. But you will notice something that seems to be a bit of a
problem here: pressing the cursor keys in the emulator has no effect;
the sprite doesn't move at all.
The reason for this is that the emulator disables
keyboard input by default. There are three keys that can be pressed on
the PC keyboard to change this: the Page Up key will enable keyboard input, Page Down will disable it, and the Pause/Break key will toggle its enabled state. By default, keyboard input is disabled.
Press the Page Up key and then try the
cursor keys again. The sprite should spring into life (if it doesn't,
click within the screen area of the emulator and try pressing Page Up
again). Knowing how to enable the keyboard is useful in other areas
within the emulator, too—it makes it much easier to use the web
browser, for example!
Polling for input in this way is ideal for games. There is no keyboard repeat delay between the first report of a key being pressed and subsequent reports for the same key, and no repeat speed
to worry about that would cause delays between reports of a held key
even after the initial delay had expired. Polling gives us a true and
accurate picture of each key state every time we ask for it.
It also allows us to easily check for multiple keys
pressed together. If you try pressing multiple cursor keys, you will
see that the sprite is happy to move diagonally. This is perfect for
gaming, in which pressing multiple keys together is a common
requirement.
1.2. Checking for Key Pressed and Key Released States
If you need to monitor for the point in time where the user has just pressed or released a key, XNA's Keyboard
object doesn't provide any information to this effect for you to use.
It is easy to work this out with a little extra code, however.
Once the keyboard state has been read, the returned KeyboardState
structure keeps its values even after the keyboard state has moved on.
By keeping a copy of the previous state and comparing it with the
current state, we can tell when a key has been pressed or released: if
it was up last time but is down now, the key has just been pressed; if
it was down last time but is up now, it has just been released.
We can easily use this approach to look for individual keys or can loop through the array returned from GetPressedKeys in order to look for all keys that were pressed or released since the last update. Listing 2 shows how details of all pressed and released keys can be printed to the Debug window. This code can also be found within the KeyboardInput example project.
Example 2. Checking for pressed and released keys
// Read the current keyboard state currentKeyState = Keyboard.GetState();
// Check for pressed/released keys. // Loop for each possible pressed key (those that are pressed this update) Keys[] keys = currentKeyState.GetPressedKeys(); for (int i = 0; i < keys.Length; i++) { // Was this key up during the last update? if (_lastKeyState.IsKeyUp(keys[i])) { // Yes, so this key has been pressed System.Diagnostics.Debug.WriteLine("Pressed: " + keys[i].ToString()); } } // Loop for each possible released key (those that were pressed last update) keys = _lastKeyState.GetPressedKeys(); for (int i = 0; i < keys.Length; i++) { // Is this key now up? if (currentKeyState.IsKeyUp(keys[i])) { // Yes, so this key has been released System.Diagnostics.Debug.WriteLine("Released: " + keys[i].ToString()); } }
// Store the state for the next loop _lastKeyState = currentKeyState;
|
There are two important things to remember
when monitoring for pressed and released keys. First, you must check
them during every single update if you want to avoid missing key state
updates. Second, you should query the keyboard state only once per
update and should store this retrieved state data away for use during
the next update. Without following this approach, the state might
change between the individual calls to GetState resulting in key state changes being overwritten and lost.