XInput と DirectInput の機能の比較
重要
Microsoft Game Development Kit (GDK) を介して PC および Xbox 上でサポートされる次世代の入力 API の詳細については、GameInput API に関するページを参照してください。
このドキュメントでは、コントローラー入力の XInput および DirectInput 実装を比較し、XInput デバイスとレガシ DirectInput デバイスの両方をサポートする方法について説明します。
Windows ストア アプリでは DirectInput をサポートしていません。
概要
XInput を使用すると、XUSB コントローラーからの入力をアプリケーションで受信できます。 この API は DirectX SDK を通じて取得でき、ドライバーは Windows Update を通じて入手できます。
XInput を使用することには、DirectInput の場合と比べて、次のようないくつかの利点があります。
- XInput は、DirectInput よりも使いやすく、セットアップの手間がが少なくて済む
- Xbox および Windows の両方のプログラミングで同じコア API セットが使用されるため、はるかに簡単にプログラミングをクロスプラットフォームに変換できる
- コントローラーのインストール ベースが大きくなる
- XInput デバイスの振動機能は、XInput API を使用した場合にのみ利用できる
DirectInput での XUSB コントローラーの使用
XUSB コントローラーを DirectInput 上に適切に列挙すると、DirectInputAPI で使用できます。 ただし、DirectInput 実装では次のように、XInput によって提供される一部の機能には対応していません。
- 左右のトリガー ボタンは、独立したボタンではなく、単一のボタンとして機能する
- 振動効果は利用できない
- ヘッドセット デバイスに対してクエリを実行できない
DirectInput での左右のトリガーの組み合わせは、意図的なものです。 ゲームでは、DirectInput デバイスとユーザーとの対話式操作がない場合、このデバイスの軸は常に中央に配置されると想定してきました。 ただし、トリガーが保持されない場合、新しいコントローラーは中心ではなく最小値を登録するように設計されました。 したがって、以前のゲームの場合はユーザーの対話式操作を想定しています。
この解決策は、トリガーを組み合わせて、一方のトリガーを正の方向に設定し、もう一方のトリガーを負の方向に設定するというものでした。それによって、DirectInput に "コントロール" が中央にあることをユーザーの対話式操作で示すことがなくなります。
トリガー値を個別にテストするには、XInput を使用する必要があります。
XInput と DirectInput を並べて表示する
XInput のみをサポートすると、ゲームはレガシ DirectInput デバイスで動作しなくなります。 これらのデバイスは、XInput によって認識されなくなるのです。
ゲームでレガシ DirectInput デバイスをサポートしたい場合は、DirectInput と XInput を並べて表示できます。 使用する DirectInput デバイスを列挙すると、すべての DirectInput デバイスが正しく列挙されます。 すべての XInput デバイスは、XInput デバイスと DirectInput デバイスの両方として表示されますが、DirectInput では処理しないでください。 どの DirectInput デバイスがレガシ デバイスで、どのデバイスが XInput デバイスであるかを判断し、DirectInput デバイスの列挙から削除する必要があります。
これを行うには、DirectInput 列挙コールバックに次のコードを挿入します。
#include <wbemidl.h>
#include <oleauto.h>
#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p) { if (p) { (p)->Release(); (p) = nullptr; } }
#endif
//-----------------------------------------------------------------------------
// Enum each PNP device using WMI and check each device ID to see if it contains
// "IG_" (ex. "VID_0000&PID_0000&IG_00"). If it does, then it's an XInput device
// Unfortunately this information cannot be found by just using DirectInput
//-----------------------------------------------------------------------------
BOOL IsXInputDevice( const GUID* pGuidProductFromDirectInput )
{
IWbemLocator* pIWbemLocator = nullptr;
IEnumWbemClassObject* pEnumDevices = nullptr;
IWbemClassObject* pDevices[20] = {};
IWbemServices* pIWbemServices = nullptr;
BSTR bstrNamespace = nullptr;
BSTR bstrDeviceID = nullptr;
BSTR bstrClassName = nullptr;
bool bIsXinputDevice = false;
// CoInit if needed
HRESULT hr = CoInitialize(nullptr);
bool bCleanupCOM = SUCCEEDED(hr);
// So we can call VariantClear() later, even if we never had a successful IWbemClassObject::Get().
VARIANT var = {};
VariantInit(&var);
// Create WMI
hr = CoCreateInstance(__uuidof(WbemLocator),
nullptr,
CLSCTX_INPROC_SERVER,
__uuidof(IWbemLocator),
(LPVOID*)&pIWbemLocator);
if (FAILED(hr) || pIWbemLocator == nullptr)
goto LCleanup;
bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (bstrNamespace == nullptr) goto LCleanup;
bstrClassName = SysAllocString(L"Win32_PNPEntity"); if (bstrClassName == nullptr) goto LCleanup;
bstrDeviceID = SysAllocString(L"DeviceID"); if (bstrDeviceID == nullptr) goto LCleanup;
// Connect to WMI
hr = pIWbemLocator->ConnectServer(bstrNamespace, nullptr, nullptr, 0L,
0L, nullptr, nullptr, &pIWbemServices);
if (FAILED(hr) || pIWbemServices == nullptr)
goto LCleanup;
// Switch security level to IMPERSONATE.
hr = CoSetProxyBlanket(pIWbemServices,
RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr, EOAC_NONE);
if ( FAILED(hr) )
goto LCleanup;
hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, nullptr, &pEnumDevices);
if (FAILED(hr) || pEnumDevices == nullptr)
goto LCleanup;
// Loop over all devices
for (;;)
{
ULONG uReturned = 0;
hr = pEnumDevices->Next(10000, _countof(pDevices), pDevices, &uReturned);
if (FAILED(hr))
goto LCleanup;
if (uReturned == 0)
break;
for (size_t iDevice = 0; iDevice < uReturned; ++iDevice)
{
// For each device, get its device ID
hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, nullptr, nullptr);
if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != nullptr)
{
// Check if the device ID contains "IG_". If it does, then it's an XInput device
// This information cannot 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_s(strVid, L"VID_%4X", &dwVid) != 1)
dwVid = 0;
WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
if (strPid && swscanf_s(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;
}
}
}
VariantClear(&var);
SAFE_RELEASE(pDevices[iDevice]);
}
}
LCleanup:
VariantClear(&var);
if(bstrNamespace)
SysFreeString(bstrNamespace);
if(bstrDeviceID)
SysFreeString(bstrDeviceID);
if(bstrClassName)
SysFreeString(bstrClassName);
for (size_t iDevice = 0; iDevice < _countof(pDevices); ++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 )
{
if( IsXInputDevice( &pdidInstance->guidProduct ) )
return DIENUM_CONTINUE;
// Device is verified not XInput, so add it to the list of DInput devices
return DIENUM_CONTINUE;
}
このコードを少し改善したバージョンが、レガシ DirectInput ジョイスティック サンプルにあります。