使用 XInput 在 C++ 中获取 Xbox 360 控制器输入
一个小型教程,介绍如何使用 XInput (需要 DX SDK) 来处理 Windows 上 Xbox 360 控制器的输入。
引言
在本教程结束时,您应该能够了解如何在 C++ 程序中实现 Windows 版 Xbox 360 控制器的基础知识,无论是游戏还是任何其他需要使用控制器的项目。
类定义 (CXBOXController)
这是我们的 Xbox 控制器类的定义代码
#ifndef _XBOX_CONTROLLER_H_
#define _XBOX_CONTROLLER_H_
// No MFC
#define WIN32_LEAN_AND_MEAN
// We need the Windows Header and the XInput Header
#include <windows.h>
#include <XInput.h>
// Now, the XInput Library
// NOTE: COMMENT THIS OUT IF YOU ARE NOT USING
// A COMPILER THAT SUPPORTS THIS METHOD OF LINKING LIBRARIES
#pragma comment(lib, "XInput.lib")
// XBOX Controller Class Definition
class CXBOXController
{
private:
XINPUT_STATE _controllerState;
int _controllerNum;
public:
CXBOXController(int playerNumber);
XINPUT_STATE GetState();
bool IsConnected();
void Vibrate(int leftVal = 0, int rightVal = 0);
};
#endif
代码详解
这定义了一个简单的 C++ 类,用于管理 Xbox 360 控制器。
_controllerState
存储 Xbox 360 控制器的状态,_controllerNum
存储类正在存储的控制器的引用 (0-3)。GetState()
更新控制器的状态并向调用者返回状态信息,以便可以检查输入 (这将在稍后完善程序时进行)。IsConnected()
检查以确保控制器已连接,如果成功则返回 ERROR_SUCCESS
。Vibrate()
函数为我们提供了一种快速简便的振动控制器的方法。
完善代码
#include "CXBOXController.h"
CXBOXController::CXBOXController(int playerNumber)
{
// Set the Controller Number
_controllerNum = playerNumber - 1;
}
XINPUT_STATE CXBOXController::GetState()
{
// Zeroise the state
ZeroMemory(&_controllerState, sizeof(XINPUT_STATE));
// Get the state
XInputGetState(_controllerNum, &_controllerState);
return _controllerState;
}
bool CXBOXController::IsConnected()
{
// Zeroise the state
ZeroMemory(&_controllerState, sizeof(XINPUT_STATE));
// Get the state
DWORD Result = XInputGetState(_controllerNum, &_controllerState);
if(Result == ERROR_SUCCESS)
{
return true;
}
else
{
return false;
}
}
void CXBOXController::Vibrate(int leftVal, int rightVal)
{
// Create a Vibraton State
XINPUT_VIBRATION Vibration;
// Zeroise the Vibration
ZeroMemory(&Vibration, sizeof(XINPUT_VIBRATION));
// Set the Vibration Values
Vibration.wLeftMotorSpeed = leftVal;
Vibration.wRightMotorSpeed = rightVal;
// Vibrate the controller
XInputSetState(_controllerNum, &Vibration);
}
代码详解
这是 `CXBOXController` 类被完善并赋予生命的地方。
首先,`CXBOXController` 构造函数根据玩家编号分配控制器编号 (玩家 1 = 0,玩家 2 = 1,玩家 3 = 2,玩家 4 = 3)。
接下来,我们完善获取控制器状态的函数。首先,我们必须将游戏状态的指针清零,以确保没有任何伪影,从而可以检查输入。然后,我们调用 `XInputGetState`,并将控制器编号和控制器状态变量的地址传递进去,以存储控制器的状态。这将确保 `_controllerState` 始终是最新的。然后,我们返回控制器的状态。
之后,我们实现 `IsConnected()`,如果控制器已连接且没有错误,则返回 true,如果出现问题则返回 false。与 `GetState()` 函数一样,我们需要将内存清零并更新状态,以便在控制器突然断开连接时能够跟上控制器的状态。如果控制器已连接且没有问题,`XInputGetState()` 将返回 `ERROR_SUCCESS`,表示一切正常。
最后,我们实现一个允许振动的特性。Xbox 360 有两个振动马达,一个在左边,一个在右边。我们通过允许左马达和右马达的单独值来考虑这一点。每个值都可以从 0 到 65535,表示马达振动的强度,65535 为最强。我们首先定义一个 `XINPUT_VIBRATION` 结构体的实例,它允许我们将振动速度存储在一个结构体中。为了谨慎起见,我们将此内存清零,然后设置振动速度,最后使用 `XInputSetState` 设置振动状态。由于我们将 `leftVal` 和 `rightVal` 的默认值设置为 0,我们可以单独调用 *controller->Vibrate()
* 来停止控制器上的所有振动。
测试应用程序
这段小代码将允许我们测试控制器类
#include "CXBOXController.h"
#include <iostream>
CXBOXController* Player1;
int main(int argc, char* argv[])
{
Player1 = new CXBOXController(1);
std::cout << "Instructions:\n";
std::cout << "[A] Vibrate Left Only\n";
std::cout << "[B] Vibrate Right Only\n";
std::cout << "[X] Vibrate Both\n";
std::cout << "[Y] Vibrate Neither\n";
std::cout << "[BACK] Exit\n";
while(true)
{
if(Player1->IsConnected())
{
if(Player1->GetState().Gamepad.wButtons & XINPUT_GAMEPAD_A)
{
Player1->Vibrate(65535, 0);
}
if(Player1->GetState().Gamepad.wButtons & XINPUT_GAMEPAD_B)
{
Player1->Vibrate(0, 65535);
}
if(Player1->GetState().Gamepad.wButtons & XINPUT_GAMEPAD_X)
{
Player1->Vibrate(65535, 65535);
}
if(Player1->GetState().Gamepad.wButtons & XINPUT_GAMEPAD_Y)
{
Player1->Vibrate();
}
if(Player1->GetState().Gamepad.wButtons & XINPUT_GAMEPAD_BACK)
{
break;
}
}
else
{
std::cout << "\n\tERROR! PLAYER 1 - XBOX 360 Controller Not Found!\n";
std::cout << "Press Any Key To Exit.";
std::cin.get();
break;
}
}
delete(Player1);
return( 0 );
}
我将不对最后这段代码的大部分内容做详细解释,因为它应该很明显它做什么。
但是,我将重点介绍以下几点:
CXBOXController* Player1;
...
Player1 = new CXBOXController(1);
我选择这样定义是因为我喜欢在处理控制器这类事物时使用指针。这使得我可以轻松地传递它们,如果我想以非全局变量的形式发送它们的话。构造函数中的 1 表示这是玩家 1 的控制器 (或根据 XInput 为控制器 #0)。
if(Player1->GetState().Gamepad.wButtons & XINPUT_GAMEPAD_A)
这是检查游戏手柄状态的代码。要检查按钮输入,必须使用逻辑 `AND` 来检查按钮是否被按下。
用于检查控制器状态按钮的十六进制值
(注意:从 MSDN 网站获取)
XINPUT_GAMEPAD_DPAD_UP 0x00000001
XINPUT_GAMEPAD_DPAD_DOWN 0x00000002
XINPUT_GAMEPAD_DPAD_LEFT 0x00000004
XINPUT_GAMEPAD_DPAD_RIGHT 0x00000008
XINPUT_GAMEPAD_START 0x00000010
XINPUT_GAMEPAD_BACK 0x00000020
XINPUT_GAMEPAD_LEFT_THUMB 0x00000040
XINPUT_GAMEPAD_RIGHT_THUMB 0x00000080
XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
XINPUT_GAMEPAD_A 0x1000
XINPUT_GAMEPAD_B 0x2000
XINPUT_GAMEPAD_X 0x4000
XINPUT_GAMEPAD_Y 0x8000
您还可以使用 `GetState()` 来获取操纵杆位置以及左右扳机的值,通过 `bLeftTrigger`、`bRightTrigger`、`sThumbLX`、`sThumbLY`、`sThumbRX` 和 `sThumbRY`。更多信息可以在 MSDN 上找到。
XINPUT_GAMEPAD 结构体
(注意:从 MSDN 网站获取)
typedef struct _XINPUT_GAMEPAD {
WORD wButtons;
BYTE bLeftTrigger;
BYTE bRightTrigger;
SHORT sThumbLX;
SHORT sThumbLY;
SHORT sThumbRX;
SHORT sThumbRY;
} XINPUT_GAMEPAD, *PXINPUT_GAMEPAD;
历史
- 2008 年 6 月 13 日:编写教程。