Introduzione con XInput nelle applicazioni Windows

XInput consente alle applicazioni di Windows di elaborare le interazioni del controller Xbox (inclusi gli effetti del rumble del controller e l'input vocale e l'output).

Questo argomento fornisce una breve panoramica delle funzionalità di XInput e di come configurarla in un'applicazione. Include quanto segue:

Introduzione a XInput

La console Xbox usa un controller di gioco compatibile con Windows. Le applicazioni possono usare l'API XInput per comunicare con questi controller quando sono collegati a un PC Windows (fino a quattro controller univoci possono essere collegati alla volta).

Usando questa API, è possibile eseguire query su qualsiasi controller Xbox connesso per il relativo stato e impostare gli effetti di vibrazione. I controller che hanno il visore VR collegato possono anche essere sottoposti a query per i dispositivi di input audio e output che possono essere usati con il visore VR per l'elaborazione vocale.

The Xbox Controller

Il controller Xbox ha due levette direzionali analogiche, ognuna con un pulsante digitale, due trigger analogici, un riquadro direzionale digitale con quattro direzioni e otto pulsanti digitali. Gli stati di ognuno di questi input vengono restituiti nella struttura XINPUT_GAMEPAD quando viene chiamata la funzione XInputGetState .

Il controller ha anche due motori di vibrazione per fornire effetti di feedback di forza all'utente. Le velocità di questi motori vengono specificate nella struttura XINPUT_VIBRATION passata alla funzione XInputSetState per impostare gli effetti di vibrazione.

Facoltativamente, un visore VR può essere collegato al controller. L'auricolare ha un microfono per l'input vocale e una cuffie per l'output audio. Puoi chiamare la funzione XInputGetAudioDeviceIds o XInputGetDSoundAudioGuids legacy per ottenere gli identificatori del dispositivo che corrispondono ai dispositivi per il microfono e le cuffie. È quindi possibile usare le API Core Audio per ricevere l'input vocale e inviare l'output audio.

Uso di XInput

L'uso di XInput è semplice come chiamare le funzioni XInput come richiesto. Usando le funzioni XInput, è possibile recuperare lo stato del controller, ottenere gli ID audio del visore VR e impostare effetti di rombo del controller.

Più controller

L'API XInput supporta fino a quattro controller connessi in qualsiasi momento. Le funzioni XInput richiedono tutti un parametro dwUserIndex passato per identificare il controller impostato o sottoposto a query. Questo ID sarà compreso nell'intervallo 0-3 e viene impostato automaticamente da XInput. Il numero corrisponde alla porta a cui è collegato il controller e non è modificabile.

Ogni controller visualizza l'ID utilizzato illuminando un quadrante sull'anello di luce al centro del controller. Un valore dwUserIndex pari a 0 corrisponde al quadrante superiore sinistro; la numerazione procede intorno all'anello in senso orario.

Le applicazioni devono supportare più controller.

Recupero dello stato del controller

Per tutta la durata di un'applicazione, il recupero dello stato da un controller verrà probabilmente eseguito più spesso. Dal frame al frame in un'applicazione di gioco, lo stato deve essere recuperato e le informazioni sul gioco devono essere aggiornate per riflettere le modifiche del controller.

Per recuperare lo stato, usare la funzione 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
    }
}

Si noti che il valore restituito di XInputGetState può essere usato per determinare se il controller è connesso. Le applicazioni devono definire una struttura per contenere le informazioni interne del controller; queste informazioni devono essere confrontate con i risultati di XInputGetState per determinare quali modifiche, ad esempio le pressioni dei pulsanti o i delta del controller analogico, sono state apportate a tale frame. Nell'esempio precedente g_Controllers rappresenta una struttura di questo tipo.

Dopo aver recuperato lo stato in una struttura di XINPUT_STATE , è possibile verificarne la presenza e ottenere informazioni specifiche sullo stato del controller.

Il membro dwPacketNumber della struttura XINPUT_STATE può essere utilizzato per verificare se lo stato del controller è stato modificato dall'ultima chiamata a XInputGetState. Se dwPacketNumber non cambia tra due chiamate sequenziali a XInputGetState, non è stata apportata alcuna modifica allo stato. In caso contrario, l'applicazione deve controllare il membro Game pad della struttura XINPUT_STATE per ottenere informazioni più dettagliate sullo stato.

Per motivi di prestazioni, non chiamare XInputGetState per uno slot utente "vuoto" ogni fotogramma. In alternativa, è consigliabile spaziarsi per verificare la presenza di nuovi controller ogni pochi secondi.

Zona morta

