-
Notifications
You must be signed in to change notification settings - Fork 513
GamePad
DirectXTK | DirectXTK12 |
---|
This is a helper for simplified access to gamepad controllers modeled after the XNA Game Studio 4 (Microsoft.Xna.Framework.Input
) GamePad class.
Related tutorial: Game controller input
#include <GamePad.h>
GamePad is a singleton.
std::unique_ptr<GamePad> gamepad;
gamepad = std::make_unique<GamePad>();
For exception safety, it is recommended you make use of the C++ RAII pattern and use a std::unique_ptr
.
GetState queries the controller status given a player index. If connected, it returns the status of the buttons (A, B, X, Y, left & right stick, left & right shoulder, back, and start), the directional pad (DPAD), the left & right thumb sticks, and the left & right triggers.
auto state = gamePad->GetState( 0 );
if ( state.IsConnected() )
{
if ( state.IsAPressed() )
// Do action for button A being down
if ( state.buttons.y )
// Do action for button Y being down
if ( state.IsDPadLeftPressed() )
// Do action for DPAD Left being down
if ( state.dpad.up || state.dpad.down || state.dpad.left || state.dpad.right )
// Do action based on any DPAD change
float posx = state.thumbSticks.leftX;
float posy = state.thumbSticks.leftY;
// These values are normalized to -1 to 1
float throttle = state.triggers.right;
// This value is normalized 0 -> 1
if ( state.IsLeftTriggerPressed() )
// Do action based on a left trigger pressed more than halfway
if ( state.IsViewPressed() )
// This is an alias for the Xbox 360 'Back' button
// which is called 'View' on the Xbox One controller.
}
The valid range for player is 0 to GamePad::MAX_PLAYER_COUNT - 1
. Outside that range, the state is always reported as disconnected.
If player is passed as
GamePad::c_MostRecent
(-1), then GamePad will use the most recently connected gamepad. This is not a good usage behavior for games or apps, but is useful for tests and samples where you don't have "press a key to start" logic for handling multiple gamepads.
GameInput-based implementations also support
GamePad::c_MergedInput
(-2) which returns input from all connected controllers. This is again not intended for games or apps, but is useful for tests and samples.
Since GamePad is a singleton, you can make use of the static method Get if desired:
auto state = GamePad::Get().GetState()
GamePad::State field | Description |
---|---|
bool connected | Set to true if a gamepad is present at that player index. |
uint64_t packet | A timestamp order value. |
Buttons buttons | |
bool buttons.a bool buttons.b bool buttons.x bool buttons.y |
A/B/X/Y buttons. |
bool buttons.leftStick bool buttons.rightStick |
Left-stick/right-stick push buttons. |
bool buttons.leftShoulder bool buttons.rightShoulder |
Left/right-shoulder buttons. |
bool buttons.view | View button (also accessible as buttons.back). |
bool buttons.menu | Menu button (also accessible as buttons.start). |
DPad dpad | |
bool dpad.up | Directional-pad up. |
bool dpad.down | Directional-pad down. |
bool dpad.right | Directional-pad right. |
bool dpad.left | Directional-pad left. |
ThumbSticks thumbSticks | |
float thumbSticks.leftX float thumbSticks.leftY |
Left thumb-stick X/Y in range -1..1. |
float thumbSticks.rightX float thumbSticks.rightY |
Right thumb-stick X/Y in range -1..1. |
Triggers triggers | |
float triggers.left | Left trigger value in range 0..1. |
float triggers.right | Right trigger value in range 0..1. |
GamePad implements the same dead zone scheme as XNA.
-
DEAD_ZONE_INDEPENDENT_AXES
which is the default -
DEAD_ZONE_CIRCULAR
which provides a deadzone for the combined X/Y axes -
DEAD_ZONE_NONE
which provides 'raw' scaled information to allow the application to implement dead zones
For example:
auto state = gamePad->GetState( 0, GamePad::DEAD_ZONE_CIRCULAR );
See Shawn's blog for details.
Many controllers include vibration motors to provide force-feedback to the user, which can be controlled with SetVibration and the player index. The motor values range from 0 to 1.
if ( gamePad->SetVibration( 0, 0.5f, 0.25f ) )
// If true, the vibration was successfully set.
The GamePad class provides a simplified model for the device capabilities.
auto caps = gamePad->GetCapabilities( 0 );
if ( caps.IsConnected() )
{
if ( caps.gamepadType == GamePad::Capabilities::FLIGHT_STICK )
// Use specific controller layout based on a flight stick controller
else
// Default to treating any unknown type as a standard gamepad
}
Much of the information reported in
XINPUT_CAPABILITIES
is omitted. This is because much of it is unreliable (see below), but also because the actionable information is entirely captured by the gamepadType subtype.
Unlike mouse or keyboard input on Windows, XInput has 'global' focus when reading the game controller. Therefore, applications should ignore the input when in the background. To implement this, you can call the following method when you lose focus, which will shut off any active vibration and then return 'neutral' data for all connected gamepads.
gamePad->Suspend();
When focus is returned to the application, call the following method to restore any active vibration and read gamepad data again.
gamePad->Resume();
A common pattern for gamepads is to trigger an action when a button is pressed or released, but you don't want to trigger the action every single frame if the button is held down for more than a single frame. This helper class simplifies this.
GamePad::ButtonStateTracker tracker;
...
auto state = gamePad->GetState( 0 );
if ( state.IsConnected() )
{
tracker.Update( state );
if ( tracker.a == GamePad::ButtonStateTracker::PRESSED )
// Take an action when Button A is first pressed, but don't do it again until
// the button is released and then pressed again
}
else
{
tracker.Reset();
}
Each button is reported by the tracker with a state:
-
PRESSED
: Indicates that the button was just pushed down since the previousUpdate
. -
RELEASED
: Indicates that the button was just let up since the previousUpdate
. -
UP
: This indicates the button has been up both for thisUpdate
and the previous one. -
HELD
: This indicates the button has been held down both for thisUpdate
and the previous one.
The
UP
andHELD
states are for convenience and readability as they provide exactly the same information as the result fromGamePad::GetState
.
You may find that using GamePad::ButtonStateTracker::PRESSED
is a bit verbose. You can simplify the code by doing:
if ( state.IsConnected() )
{
tracker.Update( state );
using ButtonState = GamePad::ButtonStateTracker::ButtonState;
if ( tracker.a == ButtonState::PRESSED )
// Take an action when Button A is first pressed, but don't do it again until
// the button is released and then pressed again
}
When resuming from a pause or suspend, be sure to call Reset on the tracker object to clear the state history.
If you are using multiple controllers, remember each 'player' needs their own instance of the GamePad::ButtonStateTracker
.
The GamePad class provides no special synchronization above the underlying API. XInput on Windows is thread-safe through a internal global lock, so performance is best when only a single thread accesses the controller.
The GamePad object and the underlying XInput APIs are polling based. Typically the controller is read once per render frame, which means that low frame rate can result in missing user input.
For the Windows platform, the GamePad class ensures that attempts to locate unconnected controllers do not happen too frequently to avoid a potential performance issue with XInput.
When built for Windows 8.0 or 8.1, it makes use of XInput 1.4 (linking to xinput.lib
). When built for down-level support, it makes use of XInput 9.1.0 which avoids the need for any dependency on the legacy DirectSetup (linking to xinput9_1_0.lib
).
When building with the MinGW toolset, the GamePad uses XInput and you should explicitly link with
xinput1_4.lib
orxinput9_1_0.lib
.
When built for Windows 10, it makes use of Windows.Gaming.Input
. This class assumes that the client code has called Windows::Foundation::Initialize
as needed.
For a Universal Windows Platform (UWP) app, the Windows Runtime (and COM generally) is initialized by the C/C++ Run-Time. For a classic Windows desktop application you have to do this explicitly:
Microsoft::WRL::Wrappers::RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
if (FAILED(initialize))
// Error
Note that subtype capabilities information is somewhat unreliable down-level depending on your exact mix of device and driver, and in some cases is hard-coded. All capabilities information is reliable on Windows 8.0 or later.
XInput supports controllers compatible with the Xbox 360 Common Controller for Windows, the Xbox 360 Wireless Receiver for Windows, and the Xbox One Controller.
Vibration settings for the trigger impulse motors (leftTrigger
, rightTrigger
) on the Xbox One Controller are not supported by XInput--this is supported by Windows.Gaming.Input
APIs. The "View" button is reported as the "Back" button, and the "Menu" button is reported as the "Start" button.
For Microsoft GDK, this class is implemented using GameInput interfaces rather than XInput. It is abstracted to return the same structures. Here are a few notes:
- state.packet is the reading number from GameInput
-
MAX_PLAYER_COUNT
is 8 rather than 4 -
c_MergedInput
is supported forGetState
-
Capabilities::id
isAPP_LOCAL_DEVICE_ID
. The VID and PID are returned as reported by GameInput.
GameInput is supported for Xbox One and Xbox Series X|S using the Microsoft GDK. GameInput is supported on Windows PC as of the June 2022 release of the Microsoft GDK.
On Xbox One using the legacy Xbox One XDK, this class is implemented using the Windows.Xbox.Input interfaces rather than XInput. It is abstracted to return the same structures. Here are a few notes:
- state.packet is a timestamp in "Universal time" format.
-
MAX_PLAYER_COUNT
is 8 rather than 4 - Currently only the GAMEPAD type is reported for Xbox One controllers
The player index mapping is not correlated directly with a user as it is on Xbox 360 or Windows, and is assigned 'upon arrival'. To determine the actual user for a given gamepad, you should use the controller ID reported as part of the Capabilities.
auto caps = gamePad->GetCapabilities( playerIndex );
if ( caps.IsConnected() )
{
try
{
auto ctrl = Controller::GetControllerById( caps.id );
if ( ctrl )
{
User^ user = ctrl->User;
// user is the user associated with the controller, if any
}
}
catch ( Platform::Exception ^ e )
{
// error handling, likely the controller has been removed
// since the caps were obtained
}
}
When built for Windows 10, the GamePad class is implemented using a new WinRT Windows.Gaming.Input
API similar to the Xbox One API. Here are a few notes:
- Full support for
leftTrigger
andrightTrigger
motors for the Xbox One controller on Windows. -
MAX_PLAYER_COUNT
is 8 rather than 4 - Only the GAMEPAD type is reported for Xbox One controllers (i.e. controllers that support the
Windows::Gaming::Input::Gamepad
interface) -
Capabilities::id
is astd::wstring
. The VID and PID are valid as reported by the WGI API.
ArcadeSticks, FlightSticks, and RacingWheels don't support the
Windows::Gaming::Input::Gamepad
interface, although they do supportUINavigation
.
Technically the
Windows.Gaming.Input
API can support more than 8 devices, but the DirectX Tool KitGamePad
implementation can only support up toMAX_PLAYER_COUNT
.
Whenever the B button on a gamepad controller is pressed on Xbox One, the running UWP app is sent a "back request" (like the hardware 'Back' button on Windows Mobile). If this is unhandled, the application will be suspended and the previous application is brought forward. This can make using the B button in your UI design a challenge, so the recommended solution is to add a message handler to 'handle' the request:
void SetWindow(CoreWindow^ window)
{
...
auto navigation = Windows::UI::Core::SystemNavigationManager::GetForCurrentView();
navigation->BackRequested +=
ref new EventHandler<BackRequestedEventArgs^>(this, &ViewProvider::OnBackRequested);
...
}
void OnBackRequested(Platform::Object^, Windows::UI::Core::BackRequestedEventArgs^ args)
{
// UWP on Xbox One triggers a back request whenever the B button is pressed
// which can result in the app being suspended if unhandled
args->Handled = true;
}
#include <winrt/Windows.UI.Core.h>
// UWP on Xbox One triggers a back request whenever the B button is pressed
// which can result in the app being suspended if unhandled
using namespace winrt::Windows::UI::Core;
auto navigation = SystemNavigationManager::GetForCurrentView();
navigation.BackRequested([](const winrt::Windows::Foundation::IInspectable&, const BackRequestedEventArgs& args)
{
args.Handled(true);
});
Xbox 360 Controller Images
Xbox 360 Controller Buttons
See MakeSpriteFont.
DirectX Tool Kit: Now with GamePads
XInput and Windows 8
XInput and XAudio2
All content and source code for this package are subject to the terms of the MIT License.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
- Universal Windows Platform apps
- Windows desktop apps
- Windows 11
- Windows 10
- Windows 8.1
- Xbox One
- x86
- x64
- ARM64
- Visual Studio 2022
- Visual Studio 2019 (16.11)
- clang/LLVM v12 - v18
- MinGW 12.2, 13.2
- CMake 3.20