Hardware Abstraction

Games that run on platforms that support a variety of input controllers offer the richest gaming experience. This is the case not only with the PC (which uses a keyboard, a mouse, and joysticks), but also with most game consoles via exotic peripherals. In recent years, we have used standard controllers, aircraft-style joysticks, snowboards, dance pads of all sorts, and even a fishing rod!

There are two paths a developer might follow when coding for such a platform. Some games will choose to use one (and only one) of the existing controllers. This is the case in most strategy games, which for gameplay reasons are usually coded with a PC mouse in mind. On the other hand, some other games will let the user choose the input method he or she wants to employ. Action games for the PC can often be played with a keyboard, joystick, gamepad, and sometimes even a mouse. Although good for the player, it comes at the cost of complicating the input handling code significantly; and this is exactly where hardware abstraction becomes an issue.

By hardware abstraction, I mean coding your game with a "virtual" controller in mind, so any controller that conforms to that abstract profile can be fitted into the code with zero impact on the game engine. All you need to do is write a generic controller handler (usually, a pure abstract class) from which specific controllers are derived via inheritance. Then, at runtime, only the kind of controller selected by the player is created, providing a seamless and elegant way of integrating different controllers. Figure 5.3 shows how this class structure would work out.

Figure 5.3. A possible class structure to implement device abstraction.

graphics/05fig03.gif

Notice how, for the sake of clarity, I have created a global controller that closely resembles that of a game console. It has two directional controllers and four buttons. These inputs can then be mapped to a keyboard (using keys for the different options), an analog joystick, or a digital joystick. The only item we need to watch out for is the different sensitivity found in keys and sticks. A key can either be pressed or released, thus requiring only a binary value, whereas a joystick will probably map to a continuous set. Thus, some discretization code will be required to offer a standard output.

An even better alternative if you are using DirectInput is to take advantage of action mapping, which allows your device to return not specific state values but game-oriented values. We can assign device events to game events, so the input controller will receive values that are somehow device independent. For example, we can have a joystick and assign a movement on the X axis to the "advance left" action. We can then assign a specific key to the same event, so the input control code becomes independent of the device. We can change the device, and as the control is receiving abstract messages, everything will still work.

Action mapping is not hard to get working under DirectInput, although the code is too lengthy to be detailed here. The first step is defining the actions in our game:

 enum GAME_ACTIONS { WALK, WALK_LEFT, WALK_RIGHT, JUMP QUIT }; 

Then, we must provide an action map that relates axis and buttons or keys to the different actions:

 DIACTION g_adiaActionMap[] = { // Joystick input mappings { WALK, DIAXIS_FIGHTINGH_LATERAL, 0, ACTION_NAMES[WALK], }, { JUMP, DIBUTTON_FIGHTINGH_JUMP, 0,  ACTION_NAMES[JUMP], }, // Keyboard input mappings { WALK_LEFT, DIKEYBOARD_LEFT, 0, ACTION_NAMES[WALK_LEFT], }, { WALK_RIGHT, DIKEYBOARD_RIGHT, 0, ACTION_NAMES[WALK_RIGHT], }, { JUMP, DIKEYBOARD_J, 0, ACTION_NAMES[JUMP], }, { QUIT, DIKEYBOARD_Q, DIA_APPFIXED, ACTION_NAMES[QUIT], }, // Mouse input mappings { WALK, DIMOUSE_XAXIS, 0,  ACTION_NAMES[WALK], }, { JUMP, DIMOUSE_BUTTON0, 0, ACTION_NAMES[JUMP], }, }; 

The initialization of the input system would be quite similar to the previous example. All we would need to do is enumerate all input devices supported by the action map. This would be achieved with a call to EnumerateDevicesBySemantics, which would receive the action map and require a callback where all possible devices would be returned. The callback would then have to build the action map for that specific device. Once that is cleared, subsequent read calls would return device-specific data, as in the following example:

 hr = pdidDevice->Poll(); hr = pdidDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),                                         rgdod, &dwItems, 0 ); for( DWORD j=0; j<dwItems; j++ )      {      UINT_PTR dwAction = rgdod[j].uAppData;      switch( dwAction )          {          case WALK:               (...)          }      } 

Notice that GetDeviceData returns a list of actions, so we can process several buttons or keys in the same loop.



Core Techniques and Algorithms in Game Programming2003
Core Techniques and Algorithms in Game Programming2003
ISBN: N/A
EAN: N/A
Year: 2004
Pages: 261

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net