Windows アプリケーション上の XInput の概要

XInput を使用することで、コントローラーの対話 (コントローラーのランブル効果や音声入出力を含む) を Windows アプリケーションで処理できるようになります。

このトピックでは、XInput の機能と、アプリケーションで設定する方法の簡単な概要について説明します。 これには、以下が含まれます。

XInput の概要

アプリケーションは、XInput API を使用して、Windows PC に接続されているゲーム コントローラーと通信できます (一度に最大 4 つの一意のコントローラーを接続できます)。

この API を使用すると、互換性のある接続コントローラーの状態を照会し、振動効果を設定できます。 ヘッドセットが接続されているコントローラーは、ヘッドセットと共に音声処理用に使用できるサウンド入出力デバイスを照会することもできます。

コントローラーのレイアウト

互換性のあるコントローラーには、2つのアナログ方向スティックがあり、それぞれにデジタル ボタン、2 つのアナログ トリガー、4 方向のデジタル方向パッド、8 つのデジタル ボタンを備えています。 XInputGetState 関数が呼び出されると、これらの各入力の状態が XINPUT_GAMEPAD 構造体で返されます。

コントローラーには、ユーザーにフォース フィードバック効果を付与する 2 つの振動モーターもあります。 これらのモーターの速度は、振動効果を設定するために、XInputSetState 関数に渡される XINPUT_VIBRATION 構造体で指定されます。

必要に応じて、ヘッドセットをコントローラーに接続することもできます。 ヘッドセットには音声入力用のマイクと、サウンド出力用のヘッドフォンがあります。 XInputGetAudioDeviceIds 関数または従来の XInputGetDSoundAudioDeviceGuids 関数を呼び出して、マイクとヘッドフォンのデバイスに対応するデバイス識別子を取得できます。 その後、コア オーディオ API を使用して音声入力を受信し、サウンド出力を送信できます。

XInput の使用

XInput は、必要に応じて XInput 関数を呼び出すだけで使用できます。 XInput 関数を使用すると、コントローラーの状態の取得、ヘッドセットのオーディオ ID の取得、コントローラーの振動効果の設定を行うことができます。

複数のコントローラー

XInput API は、いつでも最大 4 つのコントローラーの接続をサポートしています。 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 の最後の呼び出し以降に変更されたかどうかをチェックできます。 dwPacketNumberXInputGetState への 2 つの連続した呼び出しの間に変化しない場合は、状態に変更はありません。 異なる場合、アプリケーションは、XINPUT_STATE 構造体の Gamepad メンバーをチェックして、より詳細な状態情報を取得する必要があります。

パフォーマンス上の理由から、フレームごとに "空" のユーザー スロットに対して XInputGetState を呼び出さないでください。 代わりに、新しいコントローラーのチェックを数秒おきに行うことをお勧めします。

デッド ゾーン

ユーザーが一貫したゲームプレイ エクスペリエンスを得るには、ゲームでデッド ゾーンを正しく実装する必要があります。 デッド ゾーンは、アナログ サムスティックがそのままの状態で中央に配置されている場合でも、コントローラーから報告される "移動" 値のことです。 2 つのアナログ トリガーにはデッド ゾーンもあります。

Note

デッド ゾーンをまったくフィルター処理していない 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] 浮動小数点にスケーリングし、必要に応じて非線形変換を適用すると便利な場合があります。

たとえば、ドライビング ゲームの場合、結果を 3 乗することで、ゲームパッドを使用して車を運転する感覚を向上させるのに役立つ場合があります。3 乗することで低い範囲での精度が高まるため、ゲーマーが一般的に微妙な動きを行うために弱い力で押したり RD 応答を得るために強い力で押したりすることを選択するため、望ましい結果になります。

振動効果の設定

コントローラーの状態を取得することに加えて、コントローラーに振動データを送信して、コントローラーのユーザーに提供されるフィードバックを変更することもできます。 コントローラーには、XInputSetState 関数に値を渡すことによって個別に制御できる、2 つの振動モーターがあります。

各モーターの速度は、次のように 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 のみ)

コントローラーに接続できるヘッドセットには、マイクを使用してサウンドを録音し、ヘッドフォンを使用してサウンドを再生できる 2 つの機能があります。 XInput API では、これらの関数は、IDirectSound8 インターフェイスと IDirectSoundCapture8 インターフェイスを使用する DirectSound を通じて実現できます。

ヘッドセットのマイクとヘッドフォンを適切な DirectSound インターフェイスに関連付けるには、XInputGetDSoundAudioDeviceGuids を呼び出して、キャプチャおよびレンダリング デバイスの DirectSoundGUIDs を取得する必要があります。

Note

従来の DirectSound の使用は推奨されておらず、Windows ストア アプリでは使用できません。 このセクションの情報は、DirectX SDK バージョンの XInput (XInput 1.3) にのみ適用されます。 Windows 8 バージョンの XInput (XInput 1.4) では、XInputGetAudioDeviceIds 経由で取得された Windows Audio Session 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;

プログラミング リファレンス