Jouez au jeu du dinosaure avec votre manette de jeu Chrome

Découvrez comment utiliser l'API Gamepad pour faire passer vos jeux en ligne au niveau supérieur.

L'easter egg de Chrome, qui apparaît sur la page hors connexion, est l'un des secrets les plus gardés de l'histoire ([citation needed], mais on affirme qu'il s'agit là d'un effet dramatique). Si vous appuyez sur la touche Espace ou, sur les appareils mobiles, sur le dinosaure, la page hors connexion devient un jeu d'arcade. Vous savez peut-être que vous n'avez pas besoin de vous déconnecter lorsque vous avez envie de jouer: dans Chrome, il vous suffit d'accéder à about://dino ou, si vous êtes un geek, d'accéder à about://network-error/-106. Mais saviez-vous que 270 millions de jeux du dinosaure sur Chrome permettent de jouer chaque mois ?

Page hors connexion de Chrome avec le jeu du dinosaure de Chrome.
Appuyez sur la barre d'espace pour jouer !

Un autre fait qu'il est sans doute plus utile de savoir et dont vous n'avez peut-être pas entendu parler est qu'en mode arcade, vous pouvez jouer à ce jeu avec une manette de jeu. La prise en charge des manettes de jeu a été ajoutée il y a environ un an à la date de rédaction de cet article, via un commit de Reilly Grant. Comme vous pouvez le voir, le jeu, tout comme le reste du projet Chromium, est entièrement Open Source. Dans cet article, je veux vous montrer comment utiliser l'API Gamepad.

Utiliser l'API Gamepad

Détection de fonctionnalités et compatibilité avec les navigateurs

L'API Gamepad offre une compatibilité totale avec les navigateurs sur ordinateur et sur mobile. Vous pouvez vérifier si l'API Gamepad est compatible à l'aide de l'extrait suivant:

if ('getGamepads' in navigator) {
  // The API is supported!
}

Comment le navigateur représente-t-il une manette de jeu ?

Le navigateur représente les manettes de jeu sous la forme d'objets Gamepad. Un Gamepad a les propriétés suivantes:

  • id: chaîne d'identification de la manette de jeu. Cette chaîne identifie la marque ou le style de la manette de jeu connectée.
  • displayId: VRDisplay.displayId d'un VRDisplay associé (le cas échéant).
  • index: index de la manette de jeu dans le navigateur.
  • connected: indique si la manette est toujours connectée au système.
  • hand: énumération définissant la main dans laquelle la manette est ou est la plus susceptible d'être maintenue.
  • timestamp: date de la dernière mise à jour des données de cette manette de jeu
  • mapping: bouton et mappage des axes en cours d'utilisation pour cet appareil ("standard" ou "xr-standard").
  • pose: objet GamepadPose représentant les informations de pose associées à un contrôleur WebVR.
  • axes: tableau de valeurs pour tous les axes de la manette de jeu, normalisée de façon linéaire dans la plage -1.01.0.
  • buttons: tableau des états de tous les boutons de la manette de jeu.

Notez que les boutons peuvent être numériques (en appuyant ou non) ou analogiques (par exemple, 78% enfoncés). C'est pourquoi les boutons sont signalés en tant qu'objets GamepadButton, avec les attributs suivants:

  • pressed: état d'appui sur le bouton (true si l'utilisateur appuie sur le bouton et false s'il n'a pas appuyé dessus).
  • touched: état tactile du bouton. Si le bouton est capable de détecter une pression tactile, cette propriété est true si l'utilisateur appuie dessus, et false dans le cas contraire.
  • value: pour les boutons dotés d'un capteur analogique, cette propriété représente la pression sur le bouton, normalisée de manière linéaire dans la plage 0.01.0.
  • hapticActuators: tableau contenant des objets GamepadHapticActuator, chacun représentant le matériel de retour haptique disponible sur le contrôleur.

