Bien démarrer avec XInput dans les applications Windows

XInput permet aux applications Windows de traiter les interactions du contrôleur (y compris les effets de vibration des contrôleurs et l’entrée et la sortie vocales).

Cette rubrique fournit une brève vue d’ensemble des fonctionnalités de XInput et explique comment la configurer dans une application. Elle comprend les éléments suivants :

Introduction à XInput

Les applications peuvent utiliser l’API XInput pour communiquer avec les contrôleurs de jeu lorsqu’ils sont connectés à un PC Windows (jusqu’à quatre contrôleurs uniques peuvent être branchés à la fois).

À l’aide de cette API, tout contrôleur connecté compatible peut être interrogé pour son état et les effets de vibration peuvent être définis. Les contrôleurs qui ont le casque attaché peuvent également être interrogés pour les périphériques d’entrée et de sortie audio qui peuvent être utilisés avec le casque pour le traitement vocal.

Disposition du contrôleur

Les contrôleurs compatibles ont deux bâtons directionnels analogiques, chacun avec un bouton numérique, deux déclencheurs analogiques, un pavé directionnel numérique avec quatre directions et huit boutons numériques. Les états de chacune de ces entrées sont retournés dans la structure XINPUT_GAMEPAD lorsque la fonction XInputGetState est appelée.

Le contrôleur a également deux moteurs de vibration pour fournir des effets de commentaires à l’utilisateur. Les vitesses de ces moteurs sont spécifiées dans la structure XINPUT_VIBRATION passée à la fonction XInputSetState pour définir les effets de vibration.

Si vous le souhaitez, un casque peut être connecté au contrôleur. Le casque a un microphone pour l’entrée vocale et un casque pour la sortie sonore. Vous pouvez appeler la fonction XInputGetAudioDeviceIds ou XInputGetDSoundAudioDeviceGuids héritée pour obtenir les identificateurs d’appareil qui correspondent aux appareils pour le microphone et le casque. Vous pouvez ensuite utiliser les API Core Audio pour recevoir une entrée vocale et envoyer une sortie audio.

Utilisation de XInput

L’utilisation de XInput est aussi simple que l’appel des fonctions XInput comme nécessaire. Les fonctions XInput permettent de récupérer l’état du contrôleur, d’obtenir les identifiants audio du casque et de définir les effets de grondement du contrôleur.

Plusieurs contrôleurs

L’API XInput prend en charge jusqu’à quatre contrôleurs connectés à tout moment. Les fonctions XInput nécessitent tous un paramètre dwUserIndex transmis pour identifier le contrôleur en cours de définition ou interrogé. Cet ID se trouve dans la plage de 0 à 3 et est défini automatiquement par XInput. Le nombre correspond au port auquel le contrôleur est connecté et n’est pas modifiable.

Chaque contrôleur affiche l’ID qu’il utilise en éclairant un quadrant sur l’« anneau de lumière » au centre du contrôleur. Une valeur dwUserIndex de 0 correspond au quadrant supérieur gauche; la numérotation se poursuit autour de l’anneau dans l’ordre des aiguilles d’une montre.

Les applications doivent prendre en charge plusieurs contrôleurs.

Obtention de l’état du contrôleur

Pendant toute la durée d’une application, l’obtention de l’état à partir d’un contrôleur sera probablement effectuée le plus souvent. D’image en image dans une application de jeu, l’état doit être récupéré et les informations de jeu mises à jour pour refléter les modifications du contrôleur.

Pour récupérer l’état, utilisez la fonction 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
    }
}

Notez que la valeur de retour de XInputGetState peut être utilisée pour déterminer si le contrôleur est connecté. Les applications doivent définir une structure pour contenir les informations du contrôleur interne; ces informations doivent être comparées aux résultats de XInputGetState pour déterminer les modifications, telles que les pressions sur les boutons ou les deltas de contrôleur analogique, ont été apportées à cette image. Dans l’exemple ci-dessus, g_Controllers représente une telle structure.

Une fois que l’état a été récupéré dans une structure de XINPUT_STATE, vous pouvez vérifier pour les modifications et obtenir des informations spécifiques sur l’état du contrôleur.

Le membre dwPacketNumber de la structure XINPUT_STATE peut être utilisé pour case activée si l’état du contrôleur a changé depuis le dernier appel à XInputGetState. Si dwPacketNumber ne change pas entre deux appels séquentiels vers XInputGetState, il n’y a pas eu de modification de l’état. Si elle diffère, l’application doit case activée membre du Boîtier de commande de la structure XINPUT_STATE pour obtenir des informations d’état plus détaillées.

Pour des raisons de performances, n’appelez pas XInputGetState pour un emplacement utilisateur « vide » chaque image. Nous vous recommandons d’espacer les vérifications pour les nouveaux contrôleurs toutes les quelques secondes à la place.

Zone morte

Pour permettre aux utilisateurs d’avoir une expérience de jeu cohérente, votre jeu doit implémenter correctement la zone morte. La zone morte est des valeurs de « mouvement » signalées par le contrôleur même lorsque les bâtons analogiques ne sont pas touchés et centrés. Il existe également une zone morte pour les 2 déclencheurs analogiques.

