Windows 应用程序中的 XInput 入门

XInput 使 Windows 应用程序能够处理控制器交互(包括控制器震动效果和语音输入和输出)。

本主题简要概述了 XInput 的功能以及如何在应用程序中设置它。 其中包括以下功能:

XAML 简介

应用程序可使用 XInput API 与插入 Windows 电脑的游戏控制器进行通信(一次最多可插入四个独特的控制器)。

使用此 API,可查询任何兼容的连接控制器的状态,并可以设置振动效果。 还可查询连接了头戴式耳机的控制器,以获取可与耳机一起使用以进行语音处理的声音输入和输出设备。

控制器布局

兼容的控制器有两个模拟方向杆,每个方向杆都有一个数字按钮、两个模拟触发器、一个具有四个方向的数字方向板和八个数字按钮。 调用 XInputGetState 函数时,每个输入的状态都会在 XINPUT_GAMEPAD 结构中返回

控制器还有两个振动电机,用于为用户提供力反馈效果。 这些电机的速度在传递给 XInputSetState 函数的 XINPUT_VIBRATION 结构中进行指定,以设置振动效果

或者,可以将头戴式耳机连接到控制器。 头戴式耳机有一个用于语音输入的麦克风,以及一个用于声音输出的耳机。 可调用 XInputGetAudioDeviceIds 或旧版 XInputGetDSoundAudioDeviceGuids 函数来获取与麦克风和耳机设备对应的设备标识符。 然后,可使用核心音频 API 来接收语音输入并发送声音输出。

使用 XInput

使用 XInput 非常简单,就像根据需要调用 XInput 函数一样。 使用 XInput 函数,可以检索控制器状态、获取头戴式耳机音频 ID 以及设置控制器震动效果。

多个控制器

XInput API 最多支持同时连接四个控制器。 XInput 函数都需要传入一个 dwUserIndex 参数来标识要进行设置或查询的控制器。 此 ID 在 0-3 的范围内,由 XInput 自动设置。 该数字对应于控制器插入的端口,并且不可修改。

每个控制器都会显示它正在使用的 ID(通过点亮控制器中心“光环”上的象限来表示)。 dwUserIndex 值 0 对应于左上象限;编号按顺时针顺序围绕环进行

应用程序应支持多个控制器。

获取控制器状态

在应用程序的整个持续时间内,从控制器获取状态可能是最常见的操作。 在游戏应用程序中,应从一帧到另一帧检索状态并更新游戏信息以反映控制器的更改。

检索状态时,请使用 XInputGetState 函数

DWORD dwResult;    
for (DWORD i=0; i< XUSER_MAX_COUNT; i++ )
{
    XINPUT_STATE state;
    ZeroMemory( &state, sizeof(XINPUT_STATE) );

    // Simply get the state of the controller from XInput.
    dwResult = XInputGetState( i, &state );

    if( dwResult == ERROR_SUCCESS )
    {
        // Controller is connected
    }
    else
    {
        // Controller is not connected
    }
}

请注意,可使用 XInputGetState 的返回值来确定是否已连接控制器。 应用程序应定义一个结构来保存内部控制器信息;应将此信息与 XInputGetState 的结果进行比较,以确定该帧进行了哪些更改,例如按钮按下或模拟控制器增量。 在上面的示例中,g_Controllers 表示此类结构

XINPUT_STATE 结构中检索到状态之后,即可检查它是否发生更改并获取有关控制器状态的特定信息

可使用 XINPUT_STATE 结构的 dwPacketNumber 成员来检查自上次调用 XInputGetState 以来控制器的状态是否已更改。 如果 dwPacketNumber 在两次连续调用 XInputGetState 之间没有发生更改,则状态不会更改。 如果有更改,则应用程序应检查 XINPUT_STATE 结构的 Gamepad 成员以获取更详细的状态信息

出于性能原因,请勿每帧调用 XInputGetState 来获取“空”用户槽位。 建议每隔几秒钟检查一次新控制器。

死区

为了让用户获得一致的游戏体验,游戏必须正确实现死区。 死区是控制器报告的“移动”值,即使模拟操纵杆未被触及和居中也是如此。 2 个模拟触发器也有一个死区。

注意

使用 XInput 但根本不筛选死区的游戏将会体验到糟糕的游戏体验。 请注意,某些控制器比其他控制器更敏感,因此死区可能因设备而异。 建议您在不同的系统上使用多个不同的控制器测试游戏。

应用程序应在模拟输入(触发器、操纵杆)上使用“死区”来指示操纵杆或触发器上的移动何时被视为有效。

应用程序应检查死区并做出适当的响应,如下例所示:

XINPUT_STATE state = g_Controllers[i].state;

float LX = state.Gamepad.sThumbLX;
float LY = state.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 > INPUT_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 -= INPUT_DEADZONE;

    //optionally normalize the magnitude with respect to its expected range
    //giving a magnitude value of 0.0 to 1.0
    normalizedMagnitude = magnitude / (32767 - INPUT_DEADZONE);
}
else //if the controller is in the deadzone zero out the magnitude
{
    magnitude = 0.0;
    normalizedMagnitude = 0.0;
}

