Jouez au jeu du dinosaure avec votre manette de jeu Chrome

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

L'œuf de Pâques de la page hors connexion de Chrome est l'un des secrets les moins bien gardés de l'histoire ([citation needed], mais cette affirmation est faite pour l'effet dramatique). Si vous appuyez sur la touche Espace ou, sur les appareils mobiles, appuyez sur le dinosaure, la page hors connexion devient un jeu d'arcade jouable. Vous savez peut-être que vous n'avez pas besoin de passer en mode hors connexion lorsque vous avez envie de jouer: dans Chrome, vous pouvez simplement accéder à about://dino ou, pour les geeks, à about://network-error/-106. Mais saviez-vous que 270 millions de parties du jeu du dino Chrome sont jouées chaque mois ?

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

Un autre fait qui est peut-être plus utile à connaître et que vous ne connaissez peut-être pas est qu'en mode arcade, vous pouvez jouer au jeu avec une manette de jeu. La prise en charge des manettes de jeu a été ajoutée il y a environ un an, au moment de la rédaction de cet article, dans un commit de Reilly Grant. Comme vous pouvez le constater, le jeu, tout comme le reste du projet Chromium, est entièrement Open Source. Dans cet article, je vais vous montrer comment utiliser l'API Gamepad.

Utiliser l'API Gamepad

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

L'API Gamepad est universellement compatible avec les navigateurs, que ce soit sur ordinateur ou sur mobile. Vous pouvez détecter si l'API Gamepad est compatible à l'aide de l'extrait de code suivant:

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

Comment le navigateur représente 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. 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 dans le navigateur.
  • connected: indique si la manette de jeu est toujours connectée au système.
  • hand: énumération définissant la main dans laquelle le contrôleur est tenu ou est le plus susceptible d'être tenu.
  • timestamp: date de la dernière mise à jour des données de cette manette
  • mapping: mappage des boutons et des axes utilisé pour cet appareil ("standard" ou "xr-standard").
  • pose: objet GamepadPose représentant les informations sur la position associées à un contrôleur WebVR.
  • axes: tableau de valeurs pour tous les axes de la manette, normalisé linéairement dans la plage -1.0 à 1.0.
  • buttons: tableau d'états des boutons pour tous les boutons de la manette.

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

  • pressed: état de pression du bouton (true si le bouton est enfoncé et false s'il ne l'est pas).
  • touched: état du bouton lorsqu'il est touché. Si le bouton est capable de détecter le toucher, cette propriété est true si le bouton est touché, et false dans le cas contraire.
  • value: pour les boutons dotés d'un capteur analogique, cette propriété représente la quantité de pression sur le bouton, normalisée linéairement dans la plage 0.0 à 1.0.
  • hapticActuators: tableau contenant des objets GamepadHapticActuator, chacun représentant du matériel de retour haptique disponible sur le contrôleur.

En fonction de votre navigateur et de la manette de jeu que vous possédez, vous pouvez également rencontrer une propriété vibrationActuator. Il permet deux types d'effets de vibration:

  • Dual-Rumble: effet de retour haptique généré par deux actionneurs à masse rotative excentrique, un dans chaque poignée de la manette de jeu.
  • Trigger-Rumble: effet de retour haptique généré par deux moteurs indépendants, un dans chacun des déclencheurs de la manette de jeu.

La vue schématique suivante, tirée directement de la spécification, montre la mise en correspondance et la disposition des boutons et des axes sur une manette de jeu générique.

Présentation schématique des mappages des boutons et des 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 est connectée

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, ce qui peut se faire via USB ou Bluetooth, un GamepadEvent est déclenché, qui contient les détails de la manette dans une propriété gamepad bien nommée. Vous trouverez ci-dessous un exemple d'une manette Xbox 360 que j'avais sous la main (oui, je suis un fan de 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

La notification de déconnexion d'une manette de jeu se produit de la même manière que la détection des 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

L'accès à une manette de jeu commence par un appel à navigator.getGamepads(), qui renvoie un tableau d'éléments Gamepad. Dans Chrome, le tableau a toujours une longueur fixe de quatre éléments. Si aucun ou moins de quatre manettes de jeu ne sont connectées, un élément peut simplement être null. Veillez toujours à vérifier tous les éléments du tableau et gardez à l'esprit que les manettes de jeu "se souviennent" de leur emplacement et qu'elles ne sont pas toujours présentes dans le premier emplacement disponible.

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

Si un ou plusieurs manettes de jeu sont connectés, mais que navigator.getGamepads() continue de signaler des éléments null, vous devrez peut-être "réveiller" chaque manette de jeu en appuyant sur l'un de ses boutons. Vous pouvez ensuite interroger les états de la manette 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();

L'actionneur de vibration

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 des effets haptiques en appelant Gamepad.vibrationActuator.playEffect(). Les seuls types d'effets valides sont 'dual-rumble' et 'trigger-rumble'.

Effets de vibration 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.
}

Double retour haptique

La double vibration décrit une configuration haptique avec un moteur de vibration à masse rotative excentrique dans chaque poignée d'une manette de jeu standard. Dans cette configuration, l'un des moteurs peut faire vibrer l'ensemble de la manette. 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 double vibreur 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ébut de la vibration.
  • strongMagnitude et weakMagnitude: définissez les niveaux d'intensité des vibrations pour les moteurs à masse rotative excentrique plus lourds et plus légers, normalisés à la plage 0.0-1.0.
// 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 retour haptique

Le retour haptique des déclencheurs est l'effet généré par deux moteurs indépendants, un dans chacun des déclencheurs de la manette.

// 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 sur les autorisations

La spécification de l'API Gamepad définit une fonctionnalité contrôlée par les règles identifiée par la chaîne "gamepad". La valeur allowlist par défaut est "self". La stratégie d'autorisations d'un document détermine si un contenu de ce document 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émo

Une démo de testeur de manette de jeu est intégrée à l'exemple suivant. Le code source est disponible sur Glitch. Essayez la démonstration en connectant une manette à l'aide d'un câble USB ou du Bluetooth, puis en appuyant sur l'un de ses boutons ou en déplaçant l'une de ses axes.

Bonus: jouer au dino Chrome sur web.dev

Vous pouvez jouer au dinosaure Chrome avec votre manette de jeu sur ce site. Le code source est disponible sur GitHub. Consultez l'implémentation du sondage de la manette de jeu dans trex-runner.js et notez comment elle émule les pressions sur les touches.

Pour que la démonstration de la manette de jeu Chrome Dino fonctionne, j'ai extrait le jeu du dinosaure Chrome du projet Chromium principal (en mettant à jour un effort antérieur de Arnelle Ballane), l'ai placé sur un site autonome, étendu l'implémentation de l'API de manette de jeu existante en ajoutant des effets de masquage et de vibration, créé un mode plein écran, et Mehul Satardekar a contribué à l'implémentation du mode sombre. Bon jeu !

Remerciements

Ce document a été examiné par François Beaufort et Joe Medley. La spécification de l'API Gamepad est édité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 est éditée par Brandon Jones. Image principale par Laura Torrent Puig.