Boîtier de commande et vibration

Cet article explique les notions de base de la programmation pour les boîtiers de commande avec l’API [Windows.Gaming.Input.Gamepadgamepad] et les API associées pour la plateforme Windows universelle (UWP).

Voici ce que vous allez apprendre à la lecture de cet article :

  • obtenir une liste des boîtiers de commande connectés et de leurs utilisateurs
  • Détecter l’ajout ou la suppression d’un boîtier de commande
  • lire les entrées provenant d’un ou de plusieurs boîtiers de commande
  • comment envoyer des commandes de vibration et d’impulsion
  • comportement des pavés de jeu en tant qu’appareils de navigation d’interface utilisateur

Vue d’ensemble du boîtier de commande

Les boîtiers de commande comme la manette sans fil Xbox et le contrôleur sans fil Xbox S sont des appareils d’entrée de jeu à usage général. Ils sont les périphériques d’entrée standard sur Xbox One et un choix courant pour les joueurs Windows lorsqu’ils ne privilégient pas un clavier et une souris. Les boîtiers de commande sont pris en charge dans les applications Windows 10 ou Windows 11 et Xbox UWP via l’espace de noms Windows.Gaming.Input.

Les boîtiers de commande Xbox One se composent d’un bouton multidirectionnel (appelé également pavé directionnel) ; des boutons A, B, X, Y, Afficher et Menu ; de joysticks gauche et droit ; de gâchettes hautes et d un déclencheur gauche et droite ; et de quatre moteurs de vibration. Les deux bâtons de pouce fournissent des lectures analogiques doubles dans les axes X et Y, et jouent également le rôle de bouton lorsqu’ils sont enfoncés vers l’intérieur. Chaque déclencheur fournit une valeur analogique qui représente sa position vers l’arrière.

Remarque

Windows.Gaming.Input.GamepadRemarque prend également en charge les boîtiers de commande Xbox 360, qui présentent la même disposition de contrôles que les boîtiers de commande Xbox One standard.

Déclencheurs de vibration et d’impulsion

Les boîtiers de commande Xbox One fournissent deux moteurs indépendants pour les vibrations fortes et subtiles du boîtier de commande, ainsi que deux moteurs dédiés pour fournir des vibrations nettes à chaque déclencheur (cette fonctionnalité unique est la raison pour laquelle les déclencheurs de boîtier de commande Xbox One sont appelés déclencheurs d’impulsion).

Remarque

Les boîtiers de commande Xbox 360 n’ont pas de déclencheurs à impulsions.

Pour plus d’informations, consultez la vue d’ensemble des déclencheurs vibration et impulsionnel.

Zones mortes du Joystick

Un joystick dans la position centrale produirait idéalement la même lecture neutre dans les axes X et Y à chaque fois. Toutefois, en raison de forces mécaniques et de la sensibilité du joystick, les lectures réelles dans la position centrale ne sont approximatives que la valeur neutre idéale et peuvent varier entre les lectures suivantes. C’est pourquoi vous devez toujours utiliser une petite zone morte, qui correspond à une plage de valeurs proches de la position centrale idéale à ignorer, pour compenser les écarts dus aux différences de fabrication, à l’usure mécanique ou à d’autres problèmes du boîtier de commande.

Les zones mortes plus grandes offrent une stratégie simple pour séparer l’entrée intentionnelle de l’entrée involontaire.

Pour plus d’informations, consultez Lecture des joysticks.

Navigation de l’interface utilisateur

Pour réduire la difficulté associée à la prise en charge de plusieurs périphériques d’entrée différents lors de la navigation dans l’interface utilisateur, et pour favoriser la cohérence entre les jeux et les périphériques, la plupart des périphériques d’entrée physiques agissent en tant que périphérique d’entrée logique distinct, appelé contrôleur de navigation de l’interface utilisateur. Le contrôleur de navigation de l’interface utilisateur fournit un vocabulaire commun pour les commandes de navigation de l’interface utilisateur sur les périphériques d’entrée.

Quand ils sont utilisés comme contrôleurs de navigation d’interface, les boîtiers de commande mappent l’ensemble obligatoire de commandes de navigation au stick analogique gauche, au bouton multidirectionnel, et aux boutons Afficher, Menu, A et B.

