Comparación de las características XInput y DirectInput

Importante

Consulta API de GameInput para obtener más información sobre la API de entrada de nueva generación compatible con PC y Xbox a través del kit de desarrollo de juegos de Microsoft (GDK).

Este documento compara las implementaciones XInput y DirectInput de la entrada del controlador y cómo dar soporte tanto a los dispositivos XInput como a los dispositivos DirectInput heredados.

Las aplicaciones de la Tienda Windows no son compatibles con DirectInput.

Información general

XInput permite a las aplicaciones recibir información de los controladores XUSB. Las API están disponibles a través del SDK de DirectX, y el controlador, a través de Windows Update.

Utilizar XInput tiene varias ventajas sobre DirectInput:

  • XInput es más fácil de usar y requiere menos configuración que DirectInput
  • Tanto la programación de Xbox como la de Windows utilizarán los mismos conjuntos de API básicas, lo que facilitará significativamente la traducción de la programación entre plataformas
  • Habrá una gran base de controladores instalada
  • El dispositivo XInput solo tendrá función de vibración cuando utilice las API XInput

Uso de controladores XUSB con DirectInput

Los controladores XUSB se enumeran correctamente en DirectInput, y se pueden utilizar con las DirectInputAPIs. Sin embargo, algunas de las funcionalidades proporcionadas por XInput faltarán en la implementación de DirectInput:

  • Los botones gatillo izquierdo y derecho actuarán como un único botón, no de forma independiente
  • Los efectos de vibración no estarán disponibles
  • La consulta de dispositivos de auriculares no estará disponible

La combinación de los gatillos izquierdo y derecho en DirectInput es por diseño. Los juegos siempre han asumido que los ejes del dispositivo DirectInput están centrados cuando no hay interacción del usuario con el dispositivo. Sin embargo, los controladores más nuevos se diseñaron para registrar el valor mínimo, no el central, cuando no se mantienen los disparadores. Por tanto, los juegos más antiguos presuponen la interacción del usuario.

La solución fue combinar los disparadores, estableciendo un disparador a una dirección positiva y el otro a una dirección negativa, por lo que ninguna interacción del usuario es indicativa para DirectInput de que el "control" está en el centro.

Para comprobar los valores de activación por separado, debe utilizar XInput.

XInput y DirectInput en paralelo

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

Si quieres que tu juego admita dispositivos DirectInput heredados, puedes usar DirectInput y XInput en paralelo. Cuando enumere sus dispositivos DirectInput, todos los dispositivos DirectInput se enumerarán correctamente. Todos los dispositivos XInput aparecerán como dispositivos XInput y DirectInput, pero no deben ser manejados a través de DirectInput. Deberá determinar cuáles de sus dispositivos DirectInput son dispositivos heredados y cuáles son dispositivos XInput, y eliminarlos de la enumeración de dispositivos DirectInput.

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

Una versión ligeramente mejorada de este código se encuentra en el ejemplo anterior de DirectInput Joystick.

Introducción a XInput

Referencia de programación