XInput库是微软开发的库,功能是让xbox360类型的手柄能在Windows PC平台使用。它被附带在DXSDK_Jun10开发包中(我写的框架基于这个版本),链接和实际使用都特别简单。这篇文章应该可以让你更快的使用上XInput。
包含文件和DX11的配置一样,头文件都在同一个目录下,如果之前配置了DX库,就不需要额外做什么。只需要链接上附加依赖项 XInput.lib。
在代码中也只需要包含头文件XInput.h。
class Input_imp;//输入的单例类
class DLL_API GamePad
{
friend class Input_imp;
public:
//按键状态
bool KeyUp(int pad);
bool KeyDown(int pad);
bool KeyState(int pad);
//LT、RT的按下程度:[0,1]
float ForceLT();
float ForceRT();
//LS、RS的位置偏移程度:[0,1]
Vector2 GetLS();
Vector2 GetRS();
//LS、RS离原地的距离:[0,1]
float ForceLS();
float ForceRS();
//设置振动,左右马达的各自强度:[0,1]
void SetVibration(Vector2 lr);
private:
UINT32 _id;//手柄编号 0-3
Input_imp* _input;
};
上面的值均规范化到0到1的范围。
其中LT、RT需要返回按下的程度,在XInput库中名字叫Trigger。LS、RS代表左右两个摇杆,在Xinput中叫Thumb。
其它的按钮均只有按下和非按下状态,所以使用KeyUp、KeyDown、KeyState来返回它们的状态。KeyUp代表上一帧按下,这一帧没有按下。KeyDown代表上一帧没按下,但此帧按下。KeyState返回当前是否按下。
单个状态的按钮枚举值如下:
struct DLL_API PadCode
{
public:
enum NAME
{
UP = 0x00000001,
DOWN = 0x00000002,
LEFT = 0x00000004,
RIGHT = 0x00000008,
START = 0x00000010,
BACK = 0x00000020,
LS = 0x00000040,
RS = 0x00000080,
LB = 0x0100,
RB = 0x0200,
A = 0x1000,
B = 0x2000,
X = 0x4000,
Y = 0x8000
};
};
通过名字,读者就应该知道代表什么含义了。如果要检测组合键应该使用第二种方式:
gamepad->KeyDown(UP | LEFT);//错误
gamepad->KeyDown(A) && gamepad->KeyDown(B);//正确
由于暂时支持最多4个手柄,Input_imp类需要存储4个GamePad类对象,4个当前按键状态,4个上一帧按键状态。用户调用GetGamePad(N)时返回第N个手柄,如果手柄不存在,就返回NULL。
XINPUT_STATE _xinputState[MAX_CONTROLLERS];//当前状态
XINPUT_STATE _xinputStatePre[MAX_CONTROLLERS];//上一帧状态
GamePad _gamePad[MAX_CONTROLLERS];//手柄接口
bool _gamePadConnected[MAX_CONTROLLERS];//手柄是否连接
XINPUT_STATE结构体包含了手柄按键的所有状态,如下
typedef struct _XINPUT_GAMEPAD
{
WORD wButtons; //单个状态按钮们的组合值
BYTE bLeftTrigger;//LT [0,255]
BYTE bRightTrigger;//RT [0,255]
SHORT sThumbLX;//LS 的X轴位移 [-32768,32767]
SHORT sThumbLY;//LS 的Y轴位移 [-32768,32767]
SHORT sThumbRX;//RS 的X轴位移 [-32768,32767]
SHORT sThumbRY;//RS 的Y轴位移 [-32768,32767]
} XINPUT_GAMEPAD, *PXINPUT_GAMEPAD;
typedef struct _XINPUT_STATE
{
DWORD dwPacketNumber;
XINPUT_GAMEPAD Gamepad;
} XINPUT_STATE, *PXINPUT_STATE;
我们就在每帧调用下面的函数,获取到手柄的按钮状态:
void Input_imp::_xinput_run()
{
DWORD dwResult;
for (DWORD i = 0; i < MAX_CONTROLLERS; i++)
{
_xinputStatePre[i] = _xinputState[i];//上一帧状态
ZeroMemory(&_xinputState[i], sizeof(XINPUT_STATE));
// Simply get the state of the controller from XInput.
dwResult = XInputGetState(i, &_xinputState[i]);
if (dwResult == ERROR_SUCCESS)
{//获取成功
// Controller is connected
_gamePadConnected[i] = true;
}
else
{//获取失败,手柄不存在
// Controller is not connected
_gamePadConnected[i] = false;
}
}
}
然后手柄状态被存储到了_xinputState中,接下来只需要GampPad类访问即可,但其中需要做一些处理。第一是将数值规范化到[0,1],其次是过小的值需要归0。所有成员函数的实现都列出来了,由于并不复杂,我就不多说了,许多直接copy自文档,并做了一些改动(文档的并不能直接使用)。
GamePad* Input_imp::GetGamePad(UINT32 id)
{
if (_gamePadConnected[id])
return &_gamePad[id];
else
return NULL;
}
bool GamePad::KeyUp(int pad)
{
return !(_input->_xinputState[_id].Gamepad.wButtons & pad) &&
(_input->_xinputStatePre[_id].Gamepad.wButtons & pad);
}
bool GamePad::KeyDown(int pad)
{
return (_input->_xinputState[_id].Gamepad.wButtons & pad) &&
!(_input->_xinputStatePre[_id].Gamepad.wButtons & pad);
}
bool GamePad::KeyState(int pad)
{
return _input->_xinputState[_id].Gamepad.wButtons & pad;
}
float GamePad::ForceLT()
{
if (_input->_xinputState[_id].Gamepad.bLeftTrigger < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
return 0;
return _input->_xinputState[_id].Gamepad.bLeftTrigger / 255.0f;
}
float GamePad::ForceRT()
{
BYTE ret = _input->_xinputState[_id].Gamepad.bRightTrigger;
if (ret < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
return 0;
return ret / 255.0f;
}
Vector2 GamePad::GetLS()
{
float LX = _input->_xinputState[_id].Gamepad.sThumbLX;
float LY = -_input->_xinputState[_id].Gamepad.sThumbLY;
if (abs(LX) < XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE)
LX = 0;
if (abs(LY) < XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE)
LY = 0;
if (LX == 0 && LY == 0)
return Vector2();
//determine how far the controller is pushed
float magnitude = sqrt(LX*LX + LY*LY);
//determine the direction the controller is pushed
float normalizedLX = LX / magnitude;
float normalizedLY = LY / magnitude;
return Vector2(normalizedLX, normalizedLY);
}
Vector2 GamePad::GetRS()
{
float RX = _input->_xinputState[_id].Gamepad.sThumbRX;
float RY = -_input->_xinputState[_id].Gamepad.sThumbRY;
if (abs(RX) < XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE)
RX = 0;
if (abs(RY) < XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE)
RY = 0;
if (RX == 0 && RY == 0)
return Vector2();
//determine how far the controller is pushed
float magnitude = sqrt(RX*RX + RY*RY);
//determine the direction the controller is pushed
float normalizedRX = RX / magnitude;
float normalizedRY = RY / magnitude;
return Vector2(normalizedRX, normalizedRY);
}
float GamePad::ForceLS()
{
//copy 自DirectX文档
float LX = _input->_xinputState[_id].Gamepad.sThumbLX;
float LY = _input->_xinputState[_id].Gamepad.sThumbLY;
//determine how far the controller is pushed
float magnitude = sqrt(LX*LX + LY*LY);
//determine the direction the controller is pushed
float normalizedLX = LX / magnitude;
float normalizedLY = LY / magnitude;
float normalizedMagnitude = 0;
//check if the controller is outside a circular dead zone
if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE)
{
//clip the magnitude at its expected maximum value
if (magnitude > 32767) magnitude = 32767;
//adjust magnitude relative to the end of the dead zone
magnitude -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
//optionally normalize the magnitude with respect to its expected range
//giving a magnitude value of 0.0 to 1.0
normalizedMagnitude = magnitude / (32767 - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
}
else //if the controller is in the deadzone zero out the magnitude
{
magnitude = 0.0f;
normalizedMagnitude = 0.0f;
}
return normalizedMagnitude;
}
float GamePad::ForceRS()
{
//copy 自DirectX文档
float RX = _input->_xinputState[_id].Gamepad.sThumbLX;
float RY = _input->_xinputState[_id].Gamepad.sThumbLY;
//determine how far the controller is pushed
float magnitude = sqrt(RX*RX + RY*RY);
//determine the direction the controller is pushed
float normalizedRX = RX / magnitude;
float normalizedRY = RY / magnitude;
float normalizedMagnitude = 0;
//check if the controller is outside a circular dead zone
if (magnitude > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE)
{
//clip the magnitude at its expected maximum value
if (magnitude > 32767) magnitude = 32767;
//adjust magnitude relative to the end of the dead zone
magnitude -= XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
//optionally normalize the magnitude with respect to its expected range
//giving a magnitude value of 0.0 to 1.0
normalizedMagnitude = magnitude / (32767 - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
}
else //if the controller is in the deadzone zero out the magnitude
{
magnitude = 0.0f;
normalizedMagnitude = 0.0f;
}
return normalizedMagnitude;
}
void GamePad::SetVibration(Vector2 lr)
{
XINPUT_VIBRATION vibration;
ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION));
vibration.wLeftMotorSpeed = lr.a * 65535; // use any value between 0-65535 here
vibration.wRightMotorSpeed = lr.b * 65535; // use any value between 0-65535 here
XInputSetState(_id, &vibration);
}
下图为通过手柄操作后的结果
实际的源码请参考
https://github.com/Lveyou/DND
要运行此例子请跑其中的DNDTest项目
作者:吴泔游
QQ:1339484752
日期:2017-09-28