Commande de navigation Entrée du boîtier de commande
Haut Joystick gauche vers le haut / D-pad vers le haut
Descendre Joystick gauche vers le bas / D-pad vers le bas
Left Joystick gauche / D-pad gauche
Right Joystick gauche droite / D-pad droit
Afficher Bouton Afficher
Menu Bouton de menu
Accepter Bouton A
Annuler Bouton B

En outre, les boîtiers de commande mappent l’ensemble facultatif de commandes de navigation aux entrées restantes.

Commande de navigation Entrée du boîtier de commande
Page précédente Déclencheur gauche
Page suivante Déclencheur droit
Page vers la gauche Gâchette gauche
Page vers la droite Gâchette droite
Faire défiler vers le haut Faire défiler le joystick droit vers le haut
Faire défiler vers le bas Faire défiler le joystick droit vers le bas
Faire défiler vers la gauche Faire défiler le joystick droit vers la gauche
Faire défiler vers la droite Faire défiler le joystick droit vers la droite
Contexte 1 Bouton X
Contexte 2 Bouton Y
Contexte 3 Appuyez sur le joystick gauche
Contexte 4 Appuyez sur le joystick droit

Détectez et suivez les boîtiers de commande

Les boîtiers de commande sont gérés par le système. Par conséquent, vous n’avez pas besoin de les créer ou de les initialiser. Le système fournit une liste de boîtiers de commande et d’événements connectés pour vous avertir lorsqu’un boîtier de commande est ajouté ou supprimé.

La liste des boîtiers de commande

La classe Boîtiers de commande fournit une propriété statique, boîtiers de commande, qui est une liste en lecture seule de boîtiers de commande actuellement connectés. Vous serez certainement intéressé uniquement par certains des boîtiers de commande connectés. C’est pourquoi nous vous recommandons de gérer votre propre collection de boîtiers de commande au lieu d’utiliser ceux répertoriés par la propriété Gamepads.

L’exemple suivant copie tous les boîtiers de commande connectés dans une nouvelle collection. Notez que, étant donné que d’autres threads en arrière-plan accèdent à cette collection (dans les événements GamepadAdded et GamepadRemoved ), vous devez placer un verrou autour de tout code qui lit ou met à jour la collection.

auto myGamepads = ref new Vector<Gamepad^>();
critical_section myLock{};

for (auto gamepad : Gamepad::Gamepads)
{
    // Check if the gamepad is already in myGamepads; if it isn't, add it.
    critical_section::scoped_lock lock{ myLock };
    auto it = std::find(begin(myGamepads), end(myGamepads), gamepad);

    if (it == end(myGamepads))
    {
        // This code assumes that you're interested in all gamepads.
        myGamepads->Append(gamepad);
    }
}
private readonly object myLock = new object();
private List<Gamepad> myGamepads = new List<Gamepad>();
private Gamepad mainGamepad;

private void GetGamepads()
{
    lock (myLock)
    {
        foreach (var gamepad in Gamepad.Gamepads)
        {
            // Check if the gamepad is already in myGamepads; if it isn't, add it.
            bool gamepadInList = myGamepads.Contains(gamepad);

            if (!gamepadInList)
            {
                // This code assumes that you're interested in all gamepads.
                myGamepads.Add(gamepad);
            }
        }
    }   
}

Ajout et suppression de boîtiers de commande

Quand un boîtier de commande est ajouté ou supprimé, les événements GamepadAdded et GamepadRemoved sont déclenchés. Vous pouvez inscrire des gestionnaires pour ces événements afin de suivre les boîtiers de commande actuellement connectés.

L’exemple suivant démarre le suivi d’un boîtier de commande qui a été ajouté.

Gamepad::GamepadAdded += ref new EventHandler<Gamepad^>(Platform::Object^, Gamepad^ args)
{
    // Check if the just-added gamepad is already in myGamepads; if it isn't, add
    // it.
    critical_section::scoped_lock lock{ myLock };
    auto it = std::find(begin(myGamepads), end(myGamepads), args);

    if (it == end(myGamepads))
    {
        // This code assumes that you're interested in all new gamepads.
        myGamepads->Append(args);
    }
}
Gamepad.GamepadAdded += (object sender, Gamepad e) =>
{
    // Check if the just-added gamepad is already in myGamepads; if it isn't, add
    // it.
    lock (myLock)
    {
        bool gamepadInList = myGamepads.Contains(e);

        if (!gamepadInList)
        {
            myGamepads.Add(e);
        }
    }
};

