Xbox 360 Controller Test

From Josh Jones

Jump to: navigation, search

Contents

Introduction

Testing Environment

Just for fun, I thought I'd share how the new Xbox 360 controller interfaces with DirectX via the Xinput interface.

To do this I'm using a sandbox program of mine codenamed "Crossbaby" for its cross-platformness. One thing that computers have problems with in general is dealing with any kind of joystick interface. This includes gamepads, joysticks, steering wheels, etc. (I will call them "joysticks" from here out) There are no standards in place so one joystick adapter can be totally different from another.

I have 3 different PS2 adapters from 3 different companies. Each adapter assigns the buttons, analog axies, etc., each a different way. So what's the big deal? Well, that means that for every PC game I have I have to configure the hell out of the joystick! Sometimes the game just doesn't care about some axis. Halo PC has one of the best joystick configuration interfaces i've ever seen on a computer. Wouldn't it be nice if we could just plug in a joystick and not have to worry about it?

Now comes Microsoft with their almighty Xbox 360 controllers with USB interfaces. Compatable with PCs and Xbox 360s. I'm a big fan of great gamepads so I thought i'd see what the big fuss is about.

Official Xbox 360 Controller

The controller itself is very nice to hold. It has 4 axis for the left and right thumbsticks, 2 axis for the left and right triggers, 10 buttons (4 colored buttons, start, back, left & right thumbsticks, and the left and right shoulder buttons) The middle Xbox button can't be used with the official PC driver, the indicator lights are used automatically.

Implementation

Its going to take a few months for this to catch on to most big game titles out there, but this controller can be used 2 different ways as far as DirectX is concerned. I will refer to these two different interfaces as DirectInput and Xinput.

99% of all Windows games that have joystick support use DirectInput. If you were to plug the Xbox 360 controller into a computer without installing the interface drivers it would be detected as a standard HID or Human Interface Device. This is what DirectInput would use. Installing the official Xbox 360 drivers adds the Xinput interface ability to the controller.

When you play a game on a console. There is nothing stopping you from just removing the controller(s) from the system at any time. I dare you to try this with any popular PC game out there. Basically the joystick has to be plugged in before the game starts for the game to detect it. If you remove the joystick while the game is running, the game will still work, but plugging the joystick back in will not work. You have to usually close the game and reload it. How annoying!

The Xinput interface allows you to plug and play during gameplay as if you were playing on a console system. I'm pretty sure the interface on the Xbox 360 is almost identical to the interface on DirectX so it makes sense.

Differences

Windows Control Panel Settings

One of the biggest differences I've noticed other than the whole plug and play thing, is the trigger buttons. With DirectInput the left and right trigger buttons share an axis. Its hard to visualize without seeing it in the Game Controllers section of the Control Panel, but with no buttons being pressed the axis is at 50%. With both buttons pressed down the axis is at 50%. Weird!

Xinput allows the left and right triggers to be treated separately.

My Implementation

DirectInput only detects 10 buttons on the controller, but I'd like to be able to treat the left and right triggers as buttons as well, so I proclaim there are 12 buttons now! DirectInput detects only 5 axis on the controller since the left and right triggers share an axis in DirectInput land. I proclaim 6 axis! Take that subspace! :)

How it works

This is how it will work from the game player's perspective. You can start the game with or without the controller plugged in; I chose to leave mine out for this example.

Controller unplugged

Once its plugged in the program will detect it and assign a "player" to it:

Finding the gamepad
Controller plugged in
Ring of light is on!

Notice how the "1" indicator light turns on to show which controller is assigned as player 1. Just like the Xbox 360, there can only be 4 Xbox 360 controllers connected at the same time. DirectInput can supposedly support 52, but I can't even imagine the wires involved with that. :) Right now i'm doing a little hand waving with the "player 1" concept. Just understand that each controller gets assigned a number automatically and you can use this number to check the status of the controller (which buttons are pressed, what the left thumbstick position is, etc.).