Remarque

Les jeux qui utilisent XInput qui ne filtrent pas la zone morte du tout connaîtront un jeu médiocre. Veuillez noter que certains contrôleurs sont plus sensibles que d’autres, la zone morte peut donc varier d’une unité à l’autre. Il est recommandé de tester vos jeux avec plusieurs manettes différentes sur différents systèmes.

Les applications doivent utiliser des « zones mortes » sur des entrées analogiques (déclencheurs, bâtons) pour indiquer quand un mouvement a été effectué suffisamment sur le stick ou le déclencheur pour être considéré comme valide.

Votre application doit vérifier les zones mortes et réagir de manière appropriée, comme dans cet exemple :

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

Cet exemple calcule le vecteur de direction du contrôleur et jusqu’à quel point le long du vecteur le contrôleur a été poussé. Cela permet l’application d’une zone morte circulaire en vérifiant si l’ampleur du contrôleur est supérieure à la valeur de zone morte. En outre, le code normalise l’ampleur du contrôleur, qui peut ensuite être multipliée par un facteur spécifique au jeu pour convertir la position du contrôleur en unités pertinentes pour le jeu.

Notez que vous pouvez définir vos propres zones mortes pour les sticks et les déclencheurs (n’importe où entre 0 et 65534), ou vous pouvez utiliser les zones mortes fournies définies comme XINPUT_GAMEPAD_LEFT_THUMo_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMo_DEADZONE et XINPUT_GAMEPAD_TRIGGER_THRESHOLD dans XInput.h :

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

Une fois que la zone morte est appliquée, il peut être utile de mettre à l’échelle la plage obtenue [0.0..1.0] à virgule flottante (comme dans l’exemple ci-dessus) et éventuellement d’appliquer une transformation non linéaire.

Par exemple, dans les jeux de conduite, il peut être utile de cuber le résultat pour donner une meilleure impression de la conduite des voitures à l’aide d’une manette de jeu, car le cubage du résultat donne plus de précision dans les plages inférieures, ce qui est souhaitable, puisque les joueurs appliquent généralement soit une force douce pour obtenir un mouvement subtil, soit une force forte dans une seule direction pour obtenir une réponse rapide.

Définition des effets de vibration

En plus d’obtenir l’état du contrôleur, vous pouvez également envoyer des données de vibration au contrôleur pour modifier les commentaires fournis à l’utilisateur du contrôleur. Le contrôleur contient deux moteurs de grondement qui peuvent être contrôlés indépendamment en passant des valeurs à la fonction XInputSetState.

La vitesse de chaque moteur peut être spécifiée à l’aide d’une valeur WORD dans la structure XINPUT_VIBRATION passée à la fonction XInputSetState comme suit :

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

Notez que le moteur droit est le moteur à haute fréquence, le moteur gauche est le moteur à basse fréquence. Ils n’ont pas toujours besoin d’être définis sur la même quantité, car ils fournissent des effets différents.

Obtention d’identificateurs de périphérique audio

Le casque d’un contrôleur a les fonctions suivantes :

  • Enregistrer le son à l’aide d’un microphone
  • Lisez le son à l’aide d’un casque

Utilisez ce code pour obtenir les identificateurs d’appareil pour le casque :

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

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

Après avoir obtenu les identificateurs d’appareil, vous pouvez créer les interfaces appropriées. Par exemple, si vous utilisez XAudio 2.8, utilisez ce code pour créer une voix de mastering pour cet appareil :

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;

Pour plus d’informations sur l’utilisation de l’identificateur d’appareil captureId, consultez Capture d’un stream.

Obtention des GUID DirectSound (SDK DirectX hérité uniquement)

Le casque qui peut être connecté à un contrôleur a deux fonctions : il peut enregistrer le son à l’aide d’un microphone, et il peut lire le son à l’aide d’un casque. Dans l’API XInput, ces fonctions sont effectuées via DirectSound, à l’aide des interfaces IDirectSound8 et IDirectSoundCapture8.

Pour associer le microphone et le casque à leurs interfaces DirectSound appropriées , vous devez obtenir les DirectSoundGUIDs pour les appareils de capture et de rendu en appelant XInputGetDSoundAudioDeviceGuids.

Remarque

L’utilisation de DirectSound hérité n’est pas recommandée et n’est pas disponible dans les applications du Windows Store. Les informations de cette section s’appliquent uniquement à la version du Kit de développement logiciel (SDK) DirectX de XInput (XInput 1.3). La version Windows 8 de XInput (XInput 1.4) utilise exclusivement des identificateurs d’appareil WASAPI (Windows Audio Session API) obtenus par le biais de XInputGetAudioDeviceIds.

XInputGetDSoundAudioDeviceGuids( i, &dsRenderGuid, &dsCaptureGuid );

Une fois que vous avez récupéré les GUID, vous pouvez créer les interfaces appropriées en appelant DirectSoundCreate8 et DirectSoundCaptureCreate8 comme suit :

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

Référence de programmation