L’exemple suivant arrête le suivi d’un boîtier de commande qui a été supprimé. Vous devrez également gérer ce qui arrive aux boîtiers de commande que vous suivez lorsqu’ils sont supprimés ; par exemple, ce code suit uniquement l’entrée d’un boîtier de commande et la définit simplement sur nullptr lorsqu’elle est supprimée. Vous devez vérifier chaque image si votre boîtier de commande est actif et mettre à jour le boîtier de commande à partir duquel vous collectez des entrées lorsque les contrôleurs sont connectés et déconnectés.

Gamepad::GamepadRemoved += ref new EventHandler<Gamepad^>(Platform::Object^, Gamepad^ args)
{
    unsigned int indexRemoved;
    critical_section::scoped_lock lock{ myLock };

    if(myGamepads->IndexOf(args, &indexRemoved))
    {
        if (m_gamepad == myGamepads->GetAt(indexRemoved))
        {
            m_gamepad = nullptr;
        }

        myGamepads->RemoveAt(indexRemoved);
    }
}
Gamepad.GamepadRemoved += (object sender, Gamepad e) =>
{
    lock (myLock)
    {
        int indexRemoved = myGamepads.IndexOf(e);

        if (indexRemoved > -1)
        {
            if (mainGamepad == myGamepads[indexRemoved])
            {
                mainGamepad = null;
            }

            myGamepads.RemoveAt(indexRemoved);
        }
    }
};

Pour plus d’informations, consultez Pratiques d’entrée pour les jeux.

Utilisateurs et casques

Chaque boîtier de commande peut être associé à un compte d’utilisateur pour lier son identité à son gameplay et peut avoir un casque attaché pour faciliter la conversation vocale ou les fonctionnalités dans le jeu. Pour en savoir plus sur l’utilisation des utilisateurs et des casques, consultez Suivi des utilisateurs et de leurs appareils et casque.

Lecture du boîtier de commande

Une fois que vous avez identifié le boîtier de commande qui vous intéresse, vous pouvez commencer à collecter les entrées à partir de ce stick. Toutefois, contrairement à d’autres sortes d’entrées que vous connaissez peut-être, les boîtiers de commande ne communiquent pas les changements d’état en déclenchant des événements. À la place, vous devez les interroger régulièrement pour connaître leur état actuel.

Interrogation du boîtier de commande

Le processus d’interrogation capture un instantané du périphérique de navigation à un moment précis. Cette approche de la collecte d’entrée est adaptée à la plupart des jeux, car leur logique s’exécute généralement dans une boucle déterministe plutôt qu’en étant pilotée par les événements. Il est également généralement plus simple d’interpréter les commandes de jeu à partir d’entrées collectées toutes en même temps plutôt que de nombreuses entrées uniques collectées au fil du temps.

Vous interrogez un boîtier de commande en appelant GetCurrentReading ; cette fonction retourne un GamepadReading qui contient l’état du boîtier de commande.

L’exemple de code suivant interroge un boîtier de commande pour obtenir son état actuel.

auto gamepad = myGamepads[0];

GamepadReading reading = gamepad->GetCurrentReading();
Gamepad gamepad = myGamepads[0];

GamepadReading reading = gamepad.GetCurrentReading();

En plus de l’état du boîtier de commande, chaque valeur comprend un timestamp qui indique précisément le moment d’extraction de cet état. Le timestamp est utile pour se rapporter au minutage des lectures précédentes ou au minutage de la simulation de jeu.

Lecture des joysticks

Chaque joystick fournit une lecture analogique comprise entre -1.0 et +1.0 dans les axes X et Y. Sur l’axe X, une valeur de -1.0 correspond à la position de joystick la plus à gauche ; une valeur de +1.0 indique la position la plus à droite. Sur l’axe Y, une valeur de -1.0 correspond à la position de joystick la plus en bas ; une valeur de +1.0 indique la position la plus en haut. Sur les deux axes, la valeur est proche de 0.0 quand le stick analogique se trouve à la position centrale, mais la valeur exacte peut varier d’une lecture à l’autre. Les stratégies de compensation de cette variation sont décrites plus loin, dans cette section.

