Erste Schritte mit XInput in Windows-Anwendungen

XInput ermöglicht Windows-Anwendungen die Verarbeitung von Controllerinteraktionen (einschließlich Controller-Rumble-Effekten und Spracheingabe und -ausgabe).

Dieses Thema enthält eine kurze Übersicht über die Funktionen von XInput und das Einrichten in einer Anwendung. Dazu gehört Folgendes:

Einführung in XInput

Anwendungen können die XInput-API verwenden, um mit Gaming-Controllern zu kommunizieren, wenn sie an einen Windows-PC angeschlossen sind (bis zu vier eindeutige Controller können gleichzeitig angeschlossen werden).

Mit dieser API kann jeder kompatible verbundene Controller nach seinem Zustand abgefragt werden, und Vibrationseffekte können festgelegt werden. Controller mit angeschlossenem Headset können auch nach Soundeingabe- und Ausgabegeräten abgefragt werden, die mit dem Headset für die Sprachverarbeitung verwendet werden können.

Controllerlayout

Kompatible Controller verfügen über zwei analoge Richtungssticks, jeweils mit einem digitalen Taster, zwei analogen Triggern, einem digitalen Steuerkreuz mit vier Richtungen und acht digitalen Tasten. Die Zustände dieser Eingaben werden in der Struktur XINPUT_GAMEPAD zurückgegeben, wenn die Funktion XInputGetState aufgerufen wird.

Der Controller verfügt auch über zwei Vibrationsmotoren, um dem Benutzer Kraftrückmeldungseffekte zu liefern. Die Geschwindigkeiten dieser Motoren werden in der Struktur XINPUT_VIBRATION angegeben, die an die Funktion XInputSetState übergeben wird, um Vibrationseffekte festzulegen.

Optional kann ein Headset an den Controller angeschlossen werden. Das Headset verfügt über ein Mikrofon für die Spracheingabe und einen Kopfhörer für die Soundausgabe. Sie können die XInputGetAudioDeviceIds oder die Legacy-Funktion XInputGetDSoundAudioDeviceGuids aufrufen, um die Gerätebezeichner abzurufen, die den Geräten für das Mikrofon und den Kopfhörern entsprechen. Anschließend können Sie die Core Audio-APIs verwenden, um Spracheingaben zu empfangen und Die Soundausgabe zu senden.

Verwenden von XInput

Die Verwendung von XInput ist so einfach wie das Aufrufen der XInput-Funktionen nach Bedarf. Mit den XInput-Funktionen können Sie den Controllerzustand abrufen, Headset-Audio-IDs abrufen und Controller-Rumbleeffekte festlegen.

Mehrere Controller

Die XInput-API unterstützt jederzeit bis zu vier Controller, die verbunden sind. Die XInput-Funktionen erfordern alle einen dwUserIndex-Parameter, der übergeben wird, um den festzulegenden oder abgefragten Controller zu identifizieren. Diese ID befindet sich im Bereich von 0-3 und wird automatisch von XInput festgelegt. Die Nummer entspricht dem Port, an den der Controller angeschlossen ist, und kann nicht geändert werden.

Jeder Controller zeigt an, welche ID er verwendet, indem er einen Quadranten auf dem „Lichtring“ in der Mitte des Controllers beleuchtet. Ein dwUserIndex-Wert von 0 entspricht dem oberen linken Quadranten; die Nummerierung wird um den Ring im Uhrzeigersinn fortgesetzt.

Anwendungen sollten mehrere Controller unterstützen.

Abrufen des Controllerstatus

Während der gesamten Dauer einer Anwendung wird der Status von einem Controller wahrscheinlich am häufigsten abgerufen. Von Frame zu Frame in einer Spielanwendung sollte der Zustand abgerufen und Spielinformationen aktualisiert werden, um die Änderungen des Controllers widerzuspiegeln.

Verwenden Sie zum Abrufen des Zustands die XInputGetState-Funktion:

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
    }
}

Beachten Sie, dass der Rückgabewert von XInputGetState verwendet werden kann, um festzustellen, ob der Controller angeschlossen ist. Anwendungen sollten eine Struktur definieren, die interne Controllerinformationen enthält; Diese Informationen sollten mit den Ergebnissen von XInputGetState verglichen werden, um zu bestimmen, welche Änderungen, z. B. Tastendrücke oder analoge Controllerdelta, diesen Frame vorgenommen wurden. Im obigen Beispiel stellt g_Controllers eine solche Struktur dar.

Nachdem der Zustand in einer XINPUT_STATE-Struktur abgerufen wurde, können Sie ihn auf Änderungen überprüfen und bestimmte Informationen zum Controllerstatus abrufen.

Das Element dwPacketNumber der Struktur XINPUT_STATE kann verwendet werden, um zu überprüfen, ob sich der Zustand des Controllers seit dem letzten Aufruf von XInputGetState geändert hat. Wenn sich dwPacketNumber nicht zwischen zwei sequenziellen Aufrufen von XInputGetState ändert, wurde kein Zustand geändert. Wenn sich dies unterscheidet, sollte die Anwendung das Element Gamepad der Struktur XINPUT_STATE überprüfen, um detailliertere Zustandsinformationen zu erhalten.

Rufen Sie aus Leistungsgründen XInputGetState nicht für einen leeren Benutzerplatz für jeden Frame auf. Es wird empfohlen, alle paar Sekunden nach neuen Controllern zu suchen.

Inaktive Zone

Damit die Nutzer ein konsistentes Spielerlebnis haben, muss Ihr Spiel die tote Zone korrekt implementieren. Die inaktive Zone sind „Bewegungs“-Werte, die vom Controller gemeldet werden, auch wenn die analogen Daumensticks nicht berührt werden und zentriert sind. Es gibt auch eine inaktive Zone für die 2 analogen Trigger.

