Tutorial 60: XInput

XInput is the API that is used for programming modern Xbox controllers. XInput is intended to be used alongside Direct Input. Although Direct Input provides access to controllers, it lacks some modern functionality. For example, features such as rumble, individual trigger controls, access to headsets plugged into the controller, and others were not common on controllers when Direct Input was originally written. And so, instead of re-writing Direct Input, they just wrote a small library called XInput which supports modern controllers.

In this tutorial we will cover the basics for getting XInput working. We will cover controller detection, using some of the buttons, accessing the individual triggers, and the basics of using the thumb sticks.


Framework

The framework for this tutorial is similar to the Direct Input tutorial except that we will add the new XInputClass for handling modern controller input.

We will start the code section by looking at a simple initial implementation of a XInputClass object.


Xinputclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: xinputclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _XINPUTCLASS_H_
#define _XINPUTCLASS_H_

XInput requires the Xinput.lib library linked for compiling and using XInput.

/////////////
// LINKING //
/////////////
#pragma comment(lib, "Xinput.lib")

We will include the new xinput.h header here. Note that you always have to include the windows.h header above it or your will get version errors when trying to compile.

//////////////
// INCLUDES //
//////////////
#include <windows.h>
#include <xinput.h>
#include <math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: XInputClass
////////////////////////////////////////////////////////////////////////////////
class XInputClass
{
public:
    XInputClass();
    XInputClass(const XInputClass&);
    ~XInputClass();

The XInputClass will have a basic Initialize, Shutdown, and Frame function like other input classes would. We will also have a couple of functions to provide some initial functionality to the controllers connected.

    bool Initialize();
    void Shutdown();
    void Frame();

    bool IsControllerActive(int);

    bool IsButtonADown(int);
    bool IsButtonBDown(int);

    float GetLeftTrigger(int);
    float GetRightTrigger(int);

    void GetLeftThumbStickLocation(int, int&, int&);

private:
    bool IsLeftThumbStickInDeadZone(int);

private:

Note that XInput only supports four controllers, and so all the XInput related arrays will be sized to four elements.

    XINPUT_STATE m_controllerState[4];
    bool m_controllerActive[4];
};

#endif

Xinputclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: xinputclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "xinputclass.h"


XInputClass::XInputClass()
{
}


XInputClass::XInputClass(const XInputClass& other)
{
}


XInputClass::~XInputClass()
{
}

The Initialize function will start by just setting all four controllers off. The actual on/off status will be determined each frame, so we don't need to do much setup here.

bool XInputClass::Initialize()
{
    int i;


    // Initialize the state of the four controllers to be off.
    for(i=0; i<4; i++)
    {
        m_controllerActive[i] = false;
    }

    return true;
}


void XInputClass::Shutdown()
{

    return;
}

Each frame we need to get the current state of all four possible connected controllers. Always assume batteries and such can die at any moment, cords can be pulled out by mistake, and so forth. And so, each frame we get the state of all four controllers, and the same function that gets the state will also let us know if a controller is connected or not.

void XInputClass::Frame()
{
    unsigned long i, result;


    // Loop through all four possible XInput devices connected via USB.
    for(i=0; i<4; i++)
    {
        // Zero out the state structure prior to retrieving the new state for it.
        ZeroMemory(&m_controllerState[i], sizeof(XINPUT_STATE));

        // Get the state of the controller.
        result = XInputGetState(i, &m_controllerState[i]);

        // Store whether the controller is currently connected or not.
        if(result == ERROR_SUCCESS)
        {
            m_controllerActive[i] = true;
        }
        else
        {
            m_controllerActive[i] = false;
        }
    }

    return;
}

We will require a helper function to let the calling program know if controllers are active or not each frame for the above-mentioned reasons.

bool XInputClass::IsControllerActive(int index)
{
    // Boundary check.
    if((index < 0) || (index > 3))
    {
        return false;
    }

    return m_controllerActive[index];
}