La valeur de l’axe X du stick de gauche est lue à partir de la propriété LeftThumbstickX de la structure GamepadReading; la valeur de l’axe Y est lue à partir de la LeftThumbstickY propriété. La valeur de l’axe X du stick droit est lue à partir de la RightThumbstickX propriété ; la valeur de l’axe Y est lue à partir de la RightThumbstickY propriété.

float leftStickX = reading.LeftThumbstickX;   // returns a value between -1.0 and +1.0
float leftStickY = reading.LeftThumbstickY;   // returns a value between -1.0 and +1.0
float rightStickX = reading.RightThumbstickX; // returns a value between -1.0 and +1.0
float rightStickY = reading.RightThumbstickY; // returns a value between -1.0 and +1.0
double leftStickX = reading.LeftThumbstickX;   // returns a value between -1.0 and +1.0
double leftStickY = reading.LeftThumbstickY;   // returns a value between -1.0 and +1.0
double rightStickX = reading.RightThumbstickX; // returns a value between -1.0 and +1.0
double rightStickY = reading.RightThumbstickY; // returns a value between -1.0 and +1.0

Si vous examinez les valeurs que les joysticks fournissent, vous remarquerez qu’ils ne fournissent pas toujours la valeur neutre 0.0 quand ils se trouvent à la position centrale. Les joysticks fournissent différentes valeurs proches de 0.0 chaque fois qu’ils sont déplacés, puis remis à la position centrale. Pour atténuer ces variations, vous pouvez implémenter une petite zone morte, qui est une plage de valeurs proche de la position centrale idéale qui sont ignorées. Pour implémenter une zone morte, vous pouvez déterminer de quelle distance le joystick a été déplacé à partir de sa position centrale, en ignorant les valeurs qui sont plus proches que la distance de référence spécifiée. Vous pouvez calculer la distance de manière approximative (elle n’est pas exacte du fait que les lectures des entrées de joystick sont essentiellement des valeurs polaires, et non planaires) en utilisant simplement le théorème de Pythagore. Cela produit une zone morte radiale.

L’exemple suivant illustre une zone morte radiale simple calculée à l’aide du théorème de Pythagore.

float leftStickX = reading.LeftThumbstickX;   // returns a value between -1.0 and +1.0
float leftStickY = reading.LeftThumbstickY;   // returns a value between -1.0 and +1.0

// choose a deadzone -- readings inside this radius are ignored.
const float deadzoneRadius = 0.1;
const float deadzoneSquared = deadzoneRadius * deadzoneRadius;

// Pythagorean theorem -- for a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;

// accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) > deadzoneSquared)
{
    // input accepted, process it
}
double leftStickX = reading.LeftThumbstickX;   // returns a value between -1.0 and +1.0
double leftStickY = reading.LeftThumbstickY;   // returns a value between -1.0 and +1.0

// choose a deadzone -- readings inside this radius are ignored.
const double deadzoneRadius = 0.1;
const double deadzoneSquared = deadzoneRadius * deadzoneRadius;

// Pythagorean theorem -- for a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
double oppositeSquared = leftStickY * leftStickY;
double adjacentSquared = leftStickX * leftStickX;

// accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) > deadzoneSquared)
{
    // input accepted, process it
}

Chaque stick fait également office de bouton lorsqu’il est enfoncé vers l’intérieur; pour plus d’informations sur la lecture de cette entrée, consultez La lecture des boutons.

Lecture des déclencheurs

Les déclencheurs sont représentés sous forme de valeurs à virgule flottante comprises entre 0,0 (entièrement libéré) et 1.0 (entièrement décompressés). La valeur du déclencheur gauche est lue à partir de la propriété LeftTrigger de la structure GamepadReading; la valeur du déclencheur droit est lue à partir de la RightTrigger propriété.