//repeat for right thumb stick

此示例计算控制器的方向向量以及控制器沿向量被推动的距离。 这样,只需检查控制器的大小是否大于死区值,即可强制实施循环死区。 此外,代码对控制器的大小进行标准化,然后将其乘以游戏特定因子,将控制器的位置转换为与游戏相关的单位。

请注意,可以为摇杆和触发器定义自己的死区(0-65534 之间的任意位置),也可使用 XInput.h 中提供的定义为 XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE、XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 和 XINPUT_GAMEPAD_TRIGGER_THRESHOLD 的死区:

#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE  7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD    30

强制执行死区后,你可能会发现缩放结果范围 [0.0..1.0] 浮点(如上例所示)非常有用,并且可以选择应用非线性转换。

例如,对于驾驶游戏,将结果处理成多维数据集可能会有所帮助,以便为使用游戏手柄驾驶汽车提供更舒适的体验,因为将结果处理成多维数据集可以让你在较低范围内获得更高的精度,这是可取的行为,因为游戏玩家通常会应用轻柔的力量来获得微妙的运动,或者在一个方向上一直施加强力以获得快速反应。

设置振动效果

除获取控制器的状态之外,还可将振动数据发送到控制器以更改提供给控制器用户的反馈。 控制器包含两个震动电机,可通过将值传递到 XInputSetState 函数来单独进行控制

每个电机的速度可使用传递给 XInputSetState 函数的 XINPUT_VIBRATION 结构中的 WORD 值进行指定,如下所示

XINPUT_VIBRATION vibration;
ZeroMemory( &vibration, sizeof(XINPUT_VIBRATION) );
vibration.wLeftMotorSpeed = 32000; // use any value between 0-65535 here
vibration.wRightMotorSpeed = 16000; // use any value between 0-65535 here
XInputSetState( i, &vibration );

请注意,右边的电机是高频电机,左边的电机是低频电机。 它们并不总是需要设置为相同的数量,因为它们提供的效果不同。

获取音频设备标识符

控制器的头戴式耳机具有以下功能:

  • 使用麦克风录制声音
  • 使用耳机播放声音

使用此代码获取头戴式耳机的设备标识符:

WCHAR renderId[ 256 ] = {0};
WCHAR captureId[ 256 ] = {0};
UINT rcount = 256;
UINT ccount = 256;

XInputGetAudioDeviceIds( i, renderId, &rcount, captureId, &ccount );

获取设备标识符后,可以创建相应的接口。 例如,如果使用 XAudio 2.8,可使用此代码为此设备创建主要语音:

IXAudio2* pXAudio2 = NULL;
HRESULT hr;
if ( FAILED(hr = XAudio2Create( &pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR ) ) )
    return hr;

IXAudio2MasteringVoice* pMasterVoice = NULL;
if ( FAILED(hr = pXAudio2->CreateMasteringVoice( &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE, 0, renderId, NULL, AudioCategory_Communications ) ) )
    return hr;

有关如何使用 captureId 设备标识符的信息,请参阅捕获流

获取 DirectSound GUID(仅限旧版 DirectX SDK)

可连接到控制器的头戴式耳机具有两种功能:可以使用麦克风录制声音,并且可以使用耳机播放声音。 在 XInput API 中,这些功能是通过 DirectSound 使用 IDirectSound8 和 IDirectSoundCapture8 接口来完成的

要将头戴式耳机的麦克风和耳机与其相应的 DirectSound 接口关联,必须通过调用 XInputGetDSoundAudioDeviceGuids 获取捕获和渲染设备的 DirectSoundGUID

注意

不建议使用旧版 DirectSound,该版本在 Windows Store 应用中不可用。 本节中的信息仅适用于 XInput 的 DirectX SDK 版本 (XInput 1.3)。 Windows 8 版本的 XInput (XInput 1.4) 专门使用通过 XInputGetAudioDeviceIds 获取的 Windows 音频会话 API (WASAPI) 设备标识符

XInputGetDSoundAudioDeviceGuids( i, &dsRenderGuid, &dsCaptureGuid );

检索到 GUID 后,可通过调用 DirectSoundCreate8 和 DirectSoundCaptureCreate8 创建适当的接口,如下所示:

// Create IDirectSound8 using the controller's render device
if( FAILED( hr = DirectSoundCreate8( &dsRenderGuid, &pDS, NULL ) ) )
   return hr;

// Set coop level to DSSCL_PRIORITY
if( FAILED( hr = pDS->SetCooperativeLevel( hWnd, DSSCL_NORMAL ) ) )
   return hr;

// Create IDirectSoundCapture using the controller's capture device
if( FAILED( hr = DirectSoundCaptureCreate8( &dsCaptureGuid, &pDSCapture, NULL ) ) )
   return hr;

编程参考