Per consentire agli utenti di avere un'esperienza di gioco coerente, il gioco deve implementare correttamente la zona morta. La zona morta è "movimento" valori segnalati dal controller anche quando le levette analogiche sono non toccate e centrate. C'è anche una zona morta per i 2 trigger analogici.

Nota

I giochi che usano XInput che non filtrano affatto la zona morta avranno un'esperienza di gioco scarsa. Si noti che alcuni controller sono più sensibili di altri, pertanto la zona morta può variare da unità a unità. È consigliabile testare i giochi con diversi controller Xbox in sistemi diversi.

Le applicazioni devono usare "zone morte" su input analogici (trigger, levette) per indicare quando è stato effettuato un movimento sufficientemente sul bastone o sul trigger da considerare valido.

L'applicazione deve verificare la presenza di zone non recapitabili e rispondere in modo appopriato, come in questo esempio:

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 questo esempio viene calcolato il vettore di direzione del controller e la distanza del vettore di cui è stato eseguito il push del controller. In questo modo è possibile applicare una zona morta circolare semplicemente controllando se la grandezza del controller è maggiore del valore del deadzone. Inoltre, il codice normalizza la grandezza del controller, che può quindi essere moltiplicata per un fattore specifico del gioco per convertire la posizione del controller in unità rilevanti per il gioco.

Si noti che è possibile definire zone morte personalizzate per le levette e i trigger (ovunque da 0 a 65534) oppure è possibile usare le zone morte specificate definite come XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE e XINPUT_GAMEPAD_TRIGGER_THRESHOLD in XInput.h:

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

Dopo aver applicato la zona morta, può risultare utile ridimensionare l'intervallo risultante [0.0...1.0] a virgola mobile (come nell'esempio precedente) e, facoltativamente, applicare una trasformazione non lineare.

Ad esempio, con i giochi di guida, può essere utile cubiare il risultato per fornire un'idea migliore di guidare le auto usando un game pad, poiché il risultato ti dà una maggiore precisione negli intervalli inferiori, che è auspicabile, poiché i giocatori in genere applicano una forza morbida per ottenere un movimento sottile o applicare forza dura in una sola direzione per ottenere la risposta rd.

Impostazione degli effetti di vibrazione

Oltre a ottenere lo stato del controller, è anche possibile inviare dati sulle vibrazioni al controller per modificare il feedback fornito all'utente del controller. Il controller contiene due motori rumble che possono essere controllati in modo indipendente passando valori alla funzione XInputSetState .

La velocità di ogni motore può essere specificata utilizzando un valore WORD nella struttura XINPUT_VIBRATION passata alla funzione XInputSetState come indicato di seguito:

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

Si noti che il motore destro è il motore ad alta frequenza, il motore sinistro è il motore a bassa frequenza. Non sempre devono essere impostati sulla stessa quantità, in quanto forniscono effetti diversi.

Recupero di identificatori di dispositivo audio

Il visore VR per un controller Xbox ha queste funzioni:

  • Registrare il suono usando un microfono
  • Riprodurre il suono usando un auricolare

Usare questo codice per ottenere gli identificatori del dispositivo per il visore VR:

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

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

Dopo aver ottenuto gli identificatori del dispositivo, è possibile creare le interfacce appropriate. Ad esempio, se si usa XAudio 2.8, usare questo codice per creare una voce mastering per questo dispositivo:

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;

Per informazioni su come usare l'identificatore del dispositivo captureId, vedi Acquisizione di un flusso.

Recupero di GUID DirectSound (solo DirectX SDK legacy)

Il visore VR che può essere collegato a un controller Xbox ha due funzioni: può registrare audio usando un microfono e può riprodurre audio usando una cuffie. Nell'API XInput queste funzioni vengono eseguite tramite DirectSound, usando le interfacce IDirectSound8 e IDirectSoundCapture8 .

Per associare il microfono e l'auricolare alle interfacce DirectSound appropriate, devi ottenere i DirectSoundGUIDs per l'acquisizione e il rendering dei dispositivi chiamando XInputGetDSoundAudioDeviceGuids.

Nota

L'uso di DirectSound legacy non è consigliato e non è disponibile nelle app dello Store Windows. Le informazioni contenute in questa sezione si applicano solo alla versione directX SDK di XInput (XInput 1.3). La versione Windows 8 di XInput (XInput 1.4) usa esclusivamente Windows identificatori di dispositivo DELL'API sessione audio (WASAPI) ottenuti tramite XInputGetAudioDeviceIds.

XInputGetDSoundAudioDeviceGuids( i, &dsRenderGuid, &dsCaptureGuid );

Dopo aver recuperato i GUID, è possibile creare le interfacce appropriate chiamando DirectSoundCreate8 e DirectSoundCaptureCreate8 come segue:

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

Guida di riferimento alla programmazione