Windows 应用程序中的 XInput 入门

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

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

XInput 简介

当应用程序插入 Windows 电脑时,应用程序可以使用 XInput API 与游戏控制器通信(一次最多可以插入四个唯一控制器)。

使用此 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 使用 IDirectSound8IDirectSoundCapture8 接口来完成的。

若要将耳机麦克风和耳机与其相应的 DirectSound 接口相关联,必须通过调用 XInputGetDSoundAudioDeviceGuids 来获取捕获和呈现设备的 DirectSoundGUID。

注释

不建议使用旧 版 DirectSound ,在 Windows 应用商店应用中不可用。 本节中的信息仅适用于 XInput 的 DirectX SDK 版本(XInput 1.3)。 Windows 8 版本的 XInput (XInput 1.4) 专门使用通过 XInputGetAudioDeviceId 获取的 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;

编程参考