Getting the state of buttons is similar to Direct Input. We just do a bitwise and to get the state of the button using its define. I have implemented two buttons for this tutorial. I will leave it to you to add all of the other buttons.

bool XInputClass::IsButtonADown(int index)
{
    if(m_controllerState[index].Gamepad.wButtons & XINPUT_GAMEPAD_A)
    {
        return true;
    }

    return false;
}


bool XInputClass::IsButtonBDown(int index)
{
    if(m_controllerState[index].Gamepad.wButtons & XINPUT_GAMEPAD_B)
    {
        return true;
    }

    return false;
}

The following two functions get the status of the triggers. Previously in Direct Input it would treat both triggers as a single trigger. With XInput this is now split out. Also, I convert the 0 - 255 state of the trigger to a 0.0 - 1.0 float since I find that is more intuitive to work with.

float XInputClass::GetLeftTrigger(int index)
{
    unsigned char triggerValue;
    float finalValue;


    // Get the amount the left trigger is pressed.
    triggerValue = m_controllerState[index].Gamepad.bLeftTrigger;
 
    // If it is really light then return zero to avoid being oversensitive.
    if(triggerValue < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
    {
        return 0.0f;
    }

    // Otherwise convert from 0-255 unsigned char to 0.0-1.0 float range.
    finalValue = (float)triggerValue / 255.0f;

    return finalValue;
}


float XInputClass::GetRightTrigger(int index)
{
    unsigned char triggerValue;
    float finalValue;


    // Get the amount the left trigger is pressed.
    triggerValue = m_controllerState[index].Gamepad.bRightTrigger;
 
    // If it is really light then return zero to avoid being oversensitive.
    if(triggerValue < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
    {
        return 0.0f;
    }

    // Otherwise convert from 0-255 unsigned char to 0.0-1.0 float range.
    finalValue = (float)triggerValue / 255.0f;

    return finalValue;
}

In this tutorial we will cover just the left thumb stick. Do note that these thumb sticks are extremely sensitive. A controller not even being touched can still have very slight movement registered. It is recommended to use a dead zone calculation to ignore the first 15 to 20 percent of the thumb stick's initial range. This way you don't register really slight movement that would make the controller feel overly sensitive.

void XInputClass::GetLeftThumbStickLocation(int index, int& thumbLeftX, int& thumbLeftY)
{
    // Get the current state of the left thumb stick.
    thumbLeftX = (int)m_controllerState[index].Gamepad.sThumbLX;
    thumbLeftY = (int)m_controllerState[index].Gamepad.sThumbLY;

    // Check for dead zone, if so return zero to reduce the noise.
    if(IsLeftThumbStickInDeadZone(index) == true)
    {
        thumbLeftX = 0;
        thumbLeftY = 0;
    }

    return;
}

We will use the magnitude and the recommended define for XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE as a basic dead zone calculation. Note that the right and left thumb stick have different sensitivity and hence different defines for their recommended dead zone value.

bool XInputClass::IsLeftThumbStickInDeadZone(int index)
{
    int thumbLeftX, thumbLeftY, magnitude;


    // Get the current state of the left thumb stick.
    thumbLeftX = (int)m_controllerState[index].Gamepad.sThumbLX;
    thumbLeftY = (int)m_controllerState[index].Gamepad.sThumbLY;

    // Determine how far the controller is pushed.
    magnitude = (int)sqrt((thumbLeftX * thumbLeftX) + (thumbLeftY * thumbLeftY));

    // Check if the controller is inside a circular dead zone.
    if(magnitude < XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE)
    {
        return true;
    }

    return false;
}

Applicationclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: applicationclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _APPLICATIONCLASS_H_
#define _APPLICATIONCLASS_H_

For this tutorial I have included the xinputclass.h header in the ApplicationClass. But it may make more sense to incorporate it into the actual InputClass for your own projects.

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "inputclass.h"
#include "xinputclass.h"
#include "cameraclass.h"
#include "fontshaderclass.h"
#include "fontclass.h"
#include "textclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.3f;


////////////////////////////////////////////////////////////////////////////////
// Class name: ApplicationClass
////////////////////////////////////////////////////////////////////////////////
class ApplicationClass
{
public:
    ApplicationClass();
    ApplicationClass(const ApplicationClass&);
    ~ApplicationClass();

    bool Initialize(int, int, HWND);
    void Shutdown();
    bool Frame(InputClass*);

private:
    bool Render();
    bool UpdateControllerStrings();

private:
    D3DClass* m_Direct3D;

We create the new XInputClass object here.

    XInputClass* m_XInput;
    CameraClass* m_Camera;
    FontShaderClass* m_FontShader;
    FontClass* m_Font;

We create a number of text strings for rendering the states of the different things we are interested in on the controller for this tutorial.

    TextClass* m_ActiveStrings;
    TextClass* m_ButtonStrings;
    TextClass* m_TriggerStrings;
    TextClass* m_ThumbStrings;
};

#endif

Applicationclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: applicationclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "applicationclass.h"


ApplicationClass::ApplicationClass()
{
    m_Direct3D = 0;
    m_XInput = 0;
    m_Camera = 0;
    m_FontShader = 0;
    m_Font = 0;
    m_ActiveStrings = 0;
    m_ButtonStrings = 0;
    m_TriggerStrings = 0;
    m_ThumbStrings = 0;
}


ApplicationClass::ApplicationClass(const ApplicationClass& other)
{
}


ApplicationClass::~ApplicationClass()
{
}


bool ApplicationClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    char activeString[32], buttonString[32], triggerString[32], thumbString[32];
    bool result;


    // Create and initialize the Direct3D object.
    m_Direct3D = new D3DClass;

    result = m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
        return false;
    }

Here we create and initialize the XInputClass object which will set the state of all four controllers to off at the beginning.

    // Create and initialize the XInput object.
    m_XInput = new XInputClass;

    result = m_XInput->Initialize();
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the XInput object.", L"Error", MB_OK);
        return false;
    }

    // Create and initialize the camera object.
    m_Camera = new CameraClass;

    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
    m_Camera->Render();

    // Create and initialize the font shader object.
    m_FontShader = new FontShaderClass;

    result = m_FontShader->Initialize(m_Direct3D->GetDevice(), hwnd);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the font shader object.", L"Error", MB_OK);
        return false;
    }

    // Create and initialize the font object.
    m_Font = new FontClass;

    result = m_Font->Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), 0);
    if(!result)
    {
        return false;
    }

