Vergleich von XInput- und DirectInput-Features
Wichtig
Details zur Eingabe-API der nächsten Generation, die auf PC und Xbox über das Microsoft Game Development Kit (GDK) unterstützt wird, finden Sie unter GameInput-API.
In diesem Dokument werden XInput- und DirectInput-Implementierungen von Controllereingaben verglichen, und es wird gezeigt, wie sowohl XInput-Geräte als auch Legacy-DirectInput-Geräte unterstützt werden können.
Windows Store-Apps unterstützen DirectInput nicht.
Überblick
XInput ermöglicht Anwendungen, Eingaben von den XUSB-Controllern zu empfangen. Die APIs sind über das DirectX SDK verfügbar, und der Treiber ist über Windows Update verfügbar.
Die Verwendung von XInput anstattDirectInput bietet mehrere Vorteile:
- XInput ist einfacher zu verwenden und erfordert ein weniger aufwändiges Setup als DirectInput
- Sowohl die Xbox- als auch die Windows-Programmierung werden dieselben Sätze von Kern-APIs verwenden, wodurch die plattformübergreifende Programmierung erheblich erleichtert wird.
- Es wird eine große Installationsbasis von Controllern geben.
- Ein XInput-Gerät verfügt nur dann über Vibrationsfunktionen, wenn XInput-APIs verwendet werden.
Verwenden von XUSB-Controllern mit DirectInput
Die XUSB-Controller werden in DirectInput ordnungsgemäß enumeriert und können mit den DirectInput-APIs verwendet werden. Einige der von XInput bereitgestellten Funktionen fehlen jedoch in der DirectInput-Implementierung:
- Die Tasten für den linken und den rechten Trigger fungieren als eine Taste, nicht unabhängig voneinander.
- Die Vibrationseffekte sind nicht verfügbar.
- Abfragen nach Headset-Geräten sind nicht verfügbar.
Die Kombination von linkem und rechtem Trigger ist in DirectInput beabsichtigt. In Spielen wird immer davon ausgegangen, dass DirectInput-Geräteachsen zentriert sind, wenn keine Benutzerinteraktion mit dem Gerät stattfindet. Die neueren Controller wurden jedoch so konzipiert, dass sie den Mindestwert und nicht den Mittelwert registrieren, wenn die Trigger nicht gehalten werden. Ältere Spiele würden daher von einer Benutzerinteraktion ausgehen.
Die Lösung bestand darin, die Trigger zu kombinieren, indem ein Trigger auf eine positive Richtung und der anderen auf eine negative Richtung festgelegt wird, sodass DirectInput keine Benutzerinteraktion anzeigt, wenn das „Steuerelement“ in der Mitte ist.
Um die Triggerwerte separat zu testen, müssen Sie XInput verwenden.
XInput und DirectInput nebeneinander
Wenn nur XInput unterstützt wird, funktioniert Ihr Spiel nicht mit älteren DirectInput-Geräten. XInput erkennt diese Geräte nicht.
Wenn Ihr Spiel ältere DirectInput-Geräte unterstützen soll, können Sie DirectInput und XInput nebeneinander verwenden. Beim Enumerieren Ihrer DirectInput-Geräte werden alle DirectInput-Geräte ordnungsgemäß enumeriert. Alle XInput-Geräte werden sowohl als XInput- als auch als DirectInput-Geräte angezeigt, sollten jedoch nicht über DirectInput verarbeitet werden. Sie müssen feststellen, welche Ihrer DirectInput-Geräte Legacygeräte und welche XInput-Geräte sind und diese aus der Enumeration von DirectInput-Geräten entfernen.
Fügen Sie dazu diesen Code in ihren DirectInput-Enumerationsrückruf ein:
#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;
}
Eine leicht verbesserte Version dieses Codes befindet sich im Legacybeispiel Joystick für DirectInput.