Posted on: 2024-12-28, Updated on: 2024-12-28 03:55:42 | Read time: 13.9 minutes
Topic(s): programming & gamedev
The XInput API is used for gathering input information from Xbox 360 and Xbox One controllers on Windows and Xbox operating systems. The API is relatively simple in that it only relies on a few functions and structures.
I'll be making a regular console application just to show that things work. To use XInput we need to link against a version. Available versions range from v1.3, v9.1.0, and v1.4 (in that order). All of which have their advantages and disadvantages that can be seen here on MSDN: XInput Versions. I will be using v1.4 which is the newest one available on Windows 10. Go to Project->Properties->Linker->Input->Additional Dependencies and add xinput.lib. You may also wish to use Visual C/C++'s #pragma comment(lib, "xinput.lib") as a easier way to link the library with the preprocessor.
Go to Project->Add Class and name the class Gamepad
in the class Wizard. Now we can start coding in the Gamepad.h header file
#pragma once
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#pragma comment(lib, "xinput.lib")
#include <XInput.h>
#include <queue>
#include <string>
#include <optional>
We add our include directives at the top of the header file. We define NOMINMAX
before Windows because we will use the std::min
and std::max
functions later in our member function definitions of the Gamepad
class. This removes the MIN
and MAX
macros normally in the Windows header that could potentially interfere with the standard library min and max functions. The WIN32_LEAN_AND_MEAN
macro excludes some of the less important parts of the Win32 API. And last but not least, we include the XInput header.
We can then begin the declaration for the Gamepad
class:
class Gamepad
{
// Soon to be filled in...
};
The Gamepad
class will declare several nested types: Button
, ButtonEvent
, Axis
, and Deadzone
to make interacting with the class easier for the user.
// Inside of Gamepad class declaration
public:
enum class Button : unsigned int
{
DPAD_UP = XINPUT_GAMEPAD_DPAD_UP,
DPAD_DOWN = XINPUT_GAMEPAD_DPAD_DOWN,
DPAD_LEFT = XINPUT_GAMEPAD_DPAD_LEFT,
DPAD_RIGHT = XINPUT_GAMEPAD_DPAD_RIGHT,
START = XINPUT_GAMEPAD_START,
BACK = XINPUT_GAMEPAD_BACK,
LEFT_THUMB = XINPUT_GAMEPAD_LEFT_THUMB,
RIGHT_THUMB = XINPUT_GAMEPAD_RIGHT_THUMB,
LEFT_SHOULDER = XINPUT_GAMEPAD_LEFT_SHOULDER,
RIGHT_SHOULDER = XINPUT_GAMEPAD_RIGHT_SHOULDER,
A = XINPUT_GAMEPAD_A,
B = XINPUT_GAMEPAD_B,
X = XINPUT_GAMEPAD_X,
Y = XINPUT_GAMEPAD_Y,
};
The Button
scoped enum provides aliases for the XINPUT_GAMEPAD_ button constants that are easier to write.
// Button declaration above...
class ButtonEvent
{
public:
enum class Type
{
PRESS,
RELEASE,
};
private:
Type type;
Button button;
public:
ButtonEvent( Type type, Button button ) noexcept
:
type( type ),
button ( button )
{}
bool IsPress() const noexcept
{
return type == Type::PRESS;
}
bool IsRelease() const noexcept
{
return type == Type::RELEASE;
}
Button GetButton() const noexcept
{
return button;
}
};
The ButtonEvent
nested class encapsulates a Button
value with the scoped enum ButtonEvent::Type
that indicates whether a button was pressed or released.
// ButtonEvent declaration above...
struct Axis
{
float x, y;
};
using Deadzone = Axis;
The Axis
struct simply contains two members, x and y, for representing an axis with two directions such as the controller's thumbsticks. Deadzone
is an alias for indicating the values are intended to applied as an axis deadzone. Deadzones represent a small area in which the left and right thumbsticks will not read values starting in the center. This is because not all joysticks are perfect and might read in very small values when not used causing the camera in a first person game, for example, to be very jittery.
public:
// id is a number of 1-4 but is represented by 0-3 in the API
explicit Gamepad( unsigned int id, const Deadzone& deadzone = {
XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE,
XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE
} );
Gamepad( const Gamepad& ) = delete;
Gamepad& operator=( const Gamepad& ) = delete;
Gamepad( Gamepad&& ) noexcept = delete;
Gamepad& operator=( Gamepad&& ) noexcept = delete;
The sole constructor takes in an id parameter that will be used to initialize controllerID
. It also takes an optional deadzone parameter which is default initialized to the XINPUT_GAMEPAD_THUMB_DEADZONE
constants. The copy and move constructors/assignment operators are marked as deleted to prevent copy or move construction.
UINT GetControllerID() const noexcept;
XINPUT_GAMEPAD* GetGamepad() noexcept;
const XINPUT_GAMEPAD* GetGamepad() const noexcept;
bool IsConnected();
bool Update();
void Vibrate( unsigned short leftSpeed, unsigned short rightSpeed );
void Vibrate( unsigned short bothSpeed );
void ResetVibration();
bool IsButtonPressed( Button button ) const;
std::optional<ButtonEvent> ReadButtonBuffer() noexcept;
Axis& LeftStick() noexcept;
const Axis& LeftStick() const noexcept;
Axis& RightStick() noexcept;
const Axis& RightStick() const noexcept;
float LeftTrigger() const noexcept;
float RightTrigger() const noexcept;
bool GetAudioDeviceIDs(
const std::wstring& pRenderDeviceId, unsigned int* pRenderCount,
const std::wstring& pCaptureDeviceId, unsigned int* pCaptureCount
) const;
XINPUT_CAPABILITIES* GetCapabilities( unsigned long flags = 0u );
const XINPUT_CAPABILITIES* GetCapabilities(unsigned long flags = 0u) const;
void SetDeadzone( const Deadzone &deadzone ) noexcept;
Deadzone& GetDeadzone() noexcept;
const Deadzone& GetDeadzone() const noexcept;
bool ButtonIsEmpty() const noexcept;
void Flush() noexcept;
// XInputGetBatteryInformation is not supported for 9.1.0
// XINPUT_BATTERY_INFORMATION* GetBatteryInfo();
// void Enable(bool value) const; no supported for 9.1.0
These functions will be explained in the definitions to keep things clear. Functions regarding XINPUT_BATTERY_INFORMATION
are of course commented out because they do not work in XInput v1.4.
private: // axis & trigger values
Axis leftStick;
Axis rightStick;
float leftTrigger, rightTrigger;
private:
void OnButtonPressed( Button button ) noexcept;
void OnButtonReleased( Button button ) noexcept;
static float ApplyDeadzone( float value, float maxValue, float deadzone );
template <typename buf>
static void TrimBuffer( std::queue<buf>& buffer ) noexcept
{
while ( buffer.size() > bufferSize )
buffer.pop(); // [[yesdiscard]] xD
}
private:
static constexpr unsigned int bufferSize = 16u;
static constexpr float maxAxisValue = 1.0f;
static constexpr float triggerThreshold = XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 255.0f;
unsigned int controllerID;
Deadzone deadzone;
mutable XINPUT_STATE state;
mutable XINPUT_VIBRATION vibration;
mutable XINPUT_BATTERY_INFORMATION battery;
mutable XINPUT_CAPABILITIES capabilities;
std::queue<ButtonEvent> buttonbuffer;
Now we will declare the private class members. The leftStick
and rightStick
members contain the x and y values of the left and right stacks in the range of -1.0 to 1.0. The leftTrigger
and rightTrigger
members contain the values of the left and right trigger axes in the range of 0.0 to 1.0.
The member functions void OnButtonPressed( Button ) noexcept
and void OnButtonReleased( Button ) noexcept
process buttons as they are pressed and released. The static member function void TrimBuffer( std::queue<buf>& ) noexcept
is used to limit the buttonBuffer
to bufferSize
elements. The static member function float ApplyDeadzone( float, float, float )
takes in the value of the axis, the max value of that axis, and the deadzone (in the range -1.0 to 1.0) to be applied.
The maxAxisValue
constant will be used as a constant for the normalized(a value from -1.0f to +1.0f) max value of our controller's left and right joysticks. The controllerID
represents the selected controller that we will perform operations on. Windows allows for up to 4 controllers to be connected at once. In our class the range of the controllerID
will be 1-4 but XInput functions expect a number ranging from 0-3. So we will simply subtract one from the controllerID
when we use it as a parameter. The deadzone
member contains the deadzone that will be applied to both the left and right thumbsticks. The XINPUT_STATE
structure contains state of all of the controllers buttons in a XINPUT_GAMEPAD
member, as seen here:
typedef struct _XINPUT_STATE {
DWORD dwPacketNumber;
XINPUT_GAMEPAD Gamepad;
} XINPUT_STATE, *PXINPUT_STATE;
The XINPUT_VIBRATION
structure consists of the speeds for the left and right force-feedback motors housed in the controller:
typedef struct _XINPUT_VIBRATION {
WORD wLeftMotorSpeed;
WORD wRightMotorSpeed;
} XINPUT_VIBRATION, *PXINPUT_VIBRATION;
The XINPUT_BATTERY_INFORMATION
structure is commented out because it is not available in XInput v1.4.
The buttonbuffer
queue is used to maintain a sequence of the most recent ButtonEvent
's. The queue's capacity is capped by the bufferSize
constant.
Now we can move to the Gamepad.cpp source file.
#include "Gamepad.h"
#include <algorithm>
#include <climits>
#include <cmath>
Let's start by adding our include directories. We will of course include our Gamepad.h header file and algorithm which includes std::min
and std::max
which we will use later for working with deadzones. climits has constants for the maximum and minimum numbers that a variable type can hold which will be normalizing the axis values. cmath is used for some operations regarding the gamepad's axis values.
static float Normalize( float value, float min, float max );
Gamepad::Gamepad( unsigned int id, const Deadzone& deadzone )
: controllerID( id ), deadzone( deadzone )
{
ZeroMemory( &state, sizeof(XINPUT_STATE) );
ZeroMemory( &vibration, sizeof(XINPUT_VIBRATION) );
ZeroMemory( &battery, sizeof(XINPUT_BATTERY_INFORMATION) );
ZeroMemory( &capabilities, sizeof(XINPUT_CAPABILITIES) );
}
We'll start with a forward declaration of a non-member function Normalize
, which I will explain later. Then we define the Gamepad
constructor which uses the initializer list to initialize controllerID
and the deadzone
. The deadzones are initialized with the XInput constants:
// In the XInput header:
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
These are likely meant to be SHORT
values so we will have to normalize these later on. In the constructor body we call ZeroMemory
on both the XINPUT_STATE
and XINPUT_VIBRATION
member structures to give them default values.
UINT Gamepad::GetControllerID() const noexcept
{
return this->controllerID - 1;
}
XINPUT_GAMEPAD* Gamepad::GetGamepad() noexcept
{
return &this->state.Gamepad;
}
const XINPUT_GAMEPAD *Gamepad::GetGamepad() const noexcept
{
return &this->state.Gamepad;
}
/*XINPUT_BATTERY_INFORMATION* Gamepad::GetBatteryInfo()
{
XInputGetBatteryInformation(controllerID, XINPUT_DEVTYPE_GAMEPAD, &battery);
return &battery;
}*/
bool Gamepad::IsConnected()
{
return ( XInputGetState( this->controllerID - 1, &state ) == ERROR_SUCCESS )
? true : false;
}
The GetControllerID
member function returns the controller ID number. The GetGamepad
member function and its const overload return a pointer to the XINPUT_GAMEPAD
type. The GetBatteryInfo
member function implementation is commented out since the API version used in this tutorial doesn't support retrieving battery information. The IsConnected
member function uses the XInputGetState
function to update the state structure. The first parameter takes in the ID of the controller ranging from 0-3. Since our controllerID
expects class users to enter a value from 1 to 4 we will subtract one from controllerID
. The second parameter takes in a pointer to our XINPUT_STATE
member so it can be updated. XInputGetState
returns a DWORD
that either equates to the ERROR_SUCCES
or ERROR_DEVICE_NOT_CONNECTED
constants. If the call equates to ERROR_SUCCESS
the function returns true, otherwise it returns false.
bool Gamepad::Update()
{
if (!IsConnected())
return false; // don't update the controller if it's not connected
if (state.Gamepad.wButtons != 0u)
{
OnButtonPressed( static_cast<Gamepad::Button>(state.Gamepad.wButtons) );
}
else
{
if (!ButtonIsEmpty())
OnButtonReleased( buttonbuffer.front().getButton() );
}
static constexpr std::numeric_limits<short> limitShrt{};
static constexpr auto limitMin = limitShrt.min();
static constexpr auto limitMax = limitShrt.max();
// Normalize input from the left and right stick's x and y axes.
const float normLX = Normalize( static_cast<float>(state.Gamepad.sThumbLX),
limitMin, limitMax);
const float normLY = Normalize( static_cast<float>(state.Gamepad.sThumbLY),
limitMin, limitMax);
const float normRX = Normalize( static_cast<float>(state.Gamepad.sThumbRX),
limitMin, limitMax);
const float normRY = Normalize( static_cast<float>(state.Gamepad.sThumbRY),
limitMin, limitMax);
// Apply the deadzones to the normalized form of each stick axes.
leftStick.x = ApplyDeadzone( normLX, maxAxisValue, Normalize(deadzone.x, limitMin, limitMax) );
leftStick.y = ApplyDeadzone( normLY, maxAxisValue, Normalize(deadzone.y, limitMin, limitMax) );
rightStick.x = ApplyDeadzone( normRX, maxAxisValue, Normalize(deadzone.x, limitMin, limitMax) );
rightStick.y = ApplyDeadzone( normRY, maxAxisValue, Normalize(deadzone.y, limitMin, limitMax) );
// Normalize trigger input of BYTE/char limit.
static constexpr std::numeric_limits<unsigned char> limitChr{};
leftTrigger = (
static_cast<float>(state.Gamepad.bLeftTrigger)
/ static_cast<float>(limitChr.max())
);
rightTrigger = (
static_cast<float>(state.Gamepad.bRightTrigger)
/ static_cast<float>(limitChr.max())
);
return true;
}
In the Update function we first check if the controller is connected, if not it Update returns false and the rest of the function body is not performed. The IsConnected
member function also takes care of updating the XINPUT_STATE
member. This ensures that the rest of the update will have access to updated controller information. If the controller is connected, the Update
member function continues by updating button state, normalizing thumbstick axes, and applying deadzones.
/*void Gamepad::Enable(bool value) const
{
// XInputEnable is meant for use when an application loses/gains focus
// for example the WM_ACTIVATEAPP message.
// you may pass FALSE when you wish for vibrations to stop and vice versa
XInputEnable(static_cast<BOOL>(value));
}*/
The Enable
member function is also commented out like GetBatteryInfo
due to the API version.
void Gamepad::Vibrate( unsigned short leftSpeed, unsigned short rightSpeed )
{
vibration.wLeftMotorSpeed = leftSpeed;
vibration.wRightMotorSpeed = rightSpeed;
XInputSetState( GetControllerID(), &vibration );
}
void Gamepad::Vibrate( unsigned short lrSpeed )
{
vibration.wLeftMotorSpeed = lrSpeed;
vibration.wRightMotorSpeed = lrSpeed / 2u;
XInputSetState( GetControllerID(), &vibration );
}
void Gamepad::ResetVibration()
{
vibration.wLeftMotorSpeed = 0u;
vibration.wRightMotorSpeed = 0u;
XInputSetState(GetControllerID(), &vibration);
}
The above member functions set the state of the gamepad's vibration motors. Vibrate
takes in two different speeds, one for the left and one for the right. The parameter type is unsigned short
because the motor speed range is 0-65535. The XINPUT_VIBRATION contains the wLeftMotorSpeed
and wRightMotorSpeed
members to control the force feedback motors' speeds. The XInputSetState
is specific for updating controller vibration effects. It takes the controllerID
as the first parameter and a pointer to the vibration
member variable. The overload of Vibrate
with a single parameter halves the speed for the right motor since it stronger than the left in XBox 360 and XBox One controllers. ResetVibration
simply resets both motor speeds to 0 and updates the vibration state.
bool Gamepad::IsButtonPressed( Button button ) const
{
return ( state.Gamepad.wButtons & static_cast<unsigned short>(button) ) != 0u;
}
The IsButtonPressed
member function returns true
if the given gamepad button
is currently held down.
std::optional<Gamepad::ButtonEvent> Gamepad::ReadButtonBuffer() noexcept
{
if ( buttonbuffer.size() > 0u )
{
ButtonEvent e = buttonbuffer.front();
buttonbuffer.pop();
return e;
}
return {};
}
The ReadButtonBuffer
member function attempts removes a ButtonEvent
from the front of the buttonbuffer
. As such this member function can be used to retrieve the most recent button presses.
Gamepad::Axis& Gamepad::LeftStick() noexcept
{
return leftStick;
}
const Gamepad::Axis& Gamepad::LeftStick() const noexcept
{
return leftStick;
}
Gamepad::Axis& Gamepad::RightStick() noexcept
{
return rightStick;
}
const Gamepad::Axis& Gamepad::RightStick() const noexcept
{
return rightStick;
}
float Gamepad::LeftTrigger() const noexcept
{
return leftTrigger;
}
float Gamepad::RightTrigger() const noexcept
{
return rightTrigger;
}
The above member functions simply return the axis values for the left thumbstick, right thumbstick, left trigger, and right trigger.
bool Gamepad::GetAudioDeviceIDs( const std::wstring& pRenderDeviceId, unsigned int* pRenderCount,
const std::wstring& pCaptureDeviceId, unsigned int* pCaptureCount ) const
{ // I'm not quite sure how to use this one but this should wrap it okay
const auto result = XInputGetAudioDeviceIds( GetControllerID(),
const_cast<wchar_t*>(pRenderDeviceId.c_str()), pRenderCount,
const_cast<wchar_t*>(pCaptureDeviceId.c_str()), pCaptureCount );
if (result != ERROR_SUCCESS)
return false;
return true;
}
XINPUT_CAPABILITIES* Gamepad::GetCapabilities( unsigned long flags )
{ // returns const pointer to XINPUT_CAPABILITIES structure that details
// useful charateristics about the controller with the dwUserIndex parameter
// you may pass XINPUT_FLAG_GAMEPAD as the value of flags to limit use to Xbox 360 controllers
const auto result = XInputGetCapabilities(GetControllerID(), flags,
&this->capabilities);
return &this->capabilities;
}
const XINPUT_CAPABILITIES* Gamepad::GetCapabilities( unsigned long flags ) const
{
const auto result = XInputGetCapabilities(GetControllerID(), flags,
&this->capabilities);
return &this->capabilities;
}
The GetCapabilities
member function wraps the XInputGetCapabilities
function which returns a XINPUT_CAPABILITIES
structure that contains information about the gamepad and what features it supports.
void Gamepad::SetDeadzone( const Deadzone& deadzone ) noexcept
{
this->deadzone = deadzone;
}
Gamepad::Deadzone& Gamepad::GetDeadzone() noexcept
{
return deadzone;
}
const Gamepad::Deadzone& Gamepad::GetDeadzone() const noexcept
{
return deadzone;
}
The GetDeadzone
and SetDeadzone
member functions simply retrieve and set the current deadzone
used by the thumbsticks respectively.
void Gamepad::OnButtonPressed( Button button ) noexcept
{
using ButtonEventType = ButtonEvent::Type;
buttonbuffer.push( ButtonEvent( ButtonEventType::PRESS,button ) );
TrimBuffer( buttonbuffer );
}
void Gamepad::OnButtonReleased( Button button ) noexcept
{
using ButtonEventType = ButtonEvent::Type;
buttonbuffer.push( ButtonEvent( ButtonEventType::RELEASE,button ) );
TrimBuffer( buttonbuffer );
}
bool Gamepad::ButtonIsEmpty() const noexcept
{
return buttonbuffer.empty();
}
void Gamepad::Flush() noexcept
{
buttonbuffer = std::queue<Gamepad::ButtonEvent>();
}
OnButtonPressed
and OnButtonReleased
take care of updating the buttonbuffer
. ButtonIsEmpty
returns true
if the buttonbuffer
is empty. Flush
clears the buttonbuffer
by assigning to an empty queue.
static float Normalize(float value, float min, float max)
{ // Normalize value between min/max range.
const float average = (min + max) / 2.0f;
const float range = (max - min) / 2.0f;
return (value - average) / range;
}
This nonmember function, Normalize
, follows a formula to turn our controller's axes values to numbers ranging from -1.0f to 1.0f because XInput's default controller axis values are large values for the type SHORT
.
float Gamepad::ApplyDeadzone( float value, float maxValue, float deadzone)
{
if (value < -(deadzone))
{
value += deadzone; // increase neg vals to remove deadzone discontinuity
}
else if (value > deadzone)
{
value -= deadzone; // decrease pos vals to remove deadzone discontinuity
}
else
{
return 0.0f; // hey values are zero for once
}
const float normValue = value / (maxValue - deadzone);
return std::max(-1.0f, std::min(normValue, 1.0f));
}
The ApplyDeadone function takes in a value of the axis, maxValue of the axis when normalized(typically 1.0f), and a deadzone. Since deadzones essentaily delete a small area around the origin at which input can be read that means that if there were no further processing then a deadzone of .2f would mean our axis could only read up to .8f in any direction.
Let's write a small program to make use of the class.
#include <iostream>
#include <chrono>
#include <thread>
#include <iomanip>
#include <cstdlib>
#include "Gamepad.h"
Here are our include directives. We of course include iostream
to use the std::cout
. We will use thread
with chrono
as a way to sleep this_thread
during a while loop. The input output stream manipulations iomanip
will be used for rounding off the axis values.
static auto Clear() -> void;
auto main(int argc, char** argv) -> int
{
using Button = Gamepad::Button;
Gamepad gamepad(1u);
std::cout << sizeof(gamepad) << '\n';
bool aPressed = false;
if (!gamepad.IsConnected())
{
std::cout << "Controller not connected\n";
std::cout.flush();
std::cin.get();
return -1;
}
We created a gampad object with a controllerID of 1 and bool called a aPressed to check if the A button on the controller is pressed. If the gamepad is not connected it will return.
while (true)
{
if (gamepad.Update())
{
::Clear();
std::cout << std::fixed << std::setprecision(2)
<< "Left Trigger: " << gamepad.LeftTrigger()
<< ", Right Trigger: " << gamepad.RightTrigger() << '\n'
<< "Left Stick " << "X: " << gamepad.LeftStick().x
<< ", Y: " << gamepad.LeftStick().y << '\n'
<< "Right Stick " << "X: " << gamepad.RightStick().x
<< ", Y: " << gamepad.RightStick().y << std::endl;
Here we open a loop and call gamepad::Update
in an if statement to make sure it's connected and to refresh the controller state structure and axis information. Later, std::fixed
and std::setprecision(2)
is inserted into the stream to limit decimal numbers to two places past the decimal.
if (gamepad.IsButtonPressed(Button::A))
{
aPressed = true;
}
else
{
aPressed = false;
}
if (aPressed == true)
{
gamepad.Vibrate(20000, 20000);
}
else
{
gamepad.ResetVibration();
}
This code of course uses the Gamepad::IsButtonPressed
member function to cause the controller to vibrate while the A button is pressed.
if (gamepad.IsButtonPressed(Button::BACK))
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cin.get();
return 0;
}
This final section check if the back button is pressed and exits the loop ending the program. At the very end of the loop std::this_thread::sleep_for
is called to delay each run of the loop by 10 milliseconds.
That concludes this tutorial. Hopefully it helped you, and if you have any questions please leave a comment.
Resources/Useful Links:
Here is the full source code:
#include <iostream>
#include <chrono>
#include <thread>
#include <iomanip>
#include <cstdlib>
#include "Gamepad.h"
static auto Clear() -> void;
auto main(int argc, char** argv) -> int
{
using Button = Gamepad::Button;
Gamepad gamepad(1u);
std::cout << sizeof(gamepad) << '\n';
bool aPressed = false;
if (!gamepad.IsConnected())
{
std::cout << "Controller not connected\n";
std::cout.flush();
std::cin.get();
return -1;
}
while (true)
{
if (gamepad.Update())
{
::Clear();
std::cout << std::fixed << std::setprecision(2)
<< "Left Trigger: " << gamepad.LeftTrigger()
<< ", Right Trigger: " << gamepad.RightTrigger() << '\n'
<< "Left Stick " << "X: " << gamepad.LeftStick().x
<< ", Y: " << gamepad.LeftStick().y << '\n'
<< "Right Stick " << "X: " << gamepad.RightStick().x
<< ", Y: " << gamepad.RightStick().y << std::endl;
if (gamepad.IsButtonPressed(Button::A))
{
aPressed = true;
}
else
{
aPressed = false;
}
if (aPressed == true)
{
gamepad.Vibrate(20000, 20000);
}
else
{
gamepad.ResetVibration();
}
if (gamepad.IsButtonPressed(Button::BACK))
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::cin.get();
return 0;
}
inline static auto Clear() -> void
{
#ifdef _WIN32 || _WIN64
std::system("cls");
#else
std::system("Clear");
#endif
}
#pragma once
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#pragma comment(lib, "xinput.lib")
#include <XInput.h>
#include <queue>
#include <string>
#include <optional>
class Gamepad
{
public:
enum class Button : unsigned int
{
DPAD_UP = XINPUT_GAMEPAD_DPAD_UP,
DPAD_DOWN = XINPUT_GAMEPAD_DPAD_DOWN,
DPAD_LEFT = XINPUT_GAMEPAD_DPAD_LEFT,
DPAD_RIGHT = XINPUT_GAMEPAD_DPAD_RIGHT,
START = XINPUT_GAMEPAD_START,
BACK = XINPUT_GAMEPAD_BACK,
LEFT_THUMB = XINPUT_GAMEPAD_LEFT_THUMB,
RIGHT_THUMB = XINPUT_GAMEPAD_RIGHT_THUMB,
LEFT_SHOULDER = XINPUT_GAMEPAD_LEFT_SHOULDER,
RIGHT_SHOULDER = XINPUT_GAMEPAD_RIGHT_SHOULDER,
A = XINPUT_GAMEPAD_A,
B = XINPUT_GAMEPAD_B,
X = XINPUT_GAMEPAD_X,
Y = XINPUT_GAMEPAD_Y,
};
class ButtonEvent
{
public:
enum class Type
{
PRESS,
RELEASE,
};
private:
Type type;
Button button;
public:
ButtonEvent( Type type, Button button ) noexcept
:
type( type ),
button ( button )
{}
bool IsPress() const noexcept
{
return type == Type::PRESS;
}
bool IsRelease() const noexcept
{
return type == Type::RELEASE;
}
Button getButton() const noexcept
{
return button;
}
};
struct Axis
{
float x, y;
};
using Deadzone = Axis;
public:
// id is a number of 1-4 but is represented by 0-3 in the API
Gamepad( unsigned int id, const Deadzone& deadzone = {
XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE,
XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE
} );
Gamepad( const Gamepad& ) = delete;
Gamepad& operator=( const Gamepad& ) = delete;
Gamepad( Gamepad&& ) noexcept = delete;
Gamepad& operator=( Gamepad&& ) noexcept = delete;
UINT GetControllerID() const noexcept;
XINPUT_GAMEPAD* GetGamepad() noexcept;
const XINPUT_GAMEPAD* GetGamepad() const noexcept;
bool IsConnected();
bool Update();
void Vibrate( unsigned short leftSpeed, unsigned short rightSpeed );
void Vibrate( unsigned short bothSpeed );
void ResetVibration();
bool IsButtonPressed( Button button ) const;
std::optional<ButtonEvent> ReadButtonBuffer() noexcept;
Axis& LeftStick() noexcept;
const Axis& LeftStick() const noexcept;
Axis& RightStick() noexcept;
const Axis& RightStick() const noexcept;
float LeftTrigger() const noexcept;
float RightTrigger() const noexcept;
bool GetAudioDeviceIDs(
const std::wstring& pRenderDeviceId, unsigned int* pRenderCount,
const std::wstring& pCaptureDeviceId, unsigned int* pCaptureCount
) const;
XINPUT_CAPABILITIES* GetCapabilities( unsigned long flags = 0u );
const XINPUT_CAPABILITIES* GetCapabilities(unsigned long flags = 0u) const;
void SetDeadzone( const Deadzone &deadzone ) noexcept;
Deadzone& GetDeadzone() noexcept;
const Deadzone& GetDeadzone() const noexcept;
bool ButtonIsEmpty() const noexcept;
void Flush() noexcept;
// XInputGetBatteryInformation is not supported for 9.1.0
// XINPUT_BATTERY_INFORMATION* getBatteryInfo();
// void Enable(bool value) const; no supported for 9.1.0
private: // axis & trigger values
Axis leftStick;
Axis rightStick;
float leftTrigger, rightTrigger;
private:
void OnButtonPressed( Button button ) noexcept;
void OnButtonReleased( Button button ) noexcept;
static float ApplyDeadzone( float value, float maxValue, float deadzone );
template <typename buf>
static void TrimBuffer( std::queue<buf>& buffer ) noexcept
{
while ( buffer.size() > bufferSize )
buffer.pop(); // [[yesdiscard]] xD
}
private:
static constexpr unsigned int bufferSize = 16u;
static constexpr float maxAxisValue = 1.0f;
static constexpr float triggerThreshold = XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 255.0f;
unsigned int controllerID;
Deadzone deadzone;
mutable XINPUT_STATE state;
mutable XINPUT_VIBRATION vibration;
mutable XINPUT_BATTERY_INFORMATION battery;
mutable XINPUT_CAPABILITIES capabilities;
std::queue<ButtonEvent> buttonbuffer;
};
#include "Gamepad.h"
#include <algorithm>
#include <climits>
#include <cmath>
static float Normalize( float value, float min, float max );
Gamepad::Gamepad( unsigned int id, const Deadzone& deadzone )
: controllerID( id ), deadzone( deadzone )
{
ZeroMemory( &state, sizeof(XINPUT_STATE) );
ZeroMemory( &vibration, sizeof(XINPUT_VIBRATION) );
ZeroMemory( &battery, sizeof(XINPUT_BATTERY_INFORMATION) );
ZeroMemory( &capabilities, sizeof(XINPUT_CAPABILITIES) );
}
UINT Gamepad::GetControllerID() const noexcept
{
return this->controllerID - 1;
}
XINPUT_GAMEPAD* Gamepad::GetGamepad() noexcept
{
return &this->state.Gamepad;
}
const XINPUT_GAMEPAD *Gamepad::GetGamepad() const noexcept
{
return &this->state.Gamepad;
}
/*XINPUT_BATTERY_INFORMATION* Gamepad::getBatteryInfo()
{
XInputGetBatteryInformation(controllerID, XINPUT_DEVTYPE_GAMEPAD, &battery);
return &battery;
}*/
bool Gamepad::IsConnected()
{
return ( XInputGetState( this->controllerID - 1, &state ) == ERROR_SUCCESS )
? true : false;
}
bool Gamepad::Update()
{
if (!IsConnected())
return false; // don't update the controller if it's not connected
if (state.Gamepad.wButtons != 0u)
{
OnButtonPressed( static_cast<Gamepad::Button>(state.Gamepad.wButtons) );
}
else
{
if (!ButtonIsEmpty())
OnButtonReleased( buttonbuffer.front().getButton() );
}
static constexpr std::numeric_limits<short> limitShrt{};
static constexpr auto limitMin = limitShrt.min();
static constexpr auto limitMax = limitShrt.max();
// Normalize input from the left and right stick's x and y axes.
const float normLX = Normalize( static_cast<float>(state.Gamepad.sThumbLX),
limitMin, limitMax);
const float normLY = Normalize( static_cast<float>(state.Gamepad.sThumbLY),
limitMin, limitMax);
const float normRX = Normalize( static_cast<float>(state.Gamepad.sThumbRX),
limitMin, limitMax);
const float normRY = Normalize( static_cast<float>(state.Gamepad.sThumbRY),
limitMin, limitMax);
// Apply the deadzones to the normalized form of each stick axes.
leftStick.x = ApplyDeadzone( normLX, maxAxisValue, Normalize(deadzone.x, limitMin, limitMax) );
leftStick.y = ApplyDeadzone( normLY, maxAxisValue, Normalize(deadzone.y, limitMin, limitMax) );
rightStick.x = ApplyDeadzone( normRX, maxAxisValue, Normalize(deadzone.x, limitMin, limitMax) );
rightStick.y = ApplyDeadzone( normRY, maxAxisValue, Normalize(deadzone.y, limitMin, limitMax) );
// Normalize trigger input of BYTE/char limit.
static constexpr std::numeric_limits<unsigned char> limitChr{};
leftTrigger = (
static_cast<float>(state.Gamepad.bLeftTrigger)
/ static_cast<float>(limitChr.max())
);
rightTrigger = (
static_cast<float>(state.Gamepad.bRightTrigger)
/ static_cast<float>(limitChr.max())
);
return true;
}
/*void Gamepad::Enable(bool value) const
{
// XInputEnable is meant for use when an application loses/gains focus
// for example the WM_ACTIVATEAPP message.
// you may pass FALSE when you wish for vibrations to stop and vice versa
XInputEnable(static_cast<BOOL>(value));
}*/
void Gamepad::Vibrate( unsigned short leftSpeed, unsigned short rightSpeed )
{
vibration.wLeftMotorSpeed = leftSpeed;
vibration.wRightMotorSpeed = rightSpeed;
XInputSetState( GetControllerID(), &vibration );
}
void Gamepad::Vibrate( unsigned short lrSpeed )
{
vibration.wLeftMotorSpeed = lrSpeed;
vibration.wRightMotorSpeed = lrSpeed / 2u;
XInputSetState( GetControllerID(), &vibration );
}
void Gamepad::ResetVibration()
{
vibration.wLeftMotorSpeed = 0u;
vibration.wRightMotorSpeed = 0u;
XInputSetState(GetControllerID(), &vibration);
}
bool Gamepad::IsButtonPressed( Button button ) const
{
return ( state.Gamepad.wButtons & static_cast<unsigned short>(button) ) != 0u;
}
std::optional<Gamepad::ButtonEvent> Gamepad::ReadButtonBuffer() noexcept
{
if ( buttonbuffer.size() > 0u )
{
ButtonEvent e = buttonbuffer.front();
buttonbuffer.pop();
return e;
}
return {};
}
Gamepad::Axis& Gamepad::LeftStick() noexcept
{
return leftStick;
}
const Gamepad::Axis& Gamepad::LeftStick() const noexcept
{
return leftStick;
}
Gamepad::Axis& Gamepad::RightStick() noexcept
{
return rightStick;
}
const Gamepad::Axis& Gamepad::RightStick() const noexcept
{
return rightStick;
}
float Gamepad::LeftTrigger() const noexcept
{
return leftTrigger;
}
float Gamepad::RightTrigger() const noexcept
{
return rightTrigger;
}
bool Gamepad::GetAudioDeviceIDs( const std::wstring& pRenderDeviceId, unsigned int* pRenderCount,
const std::wstring& pCaptureDeviceId, unsigned int* pCaptureCount ) const
{ // I'm not quite sure how to use this one but this should wrap it okay
const auto result = XInputGetAudioDeviceIds( GetControllerID(),
const_cast<wchar_t*>(pRenderDeviceId.c_str()), pRenderCount,
const_cast<wchar_t*>(pCaptureDeviceId.c_str()), pCaptureCount );
if (result != ERROR_SUCCESS)
return false;
return true;
}
XINPUT_CAPABILITIES* Gamepad::GetCapabilities( unsigned long flags )
{ // returns const pointer to XINPUT_CAPABILITIES structure that details
// useful charateristics about the controller with the dwUserIndex parameter
// you may pass XINPUT_FLAG_GAMEPAD as the value of flags to limit use to Xbox 360 controllers
const auto result = XInputGetCapabilities(GetControllerID(), flags,
&this->capabilities);
return &this->capabilities;
}
const XINPUT_CAPABILITIES* Gamepad::GetCapabilities( unsigned long flags ) const
{
const auto result = XInputGetCapabilities(GetControllerID(), flags,
&this->capabilities);
return &this->capabilities;
}
void Gamepad::SetDeadzone( const Deadzone& deadzone ) noexcept
{
this->deadzone = deadzone;
}
void Gamepad::OnButtonPressed( Button button ) noexcept
{
using ButtonEventType = ButtonEvent::Type;
buttonbuffer.push( ButtonEvent( ButtonEventType::PRESS,button ) );
TrimBuffer( buttonbuffer );
}
void Gamepad::OnButtonReleased( Button button ) noexcept
{
using ButtonEventType = ButtonEvent::Type;
buttonbuffer.push( ButtonEvent( ButtonEventType::RELEASE,button ) );
TrimBuffer( buttonbuffer );
}
Gamepad::Deadzone& Gamepad::GetDeadzone() noexcept
{
return deadzone;
}
const Gamepad::Deadzone& Gamepad::GetDeadzone() const noexcept
{
return deadzone;
}
bool Gamepad::ButtonIsEmpty() const noexcept
{
return buttonbuffer.empty();
}
void Gamepad::Flush() noexcept
{
buttonbuffer = std::queue<Gamepad::ButtonEvent>();
}
float Gamepad::ApplyDeadzone( float value, const float maxValue, float deadzone)
{
if (value < -(deadzone))
{
value += deadzone; // increase neg vals to remove deadzone discontinuity
}
else if (value > deadzone)
{
value -= deadzone; // decrease pos vals to remove deadzone discontinuity
}
else
{
return 0.0f; // hey values are zero for once
}
const float normValue = value / (maxValue - deadzone);
return std::max(-1.0f, std::min(normValue, 1.0f));
}
static float Normalize(float value, float min, float max)
{ // Normalize value between min/max range.
const float average = (min + max) / 2.0f;
const float range = (max - min) / 2.0f;
return (value - average) / range;
}