These will be our text strings for displaying whether the four controllers are connected or not.

    // Create and initialize the text objects for the controllers active strings.
    m_ActiveStrings = new TextClass[4];

    strcpy_s(activeString, "Controller Active: No");

    result = m_ActiveStrings[0].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, activeString, 10, 10, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

    result = m_ActiveStrings[1].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, activeString, 10, 35, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

    result = m_ActiveStrings[2].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, activeString, 10, 60, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

    result = m_ActiveStrings[3].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, activeString, 10, 85, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

These will be our text strings for the A and B button status on just the first controller.

    // Create and initialize the text objects for the button strings.
    m_ButtonStrings = new TextClass[2];

    strcpy_s(buttonString, "A Button Down: No");

    result = m_ButtonStrings[0].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, buttonString, 10, 120, 1.0f, 0.0f, 0.0f);
    if(!result)
    {
        return false;
    }

    strcpy_s(buttonString, "B Button Down: No");

    result = m_ButtonStrings[1].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, buttonString, 10, 145, 1.0f, 0.0f, 0.0f);
    if(!result)
    {
        return false;
    }

These will be our text strings for displaying how much each of the triggers is currently pressed on just the first controller.

    // Create and initialize the text objects for the trigger strings.
    m_TriggerStrings = new TextClass[2];

    strcpy_s(triggerString, "Left Trigger: 0.0");

    result = m_TriggerStrings[0].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, triggerString, 10, 180, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

    strcpy_s(triggerString, "Right Trigger: 0.0");

    result = m_TriggerStrings[1].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, triggerString, 10, 205, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

