Compartilhar via


Comparação de recursos XInput e DirectInput

Importante

Confira API do GameInput para obter detalhes sobre a API de inserções de última geração com suporte no PC e no Xbox por meio do Kit de Desenvolvimento de Jogos da Microsoft (GDK).

Esse documento compara as implementações de inserções de controlador do XInput e do DirectInput e explica como dar suporte tanto aos dispositivos do XInput quanto aos dispositivos herdados do DirectInput.

Os aplicativos da Windows Store não dão suporte ao DirectInput.

Visão geral

O XInput habilita os aplicativos a receber entradas de dados dos controladores XUSB. As APIs estão disponíveis por meio do SDK do DirectX e o driver está disponível por meio do Windows Update.

Existem várias vantagens em usar o XInput em vez do DirectInput:

  • O XInput é mais fácil de usar e requer menos configuração do que o DirectInput
  • A programação do Xbox e do Windows usará os mesmos conjuntos de APIs básicas, permitindo que a programação faça conversões de uma plataforma para outra com muito mais facilidade
  • Haverá uma grande base instalada de controladores
  • O dispositivo do XInput terá uma funcionalidade de vibração somente quando usar APIs do XInput

Como usar controladores XUSB com o DirectInput

Os controladores XUSB são enumerados corretamente no DirectInput e podem ser usados com DirectInputAPIs. No entanto, algumas funcionalidades fornecidas pelo XInput ficarão faltando na implementação do DirectInput:

  • Os botões de disparo esquerdo e direito irão atuar como um único botão, não de forma independente
  • Os efeitos de vibração não estarão disponíveis
  • A consulta para dispositivos de headset não estará disponível

A combinação dos disparos esquerdo e direito no DirectInput existe por design. Os jogos sempre presumiram que os eixos dos dispositivos do DirectInput ficam centralizados quando não há nenhuma interação do usuário com o dispositivo. No entanto, os controladores mais novos foram projetados para registrar o valor mínimo, não o centro, quando os disparos não estiverem sendo retidos. Os jogos mais antigos, portanto, pressupõem a interação do usuário.

A solução foi combinar os disparos, definindo um disparo para uma direção positiva e o outro para uma direção negativa, de modo que nenhuma interação do usuário seja indicativa de que o DirectInput do "controle" está no centro.

Para testar os valores de disparo separadamente, você precisa usar o XInput.

XInput e DirectInput Lado a Lado

Se der suporte somente ao XInput, seu jogo não irá funcionar com dispositivos herdados do DirectInput. O XInput não irá reconhecer esses dispositivos.

Se quiser que seu jogo dê suporte a dispositivos herdados do DirectInput, você poderá usar o DirectInput e o XInput lado a lado. Quando você enumerar seus dispositivos do DirectInput, todos os dispositivos do DirectInput serão enumerados corretamente. Todos os dispositivos do XInput irão aparecer tanto como dispositivos do XInput quanto do DirectInput, mas não devem ser manuseados por meio do DirectInput. Você precisará determinar quais dos seus dispositivos do DirectInput são dispositivos herdados e quais são dispositivos do XInput e removê-los da enumeração de dispositivos do DirectInput.

Para fazer isso, insira esse código no retorno de chamadas de enumeração do seu 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;    
}

Uma versão ligeiramente aprimorada desse código está na amostra herdada do Joystick do DirectInput.

Como começar a usar o XInput

Referência da Programação