Selon votre navigateur et votre manette de jeu, vous pouvez également rencontrer une propriété vibrationActuator. Il offre deux types d'effets de rumble:

  • Double roulement: effet de retour haptique généré par deux actionneurs de masse rotatifs excentriques, un dans chaque poignée de la manette de jeu.
  • Déclencheur-Rumble: effet de retour haptique généré par deux moteurs indépendants, un moteur situé dans chacun des déclencheurs de la manette de jeu.

La présentation schématique suivante, tirée directement de la spécification, montre le mappage et la disposition des boutons et des axes sur une manette de jeu générique.

Présentation schématique des mappages de boutons et d'axes d'une manette de jeu courante.
Représentation visuelle d'une disposition de manette de jeu standard (source).

Notification lorsqu'une manette de jeu se connecte

Pour savoir quand une manette de jeu est connectée, écoutez l'événement gamepadconnected qui se déclenche sur l'objet window. Lorsque l'utilisateur connecte une manette de jeu, ce qui peut se produire à l'aide d'un câble USB ou du Bluetooth, un GamepadEvent est déclenché. Les détails de la manette se trouvent dans une propriété gamepad bien nommée. Dans l'exemple ci-dessous, vous pouvez voir une manette Xbox 360 dans laquelle je traînais (oui, j'aime les jeux rétro).

window.addEventListener('gamepadconnected', (event) => {
  console.log('✅ 🎮 A gamepad was connected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: true
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: GamepadHapticActuator {type: "dual-rumble"}
  */
});

Notification lorsqu'une manette de jeu est déconnectée

Les notifications de déconnexion de la manette de jeu fonctionnent de la même manière que les connexions. Cette fois, l'application écoute l'événement gamepaddisconnected. Notez que, dans l'exemple suivant, connected est maintenant false lorsque je débranche la manette Xbox 360.

window.addEventListener('gamepaddisconnected', (event) => {
  console.log('❌ 🎮 A gamepad was disconnected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: false
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: null
  */
});

La manette de jeu dans votre boucle de jeu

La prise en main d'une manette commence par un appel à navigator.getGamepads(), qui renvoie un tableau contenant des éléments Gamepad. Le tableau dans Chrome a toujours une longueur fixe de quatre éléments. Si zéro ou moins de quatre manettes de jeu sont connectées, un élément peut simplement être null. Assurez-vous toujours de vérifier tous les éléments du tableau et de garder à l'esprit que les manettes de jeu "mémorisent" leur emplacement et qu'elles ne se trouvent pas toujours au premier emplacement disponible.

// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]

Si une ou plusieurs manettes sont connectées, mais que navigator.getGamepads() signale toujours des éléments null, vous devrez peut-être activer chaque manette en appuyant sur l'un de ses boutons. Vous pouvez ensuite interroger les états de la manette de jeu dans votre boucle de jeu, comme indiqué dans le code suivant.

const pollGamepads = () => {
  // Always call `navigator.getGamepads()` inside of
  // the game loop, not outside.
  const gamepads = navigator.getGamepads();
  for (const gamepad of gamepads) {
    // Disregard empty slots.
    if (!gamepad) {
      continue;
    }
    // Process the gamepad state.
    console.log(gamepad);
  }
  // Call yourself upon the next animation frame.
  // (Typically this happens every 60 times per second.)
  window.requestAnimationFrame(pollGamepads);
};
// Kick off the initial game loop iteration.
pollGamepads();

Actionneur du vibreur

La propriété vibrationActuator renvoie un objet GamepadHapticActuator, qui correspond à une configuration de moteurs ou d'autres actionneurs pouvant appliquer une force à des fins de retour haptique. Vous pouvez lire les effets haptiques en appelant Gamepad.vibrationActuator.playEffect(). Le seul type d'effet valide est 'dual-rumble'. Le double bruit décrit une configuration haptique avec un moteur vibrant de masse rotative excentrique dans chaque poignée d'une manette de jeu standard. Dans cette configuration, l'un ou l'autre des moteurs est capable de faire vibrer toute la manette de jeu. Les deux masses sont inégales afin que les effets de chacune puissent être combinés pour créer des effets haptiques plus complexes. Les effets de Dual Rumble sont définis par quatre paramètres:

  • duration: définit la durée de l'effet de vibration en millisecondes.
  • startDelay: définit la durée du délai avant le déclenchement du vibreur.
  • strongMagnitude et weakMagnitude: définissez les niveaux d'intensité des vibrations pour les moteurs rotatifs excentriques plus lourds et plus légers, normalisés dans la plage 0.01.0.

Effets de rumble compatibles

if (gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
  // Trigger rumble supported.
} else if (gamepad.vibrationActuator.effects.includes('dual-rumble')) {
  // Dual rumble supported.
} else {
  // Rumble effects aren't supported.
}

Dual rumble

// This assumes a `Gamepad` as the value of the `gamepad` variable.
const dualRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  gamepad.vibrationActuator.playEffect('dual-rumble', {
    // Start delay in ms.
    startDelay: delay,
    // Duration in ms.
    duration: duration,
    // The magnitude of the weak actuator (between 0 and 1).
    weakMagnitude: weak,
    // The magnitude of the strong actuator (between 0 and 1).
    strongMagnitude: strong,
  });
};

Déclencher le rumble

// This assumes a `Gamepad` as the value of the `gamepad` variable.
const triggerRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  // Feature detection.
  if (!('effects' in gamepad.vibrationActuator) || !gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
    return;
  }
  gamepad.vibrationActuator.playEffect('trigger-rumble', {
    // Duration in ms.
    duration: duration,
    // The left trigger (between 0 and 1).
    leftTrigger: leftTrigger,
    // The right trigger (between 0 and 1).
    rightTrigger: rightTrigger,
  });
};

Intégration avec les règles d'autorisation

La spécification de l'API Gamepad définit une fonctionnalité contrôlée par des règles, identifiée par la chaîne "gamepad". Son allowlist par défaut est "self". La stratégie d'autorisations d'un document détermine si son contenu est autorisé à accéder à navigator.getGamepads(). Si cette option est désactivée dans un document, aucun contenu du document ne sera autorisé à utiliser navigator.getGamepads(), et les événements gamepadconnected et gamepaddisconnected ne seront pas déclenchés.

<iframe src="index.html" allow="gamepad"></iframe>

Démonstration

Une démo des testeurs de manette de jeu est intégrée dans l'exemple suivant. Le code source est disponible sur Glitch. Essayez la démonstration en connectant une manette de jeu via USB ou le Bluetooth, puis en appuyant sur l'un de ses boutons ou en déplaçant n'importe quel axe.

En bonus: jouez au dinosaure de Chrome sur web.dev

Vous pouvez jouer à Chrome Dino avec votre manette sur ce site. Le code source est disponible sur GitHub. Découvrez l'implémentation de l'interrogation de manette de jeu dans trex-runner.js et notez comment elle émule les pressions sur les touches.

Pour que la démo de la manette de jeu Chrome avec le dinosaure fonctionne, j'ai extrait le jeu du dinosaure de Chrome du projet principal Chromium (en mettant à jour une initiative antérieure d'Arnelle Ballane), l'ayant placée sur un site autonome, étendu l'implémentation existante de l'API de la manette de jeu en ajoutant des effets de diminution et de vibration, créé un mode plein écran et Mehul Satardekar a contribué à l'implémentation du mode sombre. À vous de jouer !

Remerciements

Ce document a été examiné par François Beaufort et Joe Medley. La spécification de l'API Gamepad est modifiée par Steve Agoston, James Hollyer et Matt Reynolds. Les anciens éditeurs de spécifications sont Brandon Jones, Scott Graham et Ted Mielczarek. La spécification des extensions de manette de jeu est modifiée par Brandon Jones. Image héros de Laura Torrent Puig.