Input Handling |
This section describes the classes that read and process input from devices, like keyboard, mouse, etc.
This topic contains the following sections:
Every interactive application has to read and process input from one or more input devices. The namespace DigitalRune.Game.Input (see also Class Diagrams of Namespace DigitalRune.Game.Input) provides classes that handle several common input-related tasks:
The central interface is IInputService, which provides access to all input devices supported by XNA:
Tip |
---|
All components of a game should use this interface to read user input instead of accessing the input devices in XNA directly. The interface could be implemented in various ways. The implementations can be changed without breaking existing components. For example, the default implementation of the input service reads input from various the devices. Another implementation could read input from a file to replay a previous gameplay session. Components that only depend on certain interfaces are easier to unit-tested. |
This interface is implemented by the class InputManager. The input manager has one additional member, which is not in the interface: InputManagerUpdate(TimeSpan). Update must be called once per frame. In this method the input manager reads the device states and processes the input.
The input service has a Settings property where InputSettings can be configured (timings, thresholds, etc.).
The input service provides methods to check if a button is down, up, pressed, or released:
That means: If the user presses a button and holds it for 10 frames, then IsDown returns true for all 10 frames. IsPressed is only true in the first frame. IsReleased is true for the one frame where the user releases the button.
Typically, multiple components in a game need to handle input. The input needs to be processed in a certain order.
For example: If several game menus are open, only the top-most menu should handle the input. If a component has captured the mouse during a drag-and-drop operation, other components should ignore mouse input.
To handle the input preemption, the input service provides IsHandled flags for all devices:
The flags are reset in InputManagerUpdate(TimeSpan) and the game components that handle input can set one or more flags to indicate that a device has been handled and should not be processed by other components. All components should check the flags before processing input. (Game components can choose to ignore this flag and still read and handle the input.)
Here is an example that shows how a game component should process mouse clicks:
// Has mouse input been handled by another game component? if (!inputService.IsMouseOrTouchHandled) { // Was left mouse button pressed in this frame? if (inputService.IsPressed(MouseButtons.Left, false)) { // React to mouse click. ... // Inform other game objects that the mouse input was handled. inputService.IsMouseOrTouchHandled = true; } }
Albeit very simple, this mechanism has proven to be very powerful in practice. It allows to correctly handle input even in complex games with multiple windows, nested 3D scenes and even "mouse-capturing" for drag-and-drop. The IsHandled flags replace more complex input routing methods.
The Xbox 360 supports up to 4 connected game controllers.
Important: Games should not assume that the game controller connected to the first port is the controller that the player wants to use. For single player games, the user should be able to pick any controller and play with it. The input handling must be able to ignore some controllers (e.g. the first controller could be a guitar controller). And the game must be able to detect when a controller was disconnected and automatically pause the gameplay.
The input service allows to assign a LogicalPlayerIndex (= player) to a PlayerIndex (= controller port). This assignment does not occur automatically, it must be set by the game code. Typically, the game will show a start screen where the user has to press the START or the A button. The controller where START or A is pressed should henceforth be used as the first logical player index ("Player One").
Game controllers are assigned to players using the method SetLogicalPlayer.
Several methods of the input service can be called using either the PlayerIndex or the LogicalPlayerIndex. When using the PlayerIndex, the methods will return the input of the game controller at the given port when connected or default values when disconnected. When using the LogicalPlayerIndex the methods will return the input of the game controller assigned to the specified player or default values when no controller has been assigned to the player.
Further, the game must poll the IsConnected flag of the game controller in each frame to detect and handle disconnects. (The input service does not handle disconnects automatically.)
Key repetition creates "virtual key presses" when a key is held down for a longer period.
Key repetition is, for example, used for keyboard input in text boxes: The user presses and holds the 'A' key. The first 'A' character is displayed immediately and after a short delay more 'A' characters are added to the text box.
Button repetition is also used when navigating menus with a gamepad: The user opened a game menu and holds down the thumbstick or D-pad in one direction. Immediately the next item in the menu is selected. After a short delay the selection moves from menu item to menu item until the user releases the thumbstick or D-pad.
The input services automatically generates virtual key presses. The IsPressed methods have an additional parameter called useKeyRepetition/useButtonRepetition. If this parameter is true, the IsPressed method return physical key presses as well as virtual key presses.
The repetition delay and repetition interval can be configured in the InputSettings.
Note |
---|
Only the last key/button press is repeated. If button A and then B are pressed and held, virtual presses are only generated for button B. This is not optimal when navigating menus with a gamepad. For example: The left thumbstick is pushed downwards. Then the thumbstick is, accidentally, pushed to the left. Now both a down and a left press are recognized. If the user corrects his mistake and pushes the thumbstick straight down, no virtual presses are generated because virtual presses are only generated for the last pressed button/direction. The thumbstick must be released and pushed down so that a new down press is recognized and used for the repetition. - If this problem occurs, it might help to increase the ThumbstickThreshold. |
The mouse position, as read from the mouse device, is available in MousePositionRaw, which is a read-only property. The same value is also stored in MousePosition, which is writable property and can be modified by game components. This is helpful if a game component expects the mouse position to be relative to a local (or transformed) viewport. In general, game components should use the MousePosition property to read the mouse position. The game (or the first game component that handles input) can read MousePositionRaw in each frame and set the adjusted position in MousePosition.
Some games, like first-person shooters, need relative mouse input to rotate the view. Normally, the mouse is stopped when it reaches the screen boundary - and the view does not rotate any further. To avoid this behavior, EnableMouseCentering can be set. The input service will automatically reset the mouse position in each frame. The mouse position will be set to MouseCenter - usually the center of the screen. The property is defined in the input settings.
MousePositionDeltaRaw and MousePositionDelta can be used to get the relative movement of the mouse since the last frame.
Tip |
---|
It is a good practice to disable mouse centering when the game window does not have the focus (e.g. the user presses ALT+Tab and switches to another applications). Check Game.IsActive to see whether the game window is active. |
Game input should be configurable by the user. Users should be able to reconfigure the controls to their likings. Game components that require user input should not directly check the "A" button to decide if the user wants to jump. Instead, there needs to be a level of abstraction that separates the actual input from the desired action the user wants to perform.
Input commands (see base class IInputCommand) translate raw input (such as button presses, key combinations, etc.) to semantic actions.
All input commands have a Name, such as "Forward", "Jump", "Shoot", which identifies the action and a Value that represents the result of the user input. The Value is a floating-point number - most actions will have only two output values: 0 to indicate that the input command is inactive - the user has not pressed the required buttons or keys. And 1 to indicate that the input command is active - the user wants to perform the action. Depending on the type of action, input commands can be bound to analog input. For example, the "Forward" command could be mapped to the y-axis of the game pad. In this case a positive value indicates that the user wants to move forward and a negative value indicates that the user wants to move backward.
Input commands are managed by the IInputService. Input commands can be registered or unregistered by adding them to or removing them from the Commands. When the input service is updated, the method Update of all registered input commands is executed and the commands can check the input devices and update their value.
Game components can check the Value of an input command to see if a certain action should be performed. For example:
if (InputService.Commands["Jump"].Value > 0) { // Let the player character jump. ... }
If all game components strictly use input commands, then controls can easily be remapped at runtime. Input commands can be created for different input events: button presses, key presses, key combinations, double-clicks, mouse gestures, etc. Custom input commands can also be used to encapsulate complex input processing. For example:
Creating custom input commands is simple: New input commands need to implement the interface IInputCommand. In the Update method the commands need to check the input and set their Value accordingly.
If game components regularly access input commands, they can retrieve the input commands once from the input service and store a reference to the command for future use. (This assumes that the command instances are persistent for the lifetime of the game. That means, once a "Jump" command has been registered in the input service it should not be replaced by another "Jump" command.)
The ConfigurableInputCommand is the default implementation of IInputCommand, which is recommend for most games: The ConfigurableInputCommand allowes to define a PrimaryMapping and a SecondaryMapping. Actions can be bound to mouse, keyboard, or game pad input and reconfigured at runtime.
In some game states it makes sense to skip InputManagerUpdate(TimeSpan), for example if the game window does not have focus (Game.IsActive is false). (This is not automatically done by the input manager because in some cases input should be processed even if the game is inactive.)