4 The Game Engine
4.3 The Input Class
4.3.3 Game Controller Input
Our game engine will support input from Xbox 360 controllers (Figure 4.4). In order to use an Xbox 360 controller on a Windows PC, it must be a wired controller or the computer must be equipped with an Xbox 360 wireless receiver.
Th e USB wire that attaches to a wireless controller is only for powering the controller and does not support data transfer.
We need to include XInput.h header fi le in our input.h fi le (Listing 4.32).
We also need to add xinput.lib to “Linker/Input/Additional Dependencies” on the “Project Property” pages.
Figure 4.4. An Xbox 360 wired controller.
96 4. The Game Engine
The Xbox 360 controller input is stored in the controllers array (Listing 4.33).
MAX_CONTROLLERS is defined as 4, which is the maximum number of controllers supported.
The structure ControllerState is defined, as shown in Listing 4.34. The state member contains the state of the buttons, triggers, and thumb sticks. The structure XINPUT_STATE is defined in XInput.h, as shown in Listing 4.35. The structure XINPUT_GAMEPAD is de-fined in Listing 4.36.
#include <XInput.h>
Listing 4.32. Including XInput.h.
ControllerState controllers[MAX_CONTROLLERS];
Listing 4.33. Array of controller data.
struct ControllerState
{ XINPUT_STATE state;
XINPUT_VIBRATION vibration;
float vibrateTimeLeft; // mSec float vibrateTimeRight; // mSec bool connected;
};
Listing 4.34. The ControllerState structure.
typedef struct _XINPUT_STATE
{ DWORD dwPacketNumber;
XINPUT_GAMEPAD Gamepad;
} XINPUT_STATE, *PXINPUT_STATE;
Listing 4.35. The XINPUT_STATE structure.
typedef struct _XINPUT_GAMEPAD { WORD wButtons;
BYTE bLeftTrigger;
BYTE bRightTrigger;
SHORT sThumbLX;
97 4.3. The Input Class
SHORT sThumbLY;
SHORT sThumbRX;
SHORT sThumbRY;
} XINPUT_GAMEPAD, *PXINPUT_GAMEPAD;
Listing 4.36. The XINPUT_GAMEPAD structure.
The wButtons member contains the state of the buttons. Each button is defined by one bit in the WORD. The current status of a button may be determined by performing a bitwise AND (&) with a binary number that contains a 1 in the bit corresponding to the desired but-ton. If the result of the AND is true, the button is pressed. The definitions in Listing 4.37 are in input.h and may be used to test the button status.
// Bit corresponding to gamepad button in state.Gamepad.wButtons const DWORD GAMEPAD_DPAD_UP = 0x0001;
const DWORD GAMEPAD_DPAD_DOWN = 0x0002;
const DWORD GAMEPAD_DPAD_LEFT = 0x0004;
const DWORD GAMEPAD_DPAD_RIGHT = 0x0008;
const DWORD GAMEPAD_START_BUTTON = 0x0010;
const DWORD GAMEPAD_BACK_BUTTON = 0x0020;
const DWORD GAMEPAD_LEFT_THUMB = 0x0040;
const DWORD GAMEPAD_RIGHT_THUMB = 0x0080;
const DWORD GAMEPAD_LEFT_SHOULDER = 0x0100;
const DWORD GAMEPAD_RIGHT_SHOULDER = 0x0200;
const DWORD GAMEPAD_A = 0x1000;
const DWORD GAMEPAD_B = 0x2000;
const DWORD GAMEPAD_X = 0x4000;
const DWORD GAMEPAD_Y = 0x8000;
Listing 4.37. Gamepad button constants.
Controller vibration is controlled by two motors. The speed of each motor may be set in the vibration member of ControllerState. The structure XINPUT_VIBRATION is defined in XInput.h, as shown in Listing 4.38.
typedef struct _XINPUT_VIBRATION { WORD wLeftMotorSpeed;
WORD wRightMotorSpeed;
} XINPUT_VIBRATION, *PXINPUT_VIBRATION;
Listing 4.38. The XINPUT_VIBRATION structure.
98 4. The Game Engine
The current state of a game controller may be read with the DirectX function XInputGetState:
DWORD XInputGetState(
DWORD dwUserIndex, XINPUT_STATE* pState )
The parameters are:
• dwUserIndex. Number of the controller from 0 to 3.
• pState. Pointer to an XINPUT_STATE structure that receives the state of the con-troller.
We will call XInputGetState from our readControllers function. Our readCon trollers function reads the state of each controller and stores it in our controllers array (Listing 4.39). The call to readControllers is added to the Game::run function so it will be called automatically as part of the game loop.
//========================================================================
// Read state of connected controllers
//========================================================================
void Input::readControllers() { DWORD result;
for( DWORD i = 0; i <MAX_CONTROLLERS; i++) {
if(controllers[i].connected) {
result = XInputGetState(i, &controllers[i].state);
if(result == ERROR_DEVICE_NOT_CONNECTED) // If disconnected controllers[i].connected = false;
} } }
Listing 4.39. Reads the state of the controllers.
The current state of any controller may be read with the getControllerState func-tion (Listing 4.40).
// Return state of specified game controller.
const ControllerState* getControllerState(UINT n) { if(n > MAX_CONTROLLERS)
n=MAX_CONTROLLERS;
99 4.3. The Input Class
return &controllers[n];
}
Listing 4.40. Returns the state of the controllers.
It returns a pointer to a constant ControllerState structure in the controllers array.
To read just the state of a controller’s buttons, we have the getGamepadButtons function.
The number of the controller is passed in parameter n (Listing 4.41).
// Return state of controller n buttons.
const WORD getGamepadButtons(UINT n) { if(n > MAX_CONTROLLERS)
n=MAX_CONTROLLERS;
return controllers[n].state.Gamepad.wButtons;
}
Listing 4.41. Returns the controller’s buttons.
The state of individual buttons may be tested by performing a bitwise AND (&) with the WORD returned and one of the predefined button constants. For example, to test the state of button A from controller 0, we would use the code in Listing 4.42.
if(input->getGamepadButtons(0) & GAMEPAD_A) // Button A pressed
Listing 4.42. Testing button A.
To make it a little easier to test the state of a button, we have included a function for each button. true is returned if the button is currently pressed. The code for the functions in Listing 4.43 is in input.h. To test the state of button A from controller 0, we would use the code in Listing 4.44.
// Return state of controller n D-pad Up.
bool getGamepadDPadUp(UINT n)
// Return state of controller n D-pad Down.
bool getGamepadDPadDown(UINT n)
// Return state of controller n D-pad Left.
bool getGamepadDPadLeft(UINT n)
// Return state of controller n D-pad Right.
bool getGamepadDPadRight(UINT n)
// Return state of controller n Start button.
bool getGamepadStart(UINT n)
// Return state of controller n Back button.
bool getGamepadBack(UINT n)
// Return state of controller n Left Thumb button.
bool getGamepadLeftThumb(UINT n)
100 4. The Game Engine // Return state of controller n Right Thumb button.
bool getGamepadRightThumb(UINT n)
// Return state of controller n Left Shoulder button.
bool getGamepadLeftShoulder(UINT n)
// Return state of controller n Right Shoulder button.
bool getGamepadRightShoulder(UINT n) // Return state of controller n A button.
bool getGamepadA(UINT n)
// Return state of controller n B button.
bool getGamepadB(UINT n)
// Return state of controller n X button.
bool getGamepadX(UINT n)
// Return state of controller n Y button.
bool getGamepadY(UINT n)
Listing 4.43. Button test functions.
if(input->getGamepadA(0)) // Button A pressed
Listing 4.44. Testing button A.
The left and right triggers and the thumb sticks are analog controls. The left and right triggers return a BYTE value from 0 to 255, where 0 is released and 255 represents fully de-pressed. The thumb sticks return a SHORT value for each axis as a signed number between –32,768 and 32,767. The number describes the position of the thumb stick where 0 is cen-tered. Negative numbers indicate left and down; positive numbers indicate right and up.
The analog controls may return values other than 0 when they are fully released. For this reason, applications should use a dead zone when processing the analog data. The dead zone is simply a defined threshold value that must be surpassed before movement is consid-ered valid. We have defined two dead zone values in input.h (Listing 4.45).
// Default to 20% of range as deadzone
const DWORD GAMEPAD_THUMBSTICK_DEADZONE = 0.20f * float(0X7FFF);
const DWORD GAMEPAD_TRIGGER_DEADZONE = 30; // Trigger range 0-255
Listing 4.45. Dead zones.
The thumb stick dead zone is set to 20% of maximum movement and the trigger dead zone is set to 30 out of the maximum value of 255. The values for the dead zones may need to be adjusted for different controllers. Microsoft’s website states, “Please note that some controllers are more sensitive than others, thus the dead zone may vary from unit to unit.
It is recommended that you test your games with several Xbox 360 controllers on differ-ent systems.”1 The functions in Listing 4.46 for reading the analog controls are located in input.h.
1 http://msdn.microsoft.com/enus/library/windows/desktop/ee417001(v=vs.85).aspx
101 4.3. The Input Class
// Return value of controller n Left Trigger.
BYTE getGamepadLeftTrigger(UINT n)
// Return value of controller n Right Trigger.
BYTE getGamepadRightTrigger(UINT n)
// Return value of controller n Left Thumbstick X.
SHORT getGamepadThumbLX(UINT n)
// Return value of controller n Left Thumbstick Y.
SHORT getGamepadThumbLY(UINT n)
// Return value of controller n Right Thumbstick X.
SHORT getGamepadThumbRX(UINT n)
// Return value of controller n Right Thumbstick Y.
SHORT getGamepadThumbRY(UINT n)
Listing 4.46. Functions for reading gamepad analog inputs.