float leftTrigger  = reading.LeftTrigger;  // returns a value between 0.0 and 1.0
float rightTrigger = reading.RightTrigger; // returns a value between 0.0 and 1.0
double leftTrigger = reading.LeftTrigger;  // returns a value between 0.0 and 1.0
double rightTrigger = reading.RightTrigger; // returns a value between 0.0 and 1.0

Lecture des boutons

Chacun des boutons du boîtier de commande (le bouton à quatre directions, les gâchettes hautes gauche et droite, les joysticks gauche et droit, et les boutons A, B, X, Y, Afficher et Menu) fournit une valeur numérique qui indique si le bouton est à l’état appuyé (position basse) ou relâché (position haute). Pour plus d’efficacité, les entrées de bouton ne sont pas représentées individuellement sous forme de valeurs booléennes. Elles sont toutes regroupées dans un seul champ de bits représenté par l’énumération GamepadButtons.

Les valeurs des boutons sont lues à partir de la propriété Buttons de la structure GamepadReading. Étant donné que cette propriété est un champ de bits, un masquage au niveau du bit est effectué pour isoler la valeur du bouton qui vous intéresse. Le bouton est appuyé (bas) lorsque le bit correspondant est défini ; sinon, il est relâché (haut).

L’exemple suivant détermine si le bouton A est à l’état appuyé.

if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
    // button A is pressed
}
if (GamepadButtons.A == (reading.Buttons & GamepadButtons.A))
{
    // button A is pressed
}

L’exemple suivant détermine si le bouton A est à l’état relâché.

if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
    // button A is released
}
if (GamepadButtons.None == (reading.Buttons & GamepadButtons.A))
{
    // button A is released
}

Il est possible que vous ayez parfois besoin de savoir quand un bouton passe de l’état appuyé à l’état relâché, ou inversement, si plusieurs boutons sont à l’état appuyé ou relâché, ou si un groupe de boutons a une disposition particulière, certains présentant l’état appuyé et d’autres l’état relâché. Pour plus d’informations sur la détection de chacune de ces conditions, consultez Détection des transitions de boutons et Détection des dispositions complexes des boutons.

Exécuter l’exemple d’entrée du boîtier de commande

L’exemple GamepadUWP (github) montre comment se connecter à un boîtier de commande et lire son état.

Aperçu des déclencheurs de vibration et d’impulsion

Les moteurs de vibration à l’intérieur d’un boîtier de commande sont destinés à fournir des commentaires tactiles à l’utilisateur. Les jeux utilisent cette possibilité de créer un plus grand sens de l’immersion, pour aider à communiquer des informations d’état (telles que prendre des dommages), à signaler la proximité des objets importants ou pour d’autres utilisations créatives.

Les boîtiers de commande Xbox One sont équipés d’un total de quatre moteurs de vibration indépendants. Deux de ces moteurs sont de gros moteurs installés à l’intérieur du boîtier de commande. Celui de gauche fournit une forte vibration de grande amplitude, tandis que celui de droite fournit une vibration plus subtile et de plus faible amplitude. Les deux autres sont de petits moteurs, un à l’intérieur de chaque déclencheur, qui fournissent des rafales nettes de vibration directement aux doigts de déclencheur de l’utilisateur ; cette capacité unique du boîtier de commande Xbox One est la raison pour laquelle ses déclencheurs sont appelés déclencheurs d’impulsion. En orchestrant ces moteurs ensemble, un large éventail de sensations tactiles peut être produit.

Utilisation de vibrations et d’impulsions

La vibration du boîtier de commande est contrôlée par la propriété Vibration de la classe Boîtier de commande . Vibration est une instance de la structure GamepadVibration contenant quatre valeurs à virgule flottante, chacune d’elles représentant l’intensité d’un moteur.

Vous pouvez modifier directement les membres de la propriété Gamepad.Vibration, mais nous vous recommandons plutôt d’initialiser une instance GamepadVibration distincte avec les valeurs de votre choix, puis de la copier dans la propriété Gamepad.Vibration pour modifier simultanément toutes les intensités de moteur actuelles.

L’exemple suivant montre comment modifier toutes les intensités moteurs en même temps.

// get the first gamepad
Gamepad^ gamepad = Gamepad::Gamepads->GetAt(0);

