XInput と DirectInput

XInput とは、Windows 向け Xbox 360 コントローラーからの入力値を、アプリケーションで受け取れるようにする API です。このドキュメントでは、Xbox 360 コントローラーの XInput と DirectInput の実装との違いを説明し、XInput デバイスとレガシー DirectInput デバイスを同時にサポートする方法を説明します。

新しい規格:XInput

XInput をゲーム開発に使用できるようになりました。この新しい入力規格は、Xbox 360 と Windows XP Service Pack 1 以降の両方、および Windows Vista で使用できます。API は DirectX SDK から使用でき、ドライバーは Windows Update と Windowsgaming.com から入手できます。

DirectInput よりも XInput を使用する利点はいくつかあります。

  • XInput は使用しやすく、DirectInput ほどのセットアップは必要ありません。
  • Xbox 360 と Windows プログラミングの両方が同じコア API セットを使用しているため、プラットフォーム間を変換するプログラミングがはるかに簡単です。
  • Xbox 360 コントローラーのインストール ベースは大きくなります。
  • XInput デバイス (すなわち Xbox 360 コントローラー) は、XInput API を使用する場合のみバイブレーション機能を持ちます。
  • Xbox 360 コンソール (すなわちハンドル型コントローラー) 向けに将来リリースされるコントローラーは、Windows でも動作します。

DirectInput での Xbox 360 コントローラーの使用

Xbox 360 コントローラーは、DirectInput で適切に列挙され、DirectInput API で使用できます。ただし、以下のように XInput で実現されている一部の機能は、DirectInput では実装されません。

  • 個々に動作していた左右のトリガー ボタンは、1 つのボタンとして動作するようになります。
  • バイブレーション エフェクトは使用できなくなります。
  • ヘッドセット デバイスへのクエリが使用できなくなります。

DirectInput の左右のトリガーの組み合わせは、意図的なものです。ゲームでは、DirectInput デバイスとユーザーとの対話操作がない場合、このデバイスの軸は常に中央に配置されると想定しています。ただし、トリガーが保持されない場合、Xbox 360 コントローラーは中心ではなく最小値を登録するように設計されていました。したがって、以前のゲームはユーザーの対話操作を想定しています。

この解決策とは、トリガーを組み合わせて、あるトリガーを正の方向に設定し、もう 1 つのトリガーを負の方向に設定するというものでした。それによって、DirectInput に制御が中央にあることをユーザーの対話操作で示すことがなくなります。

トリガー値を別々にテストするには、XInput を使用する必要があります。

XInput と DirectInput を並列に使用

XInput のみをサポートするため、ゲームはレガシー DirectInput デバイスで動作しなくなります。XInput はこれらのデバイスを認識しません。

ゲームでレガシー DirectInput デバイスをサポートしたい場合は、DirectInput と XInput を並列に使用できます。DirectInput デバイスを列挙する場合、すべての DirectInput デバイスは正確に列挙します。XInput デバイスは、XInput デバイスと DirectInput デバイスの両方として表示されますが、DirectInput からは処理しないようにします。Dinput デバイスのいずれがレガシー デバイスか、どれが XInput デバイスかを決定し、これらを DirectInput デバイスの列挙から削除する必要があります。

これを行うには、このコードを DirectInput 列挙コールバックに挿入します。

#include <wbemidl.h>
#include <oleauto.h>
#include <wmsstd.h>

