用XInput库使用xbox360手柄

用XInput库使用xbox360手柄

前言

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);


}

测试效果

下图为通过手柄操作后的结果

GamePad

实际的源码请参考
https://github.com/Lveyou/DND
要运行此例子请跑其中的DNDTest项目

作者:吴泔游 QQ:1339484752 日期:2017-09-28

相关文章
相关标签/搜索