These will be our text strings for displaying the current X and Y position of the left thumb stick on just the first controller.

    // Create and initialize the text objects for the thumb stick strings.
    m_ThumbStrings = new TextClass[2];

    strcpy_s(thumbString, "Left Thumb X: 0");

    result = m_ThumbStrings[0].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, thumbString, 10, 240, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

    strcpy_s(thumbString, "Left Thumb Y: 0");

    result = m_ThumbStrings[1].Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), screenWidth, screenHeight, 32, m_Font, thumbString, 10, 265, 1.0f, 1.0f, 1.0f);
    if(!result)
    {
        return false;
    }

    return true;
}


void ApplicationClass::Shutdown()
{
    // Release the text objects for the thumb stick strings.
    if(m_ThumbStrings)
    {
        m_ThumbStrings[0].Shutdown();
        m_ThumbStrings[1].Shutdown();

        delete [] m_ThumbStrings;
        m_ThumbStrings = 0;
    }

    // Release the text objects for the trigger strings.
    if(m_TriggerStrings)
    {
        m_TriggerStrings[0].Shutdown();
        m_TriggerStrings[1].Shutdown();

        delete [] m_TriggerStrings;
        m_TriggerStrings = 0;
    }

    // Release the text objects for the button strings.
    if(m_ButtonStrings)
    {
        m_ButtonStrings[0].Shutdown();
        m_ButtonStrings[1].Shutdown();

        delete [] m_ButtonStrings;
        m_ButtonStrings = 0;
    }

    // Release the text objects for the controllers active strings.
    if(m_ActiveStrings)
    {
        m_ActiveStrings[0].Shutdown();
        m_ActiveStrings[1].Shutdown();
        m_ActiveStrings[2].Shutdown();
        m_ActiveStrings[3].Shutdown();

        delete [] m_ActiveStrings;
        m_ActiveStrings = 0;
    }

    // Release the font object.
    if(m_Font)
    {
        m_Font->Shutdown();
        delete m_Font;
        m_Font = 0;
    }

    // Release the font shader object.
    if(m_FontShader)
    {
        m_FontShader->Shutdown();
        delete m_FontShader;
        m_FontShader = 0;
    }

    // Release the camera object.
    if(m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }

    // Release the XInput object.
    if(m_XInput)
    {
        m_XInput->Shutdown();
        delete m_XInput;
        m_XInput = 0;
    }

    // Release the Direct3D object.
    if(m_Direct3D)
    {
        m_Direct3D->Shutdown();
        delete m_Direct3D;
        m_Direct3D = 0;
    }

    return;
}


bool ApplicationClass::Frame(InputClass* Input)
{
    bool result;


    // Check if the user pressed escape and wants to exit the application.
    if(Input->IsEscapePressed())
    {
        return false;
    }

Each frame we need to get a new state of the four possible controllers. This also lets us know whether the controllers have become inactive, or if a new controller has recently become connected to the system.

    // Do the frame processing for the XInput object.
    m_XInput->Frame();

Each frame we will update the strings for the controller's states that we are interested in for this tutorial.

    // Update the controller strings each frame.
    result = UpdateControllerStrings();
    if(!result)
    {
        return false;
    }

    // Render the graphics scene.
    result = Render();
    if(!result)
    {
        return false;
    }

    return true;
}