//-----------------------------------------------------------------------------
// Enum each PNP device using WMI and check each device ID to see if it contains 
// "IG_" (ex. "VID_045E&PID_028E&IG_00").  If it does, then it's an XInput device
// Unfortunately this information can not be found by just using DirectInput 
//-----------------------------------------------------------------------------
BOOL IsXInputDevice( const GUID* pGuidProductFromDirectInput )
{
    IWbemLocator*           pIWbemLocator  = NULL;
    IEnumWbemClassObject*   pEnumDevices   = NULL;
    IWbemClassObject*       pDevices[20]   = {0};
    IWbemServices*          pIWbemServices = NULL;
    BSTR                    bstrNamespace  = NULL;
    BSTR                    bstrDeviceID   = NULL;
    BSTR                    bstrClassName  = NULL;
    DWORD                   uReturned      = 0;
    bool                    bIsXinputDevice= false;
    UINT                    iDevice        = 0;
    VARIANT                 var;
    HRESULT                 hr;

    // CoInit if needed
    hr = CoInitialize(NULL);
    bool bCleanupCOM = SUCCEEDED(hr);

    // Create WMI
    hr = CoCreateInstance( __uuidof(WbemLocator),
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           __uuidof(IWbemLocator),
                           (LPVOID*) &pIWbemLocator);
    if( FAILED(hr) || pIWbemLocator == NULL )
        goto LCleanup;

    bstrNamespace = SysAllocString( L"\\\\.\\root\\cimv2" );if( bstrNamespace == NULL ) goto LCleanup;        
    bstrClassName = SysAllocString( L"Win32_PNPEntity" );   if( bstrClassName == NULL ) goto LCleanup;        
    bstrDeviceID  = SysAllocString( L"DeviceID" );          if( bstrDeviceID == NULL )  goto LCleanup;        
    
    // Connect to WMI 
    hr = pIWbemLocator->ConnectServer( bstrNamespace, NULL, NULL, 0L, 
                                       0L, NULL, NULL, &pIWbemServices );
    if( FAILED(hr) || pIWbemServices == NULL )
        goto LCleanup;

    // Switch security level to IMPERSONATE. 
    CoSetProxyBlanket( pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, 
                       RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE );                    

    hr = pIWbemServices->CreateInstanceEnum( bstrClassName, 0, NULL, &pEnumDevices ); 
    if( FAILED(hr) || pEnumDevices == NULL )
        goto LCleanup;

    // Loop over all devices
    for( ;; )
    {
        // Get 20 at a time
        hr = pEnumDevices->Next( 10000, 20, pDevices, &uReturned );
        if( FAILED(hr) )
            goto LCleanup;
        if( uReturned == 0 )
            break;

        for( iDevice=0; iDevice<uReturned; iDevice++ )
        {
            // For each device, get its device ID
            hr = pDevices[iDevice]->Get( bstrDeviceID, 0L, &var, NULL, NULL );
            if( SUCCEEDED( hr ) && var.vt == VT_BSTR && var.bstrVal != NULL )
            {
                // Check if the device ID contains "IG_".  If it does, then it's an XInput device
                    // This information can not be found from DirectInput 
                if( wcsstr( var.bstrVal, L"IG_" ) )
                {
                    // If it does, then get the VID/PID from var.bstrVal
                    DWORD dwPid = 0, dwVid = 0;
                    WCHAR* strVid = wcsstr( var.bstrVal, L"VID_" );
                    if( strVid && swscanf( strVid, L"VID_%4X", &dwVid ) != 1 )
                        dwVid = 0;
                    WCHAR* strPid = wcsstr( var.bstrVal, L"PID_" );
                    if( strPid && swscanf( strPid, L"PID_%4X", &dwPid ) != 1 )
                        dwPid = 0;

                    // Compare the VID/PID to the DInput device
                    DWORD dwVidPid = MAKELONG( dwVid, dwPid );
                    if( dwVidPid == pGuidProductFromDirectInput->Data1 )
                    {
                        bIsXinputDevice = true;
                        goto LCleanup;
                    }
                }
            }   
            SAFE_RELEASE( pDevices[iDevice] );
        }
    }

LCleanup:
    if(bstrNamespace)
        SysFreeString(bstrNamespace);
    if(bstrDeviceID)
        SysFreeString(bstrDeviceID);
    if(bstrClassName)
        SysFreeString(bstrClassName);
    for( iDevice=0; iDevice<20; iDevice++ )
        SAFE_RELEASE( pDevices[iDevice] );
    SAFE_RELEASE( pEnumDevices );
    SAFE_RELEASE( pIWbemLocator );
    SAFE_RELEASE( pIWbemServices );

    if( bCleanupCOM )
        CoUninitialize();

    return bIsXinputDevice;
}


//-----------------------------------------------------------------------------
// Name: EnumJoysticksCallback()
// Desc: Called once for each enumerated joystick. If we find one, create a
//       device interface on it so we can play with it.
//-----------------------------------------------------------------------------
BOOL CALLBACK EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance,
                                     VOID* pContext )
{
    HRESULT hr;

    if( IsXInputDevice( &pdidInstance->guidProduct ) )
        return DIENUM_CONTINUE;

     // Device is verified not XInput, so add it to the list of DInput devices

     return DIENUM_CONTINUE;    
}

関連項目

XInput の基礎知識 | XInput のリファレンス | XInput サンプル | DirectInput