Now I'm going to remove the controller again. The program has been running this whole time.

Detecting removal

Ta-da! Works like a charm. Now for a stress test:

Detecting gamepad multiple times

The Code

Most of this is taken directly from the samples in the DirectX SDK (October 2005)

First you need the new header file:

#include <XInput.h> // For Xbox 360 Controllers

You also need to include the Xinput.lib file for the linker or you'll get funky linker errors.

Here is a handy define that we'll be using all over the place:

//! XInput handles up to 4 controllers
#define DXUT_MAX_CONTROLLERS 4

Here is the structure that will hold all the status data for a controller:

typedef struct DXUT_GAMEPAD_t
{
    // From XINPUT_GAMEPAD
    WORD    wButtons;
    BYTE    bLeftTrigger;
    BYTE    bRightTrigger;
    SHORT   sThumbLX;
    SHORT   sThumbLY;
    SHORT   sThumbRX;
    SHORT   sThumbRY;

    // Device properties
    XINPUT_CAPABILITIES caps;
    bool    bConnected; // If the controller is currently connected
    bool    bInserted;  // If the controller was inserted this frame
    bool    bRemoved;   // If the controller was removed this frame

    // Thumb stick values converted to range [-1,+1]
    float   fThumbRX;
    float   fThumbRY;
    float   fThumbLX;
    float   fThumbLY;

    // Left and right triggers as axis [0, 1]
    float   fTriggerL;
    float   fTriggerR;
		
    // Records which buttons were pressed this frame.
    // These are only set on the first frame that the button is pressed
    WORD    wPressedButtons;
    bool    bPressedLeftTrigger;
    bool    bPressedRightTrigger;

    // Last state of the buttons
    WORD    wLastButtons;
    bool    bLastLeftTrigger;
    bool    bLastRightTrigger;

} DXUT_GAMEPAD, *DXUT_GAMEPAD_PTR;

Finally declare an array of 4 of these structures since you could potentially have 4 controllers plugged in at once:

//! All of the Xinput Joysticks
DXUT_GAMEPAD m_XinputGamePads[DXUT_MAX_CONTROLLERS];

Some more handy defines for the next function:

#define DXUT_GAMEPAD_TRIGGER_THRESHOLD      30
#define DXUT_INPUT_DEADZONE                 ( 0.24f * FLOAT(0x7FFF) )  // Default to 24% of the +/- 32767 range.   
                                                                       // This is a reasonable default value but can be altered if needed.
#define AXIS_THRESHOLD                      32767.0f
#define AXIS_TRIGGER_THRESHOLD              255.0f

#define DXUT_GAMEPAD_NUMBUTTONS             12
#define DXUT_GAMEPAD_NUMAXIS                6
#define DXUT_GAMEPAD_NUMHATS                1
#define DXUT_GAMEPAD_NUMBALLS               0
#define DXUT_GAMEPAD_NAME		    "XBOX 360 For Windows (Controller)"

This is the function that will be called once per game loop, or update loop. Here is how to use the function:

Here is how I call this function in my update loop:

// Get the state of the Xbox 360 gamepads
for( DWORD iUserIndex=0; iUserIndex<DXUT_MAX_CONTROLLERS; iUserIndex++ )
    DXUTGetGamepadState( iUserIndex, &m_XinputGamePads[iUserIndex], true, false );

The function itself:

HRESULT CJoystick_DirectX::DXUTGetGamepadState( DWORD dwPort, DXUT_GAMEPAD* pGamePad, bool bThumbstickDeadZone, bool bSnapThumbstickToCardinals )
{
    if( dwPort >= DXUT_MAX_CONTROLLERS || !pGamePad )
	return E_FAIL;

    if(!m_bUsingXbox360Interface)
	return S_OK;

    static LPXINPUTGETSTATE        s_pXInputGetState        = NULL;
    static LPXINPUTGETCAPABILITIES s_pXInputGetCapabilities = NULL;

    if( !s_pXInputGetState || !s_pXInputGetCapabilities )
    {
	LPSTR wszPath = new CHAR[MAX_PATH];
	if( GetSystemDirectory( wszPath, MAX_PATH ) )
	{
	    char* temp = "\\";
	    StringCchCat( wszPath, MAX_PATH, temp );
	    StringCchCat( wszPath, MAX_PATH, XINPUT_DLL );
	    HINSTANCE hInst = LoadLibrary( wszPath );
	    if( hInst ) 
	    {
	        s_pXInputGetState = (LPXINPUTGETSTATE)GetProcAddress( hInst, "XInputGetState" );
		s_pXInputGetCapabilities = (LPXINPUTGETCAPABILITIES)GetProcAddress( hInst, "XInputGetCapabilities" );
	    }
	}
	delete [] wszPath;
    }
    if( s_pXInputGetState == NULL )
	return E_FAIL;

    XINPUT_STATE InputState;
    DWORD dwResult = s_pXInputGetState( dwPort, &InputState );

    // Track insertion and removals
    BOOL bWasConnected   = pGamePad->bConnected;
    pGamePad->bConnected = (dwResult == ERROR_SUCCESS);
    pGamePad->bRemoved   = (  bWasConnected && !pGamePad->bConnected );
    pGamePad->bInserted  = ( !bWasConnected &&  pGamePad->bConnected );

    if(pGamePad->bRemoved)
	std::cout << "Removed Xbox 360 Gamepad: Player " << dwPort+1 << std::endl;

    // Don't update rest of the state if not connected
    if( !pGamePad->bConnected )
	return S_OK;

    // Store the capabilities of the device
    if( pGamePad->bInserted )
    {
	std::cout << "Found Xbox 360 Gamepad: Assigning player " << dwPort+1 << std::endl;

	ZeroMemory( pGamePad, sizeof(DXUT_GAMEPAD) );
	pGamePad->bConnected = true;
	pGamePad->bInserted  = true;
	if( s_pXInputGetCapabilities )
	    s_pXInputGetCapabilities( dwPort, XINPUT_DEVTYPE_GAMEPAD, &pGamePad->caps );
    }

    // Copy gamepad to local structure (assumes that XINPUT_GAMEPAD at the front in CONTROLER_STATE)
    memcpy( pGamePad, &InputState.Gamepad, sizeof(XINPUT_GAMEPAD) );

    if( bSnapThumbstickToCardinals )
    {
	// Apply deadzone to each axis independently to slightly snap to up/down/left/right
	if( pGamePad->sThumbLX < DXUT_INPUT_DEADZONE && pGamePad->sThumbLX > -DXUT_INPUT_DEADZONE )
	    pGamePad->sThumbLX = 0;
	if( pGamePad->sThumbLY < DXUT_INPUT_DEADZONE && pGamePad->sThumbLY > -DXUT_INPUT_DEADZONE ) 
	    pGamePad->sThumbLY = 0;
	if( pGamePad->sThumbRX < DXUT_INPUT_DEADZONE && pGamePad->sThumbRX > -DXUT_INPUT_DEADZONE )
	    pGamePad->sThumbRX = 0;
	if( pGamePad->sThumbRY < DXUT_INPUT_DEADZONE && pGamePad->sThumbRY > -DXUT_INPUT_DEADZONE ) 
	    pGamePad->sThumbRY = 0;
    }
    else if( bThumbstickDeadZone )
    {
	// Apply deadzone if centered
	if( (pGamePad->sThumbLX < DXUT_INPUT_DEADZONE && pGamePad->sThumbLX > -DXUT_INPUT_DEADZONE) && 
	    (pGamePad->sThumbLY < DXUT_INPUT_DEADZONE && pGamePad->sThumbLY > -DXUT_INPUT_DEADZONE) ) 
	{	
	    pGamePad->sThumbLX = 0;
	    pGamePad->sThumbLY = 0;
	}
	if( (pGamePad->sThumbRX < DXUT_INPUT_DEADZONE && pGamePad->sThumbRX > -DXUT_INPUT_DEADZONE) && 
	    (pGamePad->sThumbRY < DXUT_INPUT_DEADZONE && pGamePad->sThumbRY > -DXUT_INPUT_DEADZONE) ) 
	{
	    pGamePad->sThumbRX = 0;
	    pGamePad->sThumbRY = 0;
	}
    }

    // Convert [-1,+1] range
    pGamePad->fThumbLX = pGamePad->sThumbLX / AXIS_THRESHOLD;
    pGamePad->fThumbLY = pGamePad->sThumbLY / AXIS_THRESHOLD;
    pGamePad->fThumbRX = pGamePad->sThumbRX / AXIS_THRESHOLD;
    pGamePad->fThumbRY = pGamePad->sThumbRY / AXIS_THRESHOLD;

    // Convert to [0, 1] range
    pGamePad->fTriggerL = pGamePad->bLeftTrigger / AXIS_TRIGGER_THRESHOLD;
    pGamePad->fTriggerR = pGamePad->bRightTrigger / AXIS_THRESHOLD;

    // Get the boolean buttons that have been pressed since the last call. 
    // Each button is represented by one bit.
    pGamePad->wPressedButtons = ( pGamePad->wLastButtons ^ pGamePad->wButtons ) & pGamePad->wButtons;
    pGamePad->wLastButtons    = pGamePad->wButtons;

    // Figure out if the left trigger has been pressed or released
    bool bPressed = ( pGamePad->bLeftTrigger > DXUT_GAMEPAD_TRIGGER_THRESHOLD );
    pGamePad->bPressedLeftTrigger = ( bPressed ) ? !pGamePad->bLastLeftTrigger : false;
    pGamePad->bLastLeftTrigger = bPressed;

    // Figure out if the right trigger has been pressed or released
    bPressed = ( pGamePad->bRightTrigger > DXUT_GAMEPAD_TRIGGER_THRESHOLD );
    pGamePad->bPressedRightTrigger = ( bPressed ) ? !pGamePad->bLastRightTrigger : false;
    pGamePad->bLastRightTrigger = bPressed;

    m_pJoysticks[dwPort];

    return S_OK;
}