bool ApplicationClass::Render()
{
    XMMATRIX worldMatrix, viewMatrix, orthoMatrix;
    int i;
    bool result;


    // Clear the buffers to begin the scene.
    m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

    // Get the world, view, and projection matrices from the camera and d3d objects.
    m_Direct3D->GetWorldMatrix(worldMatrix);
    m_Camera->GetViewMatrix(viewMatrix);
    m_Direct3D->GetOrthoMatrix(orthoMatrix);

    // Disable the Z buffer and enable alpha blending for 2D rendering.
    m_Direct3D->TurnZBufferOff();
    m_Direct3D->EnableAlphaBlending();

Render all the strings for the controller states that we are looking at in this tutorial.

    // Render the controllers active text strings using the font shader.
    for(i=0; i<4; i++)
    {
        m_ActiveStrings[i].Render(m_Direct3D->GetDeviceContext());

        result = m_FontShader->Render(m_Direct3D->GetDeviceContext(), m_ActiveStrings[i].GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                                      m_Font->GetTexture(), m_ActiveStrings[i].GetPixelColor());
        if(!result)
        {
            return false;
        }	
    }

    // Render the button down strings.
    for(i=0; i<2; i++)
    {
        m_ButtonStrings[i].Render(m_Direct3D->GetDeviceContext());

        result = m_FontShader->Render(m_Direct3D->GetDeviceContext(), m_ButtonStrings[i].GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                                      m_Font->GetTexture(), m_ButtonStrings[i].GetPixelColor());
        if(!result)
        {
            return false;
        }	
    }

    // Render the trigger strings.
    for(i=0; i<2; i++)
    {
        m_TriggerStrings[i].Render(m_Direct3D->GetDeviceContext());

        result = m_FontShader->Render(m_Direct3D->GetDeviceContext(), m_TriggerStrings[i].GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                                      m_Font->GetTexture(), m_TriggerStrings[i].GetPixelColor());
        if(!result)
        {
            return false;
        }	
    }

    for(i=0; i<2; i++)
    {
        m_ThumbStrings[i].Render(m_Direct3D->GetDeviceContext());

        result = m_FontShader->Render(m_Direct3D->GetDeviceContext(), m_ThumbStrings[i].GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                                      m_Font->GetTexture(), m_ThumbStrings[i].GetPixelColor());
        if(!result)
        {
            return false;
        }	
    }

    // Enable the Z buffer and disable alpha blending now that 2D rendering is complete.
    m_Direct3D->TurnZBufferOn();
    m_Direct3D->DisableAlphaBlending();

    // Present the rendered scene to the screen.
    m_Direct3D->EndScene();

    return true;
}


bool ApplicationClass::UpdateControllerStrings()
{
    char activeString[32], buttonString[32], triggerString[32];
    int i, yPos, leftX, leftY;
    float value;
    bool result;

The m_ActiveStrings will let us know on the screen which of the four controllers is currently connected or disconnected.

    // Update the controllers active strings.
    yPos = 10;
    for(i=0; i<4; i++)
    {
        if(m_XInput->IsControllerActive(i) == true)
        {
            strcpy_s(activeString, "Controller Active: Yes");
            result = m_ActiveStrings[i].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, activeString, 10, yPos, 0.0f, 1.0f, 0.0f);
        }
        else
        {
            strcpy_s(activeString, "Controller Active: No");
            result = m_ActiveStrings[i].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, activeString, 10, yPos, 1.0f, 0.0f, 0.0f);
        }

        // Confirm the string did update.
        if(!result)
        {
            return false;
        }

        // Increment the string drawing location.
        yPos += 25;
    }
    yPos += 10;

