다음을 통해 공유


XInput 및 DirectInput 기능 비교

Important

Microsoft GDK(게임 개발 키트)를 통해 PC 및 Xbox에서 지원되는 차세대 입력 API에 대한 자세한 내용은 GameInput API를 참조하세요.

이 문서에서는 컨트롤러 입력의 XInput 및 DirectInput 구현과 XInput 디바이스와 레거시 DirectInput 디바이스를 모두 지원하는 방법을 비교합니다.

Windows 스토어 앱은 DirectInput을 지원하지 않습니다.

개요

XInput을 사용하면 애플리케이션이 XUSB 컨트롤러에서 입력을 받을 수 있습니다. API는 DirectX SDK를 통해 사용할 수 있으며 드라이버는 Windows 업데이트 통해 사용할 수 있습니다.

DirectInput보다 XInput을 사용하는 경우 다음과 같은 몇 가지 이점이 있습니다.

  • XInput은 사용하기 쉽고 DirectInput보다 설치가 덜 필요합니다.
  • Xbox 및 Windows 프로그래밍 모두 동일한 핵심 API 집합을 사용하여 프로그래밍이 플랫폼 간을 훨씬 쉽게 변환할 수 있도록 합니다.
  • 설치된 대규모 컨트롤러 기반이 있습니다.
  • XInput 디바이스는 XInput API를 사용하는 경우에만 진동 기능을 갖습니다.

DirectInput에서 XUSB 컨트롤러 사용

XUSB 컨트롤러는 DirectInput에서 제대로 열거되며 DirectInputAPIs와 함께 사용할 수 있습니다. 그러나 XInput에서 제공하는 일부 기능은 DirectInput 구현에서 누락됩니다.

  • 왼쪽 및 오른쪽 트리거 단추는 독립적으로 작동하지 않고 단일 단추로 작동합니다.
  • 진동 효과를 사용할 수 없습니다.
  • 헤드셋 디바이스에 대한 쿼리를 사용할 수 없습니다.

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 조이스틱 샘플에 있습니다.

XInput 시작

프로그래밍 참조