That magical function takes care of almost everything. To use the joystick you just make checks against that structure. Here is an example used for testing to see if a button is pressed down or not. For convenience I use numbers to reference my buttons, but you can do it however you want.

// Xinput controllers
if( (index >= 0) && (index <= 3) )
{
    if( m_XinputGamePads[index].bConnected )
    {
	switch( button ) 
	{
	case 0:  // A Button
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_A) ? true : false );

	case 1:  // B Button
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_B) ? true : false );

	case 2:  // X Button
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_X) ? true : false );

	case 3:  // Y Button
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_Y) ? true : false );

	case 4:  // Left shoulder
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false );

	case 5:  // Right shoulder
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false );

	case 6:  // Back
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_BACK) ? true : false );

	case 7:  // Start
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_START) ? true : false );

	case 8:  // Left thumb
	    return((m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false );

	case 9:  // Right thumb
	    return( (m_XinputGamePads[index].wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false );

	case 10: // Left trigger
	    return( (m_XinputGamePads[index].bLeftTrigger >= DXUT_GAMEPAD_TRIGGER_THRESHOLD) ? true : false );

	case 11: // Right rigger
	    return( (m_XinputGamePads[index].bRightTrigger >= DXUT_GAMEPAD_TRIGGER_THRESHOLD) ? true : false );
	}
    }
    else
	return false;

An example of a function that contains this logic would be similar to this:

// Check for the first gamepad's A button
// First parameter is the gamepad index, the second is the button to check
if(isButtonPressed(0, 0))
    playerJumped();  // Some made up example function

Thanks

Did you enjoy this? You think you want to change or add anything? Let me know: twonjosh@yahoo.com

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox