Udostępnij przez


Wprowadzenie do aplikacji XInput w aplikacjach systemu Windows

XInput umożliwia aplikacjom systemu Windows przetwarzanie interakcji kontrolera (w tym efekty wibracji kontrolera oraz dane głosu wyjściowe i wejściowe).

Ten temat zawiera krótkie omówienie możliwości środowiska XInput i sposobu jej konfigurowania w aplikacji. Obejmuje ona następujące elementy:

Wprowadzenie do XInput

Aplikacje mogą używać interfejsu API XInput do komunikowania się z kontrolerami gier, gdy są podłączone do komputera z systemem Windows (maksymalnie cztery unikatowe kontrolery można podłączyć naraz).

Korzystając z tego interfejsu API, można zapytać każdy zgodny połączony kontroler o jego stan, a efekty drgań można ustawić. Kontrolery, które mają dołączony zestaw słuchawkowy, mogą być również odpytywane pod kątem urządzeń wejściowych i wyjściowych dźwięku, które mogą być używane z zestawem słuchawkowym do przetwarzania głosu.

Układ kontrolera

Zgodne kontrolery mają dwa analogowe kije kierunkowe, z których każdy ma przycisk cyfrowy, dwa wyzwalacze analogowe, cyfrową podkładkę kierunkową z czterema kierunkami i osiem przycisków cyfrowych. Stany każdego z tych danych wejściowych są zwracane w strukturze XINPUT_GAMEPAD , gdy wywoływana jest funkcja XInputGetState .

Kontroler ma również dwa silniki wibracyjne, aby dostarczyć użytkownikowi efekty sprzężenia zwrotnego siłowego. Prędkości tych silników są określone w strukturze XINPUT_VIBRATION , która jest przekazywana do funkcji XInputSetState w celu ustawienia efektów drgań.

Opcjonalnie zestaw słuchawkowy można podłączyć do kontrolera. Zestaw słuchawkowy ma mikrofon do wprowadzania głosu i słuchawki do wyjścia dźwięku. Możesz wywołać funkcję XInputGetAudioDeviceIds lub starszej wersji XInputGetDSoundAudioDeviceGuids w celu uzyskania identyfikatorów urządzeń odpowiadających urządzeniom dla mikrofonu i słuchawek. Następnie możesz użyć podstawowych interfejsów API audio do odbierania danych głosowych i wysyłania danych wyjściowych dźwięku.

Korzystanie z funkcji XInput

Używanie funkcji XInput jest tak proste, jak wywoływanie funkcji XInput zgodnie z potrzebami. Za pomocą funkcji XInput można pobrać stan kontrolera, uzyskać identyfikatory audio zestawu słuchawkowego i ustawić efekty rumble kontrolera.

Wiele kontrolerów

Interfejs API XInput obsługuje maksymalnie cztery kontrolery połączone w dowolnym momencie. Wszystkie funkcje XInput wymagają parametru dwUserIndex przekazanego w celu zidentyfikowania ustawionego lub odpytowanego kontrolera. Ten identyfikator będzie mieścił się w zakresie od 0 do 3 i jest ustawiany automatycznie przez XInput. Liczba odpowiada portowi, do którego jest podłączony kontroler i nie można go modyfikować.

Każdy kontroler wyświetla identyfikator używany przez oświetlenie ćwiartki na "pierścieniu światła" w środku kontrolera. Wartość dwUserIndex 0 odpowiada ćwiartce w lewym górnym rogu; numerowanie jest kontynuowane wokół pierścienia w kolejności zgodnie z ruchem wskazówek zegara.

Aplikacje powinny obsługiwać wiele kontrolerów.

Uzyskiwanie stanu kontrolera

Przez cały czas trwania aplikacji pobieranie stanu z kontrolera prawdopodobnie będzie odbywać się najczęściej. Od ramki do ramki w aplikacji gry, stan powinien być pobrany, a informacje z kontrolera zaktualizowane, aby odzwierciedlić zmiany.

Aby pobrać stan, użyj funkcji XInputGetState :

DWORD dwResult;    
for (DWORD i=0; i< XUSER_MAX_COUNT; i++ )
{
    XINPUT_STATE state;
    ZeroMemory( &state, sizeof(XINPUT_STATE) );

    // Simply get the state of the controller from XInput.
    dwResult = XInputGetState( i, &state );

    if( dwResult == ERROR_SUCCESS )
    {
        // Controller is connected
    }
    else
    {
        // Controller is not connected
    }
}

Należy pamiętać, że wartość zwracana klasy XInputGetState może służyć do określenia, czy kontroler jest połączony. Aplikacje powinny definiować strukturę do przechowywania informacji o wewnętrznym kontrolerze; te informacje powinny być porównywane z wynikami XInputGetState, aby określić, jakie zmiany, takie jak naciśnięcia przycisków lub zmiany delty kontrolerów analogowych, nastąpiły w danym okresie rozliczeniowym. W powyższym przykładzie g_Controllers reprezentuje taką strukturę.

Po pobraniu stanu w strukturze XINPUT_STATE można go sprawdzić pod kątem zmian i uzyskać określone informacje o stanie kontrolera.

Element członkowski dwPacketNumber struktury XINPUT_STATE może służyć do sprawdzania, czy stan kontrolera uległ zmianie od ostatniego wywołania funkcji XInputGetState. Jeśli parametr dwPacketNumber nie zmienia się między dwoma sekwencyjnymi wywołaniami funkcji XInputGetState, nie nastąpiła żadna zmiana stanu. Jeśli różni się, aplikacja powinna sprawdzić element członkowski gamepad struktury XINPUT_STATE , aby uzyskać bardziej szczegółowe informacje o stanie.

Ze względu na wydajność nie należy wywoływać elementu XInputGetState dla miejsca użytkownika "pustego" dla każdej ramki. Zalecamy rozkładanie kontroli nowych kontrolerów co kilka sekund.

Strefa nieaktywna

Aby użytkownicy mieli spójne doświadczenie w rozgrywkach, gra musi poprawnie zaimplementować strefę martwą. Strefa martwa to wartości "ruchu" zgłaszane przez kontroler nawet wtedy, gdy drążki analogowe są nietknięte i wyśrodkowane. Istnieje również strefa martwa dla 2 wyzwalaczy analogowych.

Uwaga / Notatka

Gry korzystające z XInput, które w ogóle nie filtrują martwej strefy, będą doświadczać słabej rozgrywki. Należy pamiętać, że niektóre kontrolery są bardziej wrażliwe niż inne, dlatego strefa martwa może się różnić w zależności od jednostki do jednostki. Zaleca się przetestowanie gier za pomocą kilku różnych kontrolerów w różnych systemach.

Aplikacje powinny używać "martwych stref" na wejściach analogowych (wyzwalaczy, kijach), aby wskazać, kiedy ruch został wystarczająco wykonany na kiju lub wyzwalaczu, aby być uważany za prawidłowy.

Aplikacja powinna sprawdzać strefy nieaktywne i odpowiednio reagować, jak w tym przykładzie:

XINPUT_STATE state = g_Controllers[i].state;

float LX = state.Gamepad.sThumbLX;
float LY = state.Gamepad.sThumbLY;

//determine how far the controller is pushed
float magnitude = sqrt(LX*LX + LY*LY);

//determine the direction the controller is pushed
float normalizedLX = LX / magnitude;
float normalizedLY = LY / magnitude;

float normalizedMagnitude = 0;

//check if the controller is outside a circular dead zone
if (magnitude > INPUT_DEADZONE)
{
    //clip the magnitude at its expected maximum value
    if (magnitude > 32767) magnitude = 32767;

    //adjust magnitude relative to the end of the dead zone
    magnitude -= INPUT_DEADZONE;

    //optionally normalize the magnitude with respect to its expected range
    //giving a magnitude value of 0.0 to 1.0
    normalizedMagnitude = magnitude / (32767 - INPUT_DEADZONE);
}
else //if the controller is in the deadzone zero out the magnitude
{
    magnitude = 0.0;
    normalizedMagnitude = 0.0;
}

//repeat for right thumb stick

W tym przykładzie obliczany jest wektor kierunkowy kontrolera oraz jak daleko wzdłuż tego wektora został przesunięty kontroler. Umożliwia to wprowadzenie okrągłej strefy martwej poprzez sprawdzenie, czy wartość wielkości kontrolera jest większa niż wartość strefy martwej. Ponadto kod normalizuje wielkość kontrolera, którą następnie można pomnożyć przez czynnik specyficzny dla gry, aby przekonwertować pozycję kontrolera na jednostki istotne dla gry.

Należy pamiętać, że możesz zdefiniować własne martwe obszary dla drążków i przycisków (w zakresie od 0 do 65534) lub można użyć podanych martwych stref zdefiniowanych jako XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE i XINPUT_GAMEPAD_TRIGGER_THRESHOLD w XInput.h:

#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE  7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD    30

Po wymusieniu martwej strefy może okazać się przydatne skalowanie wynikowego zakresu [0.0..1.0] zmiennoprzecinkowego (jak w powyższym przykładzie) i opcjonalnie zastosowanie przekształcenia nieliniowego.

Na przykład w przypadku gier wyścigowych warto podnieść wynik do sześcianu, aby zapewnić lepsze wrażenie z jazdy samochodami za pomocą gamepada, ponieważ podniesienie wyniku do sześcianu daje większą precyzję w niższych zakresach, co jest pożądane, ponieważ gracze zazwyczaj stosują miękką siłę, aby uzyskać subtelne ruchy lub używają dużej siły na maksa w jednym kierunku, aby uzyskać szybką reakcję.

Ustawianie efektów drgań

Oprócz uzyskania stanu kontrolera można również wysłać dane drgań do kontrolera, aby zmienić opinię dostarczoną użytkownikowi kontrolera. Kontroler zawiera dwa silniki rumble, które mogą być niezależnie kontrolowane przez przekazywanie wartości do funkcji XInputSetState .

Szybkość każdego silnika można określić przy użyciu wartości WORD w strukturze XINPUT_VIBRATION , która jest przekazywana do funkcji XInputSetState w następujący sposób:

XINPUT_VIBRATION vibration;
ZeroMemory( &vibration, sizeof(XINPUT_VIBRATION) );
vibration.wLeftMotorSpeed = 32000; // use any value between 0-65535 here
vibration.wRightMotorSpeed = 16000; // use any value between 0-65535 here
XInputSetState( i, &vibration );

Należy pamiętać, że prawy silnik jest silnikiem o wysokiej częstotliwości, lewy silnik jest silnikiem o niskiej częstotliwości. Nie zawsze muszą być ustawione na ten sam poziom, ponieważ zapewniają różne efekty.

Pobieranie identyfikatorów urządzeń audio

Zestaw słuchawkowy dla kontrolera ma następujące funkcje:

  • Nagrywanie dźwięku przy użyciu mikrofonu
  • Odtwarzanie dźwięku za pomocą słuchawek

Użyj tego kodu, aby uzyskać identyfikatory urządzeń dla zestawu nagłownego:

WCHAR renderId[ 256 ] = {0};
WCHAR captureId[ 256 ] = {0};
UINT rcount = 256;
UINT ccount = 256;

XInputGetAudioDeviceIds( i, renderId, &rcount, captureId, &ccount );

Po uzyskaniu identyfikatorów urządzeń można utworzyć odpowiednie interfejsy. Jeśli na przykład używasz wersji XAudio 2.8, użyj tego kodu, aby utworzyć głos masteringowy dla tego urządzenia.

IXAudio2* pXAudio2 = NULL;
HRESULT hr;
if ( FAILED(hr = XAudio2Create( &pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR ) ) )
    return hr;

IXAudio2MasteringVoice* pMasterVoice = NULL;
if ( FAILED(hr = pXAudio2->CreateMasteringVoice( &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE, 0, renderId, NULL, AudioCategory_Communications ) ) )
    return hr;

Aby uzyskać informacje o sposobie używania identyfikatora urządzenia captureId, zobacz Przechwytywanie strumienia.

Uzyskiwanie identyfikatorów GUID DirectSound (tylko starsze wersje SDK DirectX)

Zestaw słuchawkowy, który można podłączyć do kontrolera, ma dwie funkcje: może nagrywać dźwięk przy użyciu mikrofonu i może odtwarzać dźwięk z powrotem przy użyciu słuchawek. W API XInput te funkcje są realizowane przez DirectSound, przez interfejsy IDirectSound8 i IDirectSoundCapture8.

Aby skojarzyć mikrofon zestawu słuchawkowego i słuchawki z odpowiednimi interfejsami DirectSound, należy uzyskać identyfikatory DirectSoundGUID na potrzeby przechwytywania i renderowania urządzeń przez wywołanie interfejsu XInputGetDSoundAudioDeviceGuids.

Uwaga / Notatka

Korzystanie ze starszej wersji directSound nie jest zalecane i nie jest dostępne w aplikacjach ze Sklepu Windows. Informacje w tej sekcji dotyczą tylko wersji zestawu SDK DirectX XInput (XInput 1.3). System Windows 8 w wersji XInput (XInput 1.4) używa wyłącznie identyfikatorów urządzeń Windows Audio Session API (WASAPI), które są uzyskiwane za pośrednictwem XInputGetAudioDeviceIds.

XInputGetDSoundAudioDeviceGuids( i, &dsRenderGuid, &dsCaptureGuid );

Po uzyskaniu identyfikatorów GUID można utworzyć odpowiednie interfejsy, wywołując funkcje DirectSoundCreate8 i DirectSoundCaptureCreate8 w ten sposób:

// Create IDirectSound8 using the controller's render device
if( FAILED( hr = DirectSoundCreate8( &dsRenderGuid, &pDS, NULL ) ) )
   return hr;

// Set coop level to DSSCL_PRIORITY
if( FAILED( hr = pDS->SetCooperativeLevel( hWnd, DSSCL_NORMAL ) ) )
   return hr;

// Create IDirectSoundCapture using the controller's capture device
if( FAILED( hr = DirectSoundCaptureCreate8( &dsCaptureGuid, &pDSCapture, NULL ) ) )
   return hr;

dokumentacja programowania