Hinweis

Bei Spielen, die XInput verwenden und bei denen die inaktive Zone überhaupt nicht gefiltert wird, kommt es zu einem schlechten Gameplay. Bitte beachten Sie, dass einige Controller empfindlicher sind als andere, sodass die inaktive Zone von Einheit zu Einheit variieren kann. Es wird empfohlen, dass Sie Ihre Spiele mit mehreren verschiedenen Controllern auf verschiedenen Systemen testen.

Anwendungen sollten „inaktive Zonen“ für analoge Eingaben (Trigger, Sticks) verwenden, um anzugeben, wann eine Bewegung auf dem Stick ausreichend erfolgt oder als gültig angesehen wird.

Ihre Anwendung sollte nach inaktiven Zonen suchen und entsprechend reagieren, wie im folgenden Beispiel gezeigt:

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

In diesem Beispiel wird der Richtungsvektor des Controllers berechnet und wie weit der Vektor des Controllers verschoben wurde. Dies ermöglicht die Durchsetzung einer kreisförmigen inaktiven Zone, indem einfach überprüft wird, ob die Größe des Reglers größer als der Wert der inaktiven Zone ist. Außerdem normalisiert der Code die Größe des Controllers, die dann mit einem spielspezifischen Faktor multipliziert werden kann, um die Position des Controllers in für das Spiel relevante Einheiten umzurechnen.

Beachten Sie, dass Sie Ihre eigenen inaktiven Zonen für die Sticks und Trigger definieren können (von 0-65534), oder Sie können die bereitgestellten in XInput.h als XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE und XINPUT_GAMEPAD_TRIGGER_THRESHOLD definierten inaktiven Zonen verwenden:

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

Nachdem die inaktive Zone erzwungen wurde, kann es hilfreich sein, den resultierenden Bereich [0.0..1.0] des Gleitkommas (wie im obigen Beispiel) zu skalieren und optional eine nicht lineare Transformation anzuwenden.

Bei Fahrspielen kann es zum Beispiel hilfreich sein, das Ergebnis zu würfeln, um ein besseres Gefühl für das Fahren der Autos mit einem Gamepad zu bekommen, da das Würfeln des Ergebnisses zu mehr Präzision in den unteren Bereichen führt, was wünschenswert ist, da Spieler normalerweise entweder sanfte Kraft anwenden, um subtile Bewegungen zu erhalten, oder harte Kraft in eine Richtung anwenden, um eine schnelle Reaktion zu erhalten.

Festlegen von Vibrationseffekten

Zusätzlich zum Abrufen des Zustands des Controllers können Sie auch Vibrationsdaten an den Controller senden, um das Feedback zu ändern, das dem Benutzer des Controllers zur Verfügung gestellt wird. Der Controller enthält zwei Rumblemotoren, die unabhängig voneinander gesteuert werden können, indem Werte an die Funktion XInputSetState übergeben werden.

Die Geschwindigkeit jedes Motors kann mithilfe eines WORD-Werts in der Struktur XINPUT_VIBRATION angegeben werden, die wie folgt an die Funktion XInputSetState übergeben wird:

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 );

Beachten Sie, dass der rechte Motor der Hochfrequenzmotor ist, der linke Motor ist der Niederfrequenzmotor. Sie müssen nicht immer auf denselben Betrag festgelegt werden, wie sie unterschiedliche Effekte bieten.

Abrufen von Audiogerätebezeichnern

Das Headset für einen Controller verfügt über folgende Funktionen:

  • Aufzeichnen von Sound mithilfe eines Mikrofons
  • Wiedergeben von Sound mithilfe eines Kopfhörers

Verwenden Sie diesen Code, um die Gerätebezeichner für das Headset abzurufen:

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

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

Nachdem Sie die Geräte-IDs erhalten haben, können Sie die entsprechenden Schnittstellen erstellen. Wenn Sie z. B. XAudio 2.8 verwenden, verwenden Sie diesen Code, um eine Masterstimme für dieses Gerät zu erstellen:

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;

Informationen zur Verwendung der CaptureId-Geräte-ID finden Sie unter Erfassen eines Datenstroms.

Abrufen von DirectSound-GUIDs (nur Legacy-DirectX SDK)

Das Headset, das an einen Controller angeschlossen werden kann, verfügt über zwei Funktionen: Es kann Sound mit einem Mikrofon aufzeichnen und Sound mit einem Kopfhörer wiedergeben. In der XInput-API werden diese Funktionen über DirectSoundmithilfe der Schnittstellen IDirectSound8 und IDirectSoundCapture8 ausgeführt.

Um das Headsetmikrofon und den Kopfhörer ihren entsprechenden DirectSound-Schnittstellen zuzuordnen, müssen Sie die DirectSoundGUIDs für die Aufnahme- und Rendergeräte abrufen, indem Sie XInputGetDSoundAudioDeviceGuids aufrufen.

Hinweis

Die Verwendung von DirectSound wird nicht empfohlen und ist in Windows Store-Apps nicht verfügbar. Die Informationen in diesem Abschnitt gelten nur für die DirectX SDK-Version von XInput (XInput 1.3). Die Windows 8-Version von XInput (XInput 1.4) verwendet ausschließlich Windows Audio Session API (WASAPI)-Gerätebezeichner, die über XInputGetAudioDeviceIdsabgerufen werden.

XInputGetDSoundAudioDeviceGuids( i, &dsRenderGuid, &dsCaptureGuid );

Nachdem Sie die GUIDs abgerufen haben, können Sie die entsprechenden Schnittstellen erstellen, indem Sie DirectSoundCreate8 und DirectSoundCaptureCreate8 wie folgt aufrufen:

// 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;

Programmierreferenz