// create an instance of GamepadVibration
GamepadVibration vibration;

// ... set vibration levels on vibration struct here

// copy the GamepadVibration struct to the gamepad
gamepad.Vibration = vibration;
// get the first gamepad
Gamepad gamepad = Gamepad.Gamepads[0];

// create an instance of GamepadVibration
GamepadVibration vibration = new GamepadVibration();

// ... set vibration levels on vibration struct here

// copy the GamepadVibration struct to the gamepad
gamepad.Vibration = vibration;

Utilisation des moteurs de vibration

Les moteurs de vibration gauche et droite prennent des valeurs à virgule flottante comprises entre 0,0 (aucune vibration) et 1,0 (vibration la plus intense). L’intensité du moteur gauche est définie par la propriété de la LeftMotorstructure GamepadVibration; l’intensité du moteur droit est définie par la RightMotor propriété.

L’exemple suivant définit l’intensité des moteurs de vibration et active la vibration du boîtier de commande.

GamepadVibration vibration;
vibration.LeftMotor = 0.80;  // sets the intensity of the left motor to 80%
vibration.RightMotor = 0.25; // sets the intensity of the right motor to 25%
gamepad.Vibration = vibration;
GamepadVibration vibration = new GamepadVibration();
vibration.LeftMotor = 0.80;  // sets the intensity of the left motor to 80%
vibration.RightMotor = 0.25; // sets the intensity of the right motor to 25%
mainGamepad.Vibration = vibration;

N’oubliez pas que ces deux moteurs ne sont pas identiques afin de définir ces propriétés sur la même valeur ne produit pas la même vibration dans un moteur que dans l’autre. Quelle que soit la valeur définie, le moteur gauche produit une vibration plus forte à faible fréquence que le moteur droit, lequel, pour la même valeur, produit une vibration plus subtile à haute fréquence. Même à la valeur maximale, le moteur gauche ne peut pas produire les fréquences élevées du moteur droit, ni le moteur droit ne peut produire les forces élevées du moteur gauche. Cependant, étant donné que les moteurs sont connectés rigidement par le corps du boîtier de commande, les joueurs n’ont pas l’expérience des vibrations complètement indépendamment même si les moteurs ont des caractéristiques différentes et peuvent vibrer avec différentes intensités. Cette disposition permet de produire une gamme plus large et plus expressive de sensations que si les moteurs étaient identiques.

Utilisation des déclencheurs d’impulsion

Chaque moteur de déclencheur d’impulsion prend une valeur à virgule flottante comprise entre 0,0 (aucune vibration) et 1,0 (vibration la plus intense). L’intensité du déclencheur du moteur gauche est définie par la propriété LeftTrigger de la structure GamepadVibration; l’intensité du déclencheur du moteur droit est définie par la RightTrigger propriété.

L’exemple suivant définit l’intensité des déclencheurs d’impulsion et les active.

GamepadVibration vibration;
vibration.LeftTrigger = 0.75;  // sets the intensity of the left trigger to 75%
vibration.RightTrigger = 0.50; // sets the intensity of the right trigger to 50%
gamepad.Vibration = vibration;
GamepadVibration vibration = new GamepadVibration();
vibration.LeftTrigger = 0.75;  // sets the intensity of the left trigger to 75%
vibration.RightTrigger = 0.50; // sets the intensity of the right trigger to 50%
mainGamepad.Vibration = vibration;

Contrairement aux autres, les deux moteurs de vibration à l’intérieur des déclencheurs sont identiques afin qu’ils produisent la même vibration dans l’un ou l’autre moteur pour la même valeur. Toutefois, étant donné que ces moteurs ne sont pas reliés rigidement de quelque manière que ce soit, les joueurs subissent les vibrations indépendamment. Cette disposition permet aux sensations totalement indépendantes d’être dirigées simultanément vers les deux déclencheurs, et les aide à transmettre plus d’informations spécifiques que les moteurs dans le corps du boîtier de commande.

Exécuter l’exemple de vibration du boîtier de commande

L’échantillon GamepadVibrationUWP (github) montre comment les moteurs de vibration du boîtier de commande et les déclencheurs d’impulsion sont utilisés pour produire une variété d’effets.

Voir aussi