Découvrez comment utiliser l'API Gamepad pour faire passer vos jeux Web au niveau supérieur.
L'easter egg de la page hors connexion de Chrome est l'un des secrets les moins bien gardés de l'histoire ([citation needed], mais l'affirmation est faite pour l'effet dramatique). Si vous appuyez sur la touche Espace ou, sur les appareils mobiles, si vous 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 vous déconnecter pour jouer : dans Chrome, il vous suffit d'accéder à about://dino ou, pour les plus geeks d'entre vous, à about://network-error/-106. Mais saviez-vous que le jeu Chrome Dino est joué 270 millions de fois par mois ?
Un autre fait, sans doute plus utile à connaître et que vous ne savez peut-être pas, est que vous pouvez jouer au jeu avec une manette en mode Arcade. La compatibilité avec les 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 voir, 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 compatible avec tous les navigateurs, sur ordinateur et 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 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.displayIdd'unVRDisplayassocié (le cas échéant).index: index de la manette de jeu 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 la manette est tenue ou est le plus susceptible d'être tenue.timestamp: date de la dernière mise à jour des données de cette manette de jeu.mapping: mappage des boutons et des axes utilisé pour cet appareil, soit"standard", soit"xr-standard".pose: objetGamepadPoserepré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é de manière linéaire sur la plage-1.0–1.0.buttons: tableau des états de tous les boutons de la manette de jeu.
Notez que les boutons peuvent être numériques (enfoncés ou non) ou analogiques (par exemple, enfoncés à 78 %). C'est pourquoi les boutons sont signalés comme des objets GamepadButton, avec les attributs suivants :
pressed: état du bouton (truesi l'utilisateur appuie sur le bouton,falses'il n'appuie pas dessus).touched: état du bouton lorsqu'il est touché. Si le bouton est capable de détecter le toucher, cette propriété esttruesi le bouton est touché, etfalsesinon.value: pour les boutons dotés d'un capteur analogique, cette propriété représente la pression exercée sur le bouton, normalisée de manière linéaire sur la plage0.0–1.0.hapticActuators: tableau contenant des objetsGamepadHapticActuator, chacun représentant le matériel de retour haptique disponible sur la manette.
Selon votre navigateur et votre manette, vous pouvez également rencontrer une propriété vibrationActuator. Il permet d'obtenir deux types d'effets de vibration :
- Double vibration : effet de retour haptique généré par deux actionneurs à masse rotative excentrique, un dans chaque poignée de la manette de jeu.
- Vibration des gâchettes : effet de retour haptique généré par deux moteurs indépendants, un dans chaque gâchette de la manette de jeu.
Le schéma suivant, tiré directement de la spécification, montre le mappage et la disposition des boutons et des axes sur une manette de jeu générique.
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 de jeu, ce qui peut se faire via USB ou Bluetooth, un GamepadEvent est déclenché. Il contient les détails de la manette de jeu dans une propriété gamepad bien nommée.
Vous trouverez ci-dessous un exemple de manette Xbox 360 que j'avais sous la main (oui, j'aime le rétrogaming).
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 fait de la même manière que la détection de connexion.
Cette fois, l'application écoute l'événement gamepaddisconnected. Notez que dans l'exemple suivant, connected devient 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
Pour obtenir une manette de jeu, vous devez d'abord appeler navigator.getGamepads(), qui renvoie un tableau avec des éléments Gamepad. Dans Chrome, le tableau a toujours une longueur fixe de quatre éléments. Si aucune manette de jeu ou moins de quatre manettes de jeu sont connectées, un élément peut simplement être null. Veillez toujours à vérifier tous les éléments du tableau et n'oubliez pas que les manettes de jeu "mémorisent" leur emplacement et ne sont pas toujours présentes au premier emplacement disponible.
// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]
Si une ou plusieurs manettes de jeu sont connectées, mais que navigator.getGamepads() indique toujours null éléments, vous devrez peut-être "réveiller" 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();
L'actionneur de vibrations
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. Les effets haptiques peuvent être lus 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 vibration
Le double retour haptique décrit une configuration haptique avec un moteur vibrant à masse rotative excentrique dans chaque poignée d'une manette de jeu standard. Dans cette configuration, chaque moteur peut faire vibrer l'ensemble de la manette. Les deux masses sont inégales, de sorte que les effets de chacune peuvent être combinés pour créer des effets haptiques plus complexes. Les effets de double vibration 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.strongMagnitudeetweakMagnitude: définissez les niveaux d'intensité de vibration pour les moteurs à masse rotative excentrique plus lourds et plus légers, normalisés sur la plage0.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,
});
};
Vibration des gâchettes
La vibration des gâchettes est l'effet de retour haptique généré par deux moteurs indépendants, un dans chaque gâchette de la manette de jeu.
// 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 à la règle 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". La valeur allowlist par défaut est "self". La règle d'autorisation d'un document détermine si le contenu de ce document est autorisé à accéder à navigator.getGamepads(). Si cette fonctionnalité 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 se déclencheront pas.
<iframe src="index.html" allow="gamepad"></iframe>
Démo
Une démonstration de testeur de manette de jeu est intégrée à l'exemple suivant. Le code source est disponible sur GitHub. Essayez la démo en connectant une manette de jeu via USB ou Bluetooth, puis en appuyant sur l'un de ses boutons ou en déplaçant l'un de ses axes.
Bonus : Jouer au jeu du dinosaure de Chrome sur web.dev
Vous pouvez jouer au jeu du dinosaure de Chrome avec votre manette sur ce site. Le code source est disponible sur GitHub.
Examinez l'implémentation de l'interrogation 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 Chrome Dino du projet Chromium principal (en mettant à jour un effort précédent d'Arnelle Ballane), je l'ai placé sur un site autonome, j'ai étendu l'implémentation existante de l'API Gamepad en ajoutant des effets de ducking et de vibration, j'ai créé un mode plein écran et Mehul Satardekar a contribué à l'implémentation d'un mode sombre. Bon jeu !
Liens utiles
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 de jeu est modifiée par Brandon Jones.