Comparación de las características XInput y DirectInput

XInput es una API que permite a las aplicaciones recibir entradas del controlador de Xbox para Windows. En este documento se describen las diferencias entre las implementaciones XInput y DirectInput del controlador xbox y cómo puedes admitir dispositivos XInput y dispositivos DirectInput heredados al mismo tiempo.

Nota

No se recomienda el uso de DirectInput heredado y DirectInput no está disponible para Windows aplicaciones de la Tienda.

El nuevo estándar: XInput

XInput ya está disponible para el desarrollo de juegos. Este es el nuevo estándar de entrada para xbox y Windows. Las API están disponibles a través del SDK de DirectX y el controlador está disponible a través de Windows Update.

Hay varias ventajas para usar XInput sobre DirectInput:

  • XInput es más fácil de usar y requiere menos configuración que DirectInput
  • Tanto la programación de Xbox como Windows usarán los mismos conjuntos de API principales, lo que permite la programación traducir la plataforma multiplataforma mucho más fácil
  • Habrá una gran base instalada de controladores xbox
  • Los dispositivos XInput (es decir, los controladores de Xbox) solo tendrán funcionalidad de vibración cuando se usan las API de XInput.
  • Los controladores futuros lanzados para la consola Xbox (es decir, los volantes) también funcionarán en Windows

Uso del mando de Xbox con DirectInput

El controlador de Xbox se enumera correctamente en DirectInput y se puede usar con directInputAPIs. Sin embargo, faltará alguna funcionalidad proporcionada por XInput en la implementación de DirectInput:

  • Los botones de desencadenador izquierdo y derecho actuarán como un solo botón, no de forma independiente.
  • Los efectos de vibración no estarán disponibles
  • La consulta de los dispositivos de auriculares no estará disponible

La combinación de los desencadenadores izquierdo y derecho en DirectInput es por diseño. Los juegos siempre han asumido que los ejes del dispositivo DirectInput se centran cuando no hay ninguna interacción del usuario con el dispositivo. Sin embargo, el mando de Xbox se diseñó para registrar el valor mínimo, no el centro, cuando los desencadenadores no se mantienen. Por lo tanto, los juegos más antiguos asumirían la interacción del usuario.

La solución era combinar los desencadenadores, establecer un desencadenador en una dirección positiva y la otra en una dirección negativa, por lo que ninguna interacción del usuario indica a DirectInput del "control" que está en el centro.

Para probar los valores del desencadenador por separado, debe usar XInput.

XInput y DirectInput en paralelo

Al admitir solo XInput, tu juego no funcionará con dispositivos DirectInput heredados. XInput no reconocerá estos dispositivos.

Si quieres que tu juego admita dispositivos DirectInput heredados, puedes usar DirectInput y XInput en paralelo. Al enumerar los dispositivos DirectInput, todos los dispositivos DirectInput se enumerarán correctamente. Todos los dispositivos XInput se mostrarán como dispositivos XInput y DirectInput, pero no deben controlarse a través de DirectInput. Deberá determinar cuál de los dispositivos DirectInput son dispositivos heredados y cuáles son dispositivos XInput y quitarlos de la enumeración de dispositivos DirectInput.

Para ello, inserte este código en la devolución de llamada de enumeración 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_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 = 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 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_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;    
}

Una versión ligeramente mejorada de este código está en el ejemplo heredado de DirectInput Joystick .

Introducción con XInput

Referencia de programación