The m_ButtonStrings will let us know the state of the A and B button on just the first connected controller.

    // Update the buttons pressed strings.
    if(m_XInput->IsControllerActive(0) == true)
    {
        // A button.
        if(m_XInput->IsButtonADown(0) == true)
        {
            strcpy_s(buttonString, "A Button Down: Yes");
            result = m_ButtonStrings[0].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, buttonString, 10, yPos, 0.0f, 1.0f, 0.0f);
        }
        else
        {
            strcpy_s(buttonString, "A Button Down: No");
            result = m_ButtonStrings[0].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, buttonString, 10, yPos, 1.0f, 0.0f, 0.0f);
        }
        if(!result)
        {
            return false;
        }
        yPos += 25;

        // B button.
        if(m_XInput->IsButtonBDown(0) == true)
        {
            strcpy_s(buttonString, "B Button Down: Yes");
            result = m_ButtonStrings[1].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, buttonString, 10, yPos, 0.0f, 1.0f, 0.0f);
        }
        else
        {
            strcpy_s(buttonString, "B Button Down: No");
            result = m_ButtonStrings[1].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, buttonString, 10, yPos, 1.0f, 0.0f, 0.0f);
        }
        if(!result)
        {
            return false;
        }
        yPos += 25;
    }
    else
    {
        strcpy_s(buttonString, "A Button Down: No");
		
        result = m_ButtonStrings[0].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, buttonString, 10, yPos, 1.0f, 0.0f, 0.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;

        strcpy_s(buttonString, "B Button Down: No");
		
        result = m_ButtonStrings[1].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, buttonString, 10, yPos, 1.0f, 0.0f, 0.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;
    }
    yPos += 10;

The m_TriggerStrings will keep us updated on how much the left and right trigger are currently pressed.

    // Controller 1 triggers.
    if(m_XInput->IsControllerActive(0) == true)
    {
        // Left trigger.
        value = m_XInput->GetLeftTrigger(0);
        sprintf_s(triggerString, "Left Trigger: %f", value);

        result = m_TriggerStrings[0].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;

        // Right trigger.
        value = m_XInput->GetRightTrigger(0);
        sprintf_s(triggerString, "Right Trigger: %f", value);

        result = m_TriggerStrings[1].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;
    }
    else
    {
        strcpy_s(triggerString, "Left Trigger: 0.0");

        result = m_TriggerStrings[0].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;

        strcpy_s(triggerString, "Right Trigger: 0.0");

        result = m_TriggerStrings[1].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;
    }
    yPos += 10;

The m_ThumbStrings will let us know the X and Y position of the left thumb stick. Do note we are using a dead zone calculation so that the thumb stick must be moved a decent amount before anything is registered.

    // Controller 1 left thumb.
    if(m_XInput->IsControllerActive(0) == true)
    {
        m_XInput->GetLeftThumbStickLocation(0, leftX, leftY);

        // Left X.
        sprintf_s(triggerString, "Left Thumb X: %d", leftX);

        result = m_ThumbStrings[0].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;

        // Left Y.
        sprintf_s(triggerString, "Left Thumb Y: %d", leftY);

        result = m_ThumbStrings[1].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;
    }
    else
    {
        strcpy_s(triggerString, "Left Thumb X: 0");

        result = m_ThumbStrings[0].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;

        strcpy_s(triggerString, "Left Thumb Y: 0");

        result = m_ThumbStrings[1].UpdateText(m_Direct3D->GetDeviceContext(), m_Font, triggerString, 10, yPos, 1.0f, 1.0f, 1.0f);
        if(!result)
        {
            return false;
        }
        yPos += 25;
    }

    return true;
}

Summary

We now have the basics implemented to use four XInput compatible controllers as input for our programs.


To Do Exercises

1. Compile and run the program to test connection and some feature of a Xbox compatible controller. Press escape to quit.

2. Remove the dead zone check for the left thumb stick to see how sensitive it is without the check in place.

3. Add functionality for all the buttons, thumb sticks, dpads, and so forth for all four possible controllers.

4. Review the xinput.h file to see all defines and functions available for programming the controller.

5. Implement a battery display.

6. Implement rumble controls.

7. Review the official online programming guide to learn about other programmable features such as headset audio, trigger tapping, and so forth.

8. Remember to always add the capability of remapping controls for preference and accessibility reasons.


Source Code

Source Code and Data Files: dx11win10tut60_src.zip

Back to Tutorial Index