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