Aprende a usar la API de Gamepad para llevar tus juegos de navegador al siguiente nivel.
El huevo de Pascua de la página sin conexión de Chrome es uno de los secretos peor guardados de la historia ([citation needed],
pero se afirma que tiene un efecto dramático). Si presionas la tecla espacio o, en dispositivos móviles, presionas el dinosaurio, la página sin conexión se convierte en un juego de arcade. Es posible que sepas que
no es necesario que te desconectes cuando quieras jugar: en Chrome, puedes navegar
a about://dino o, si eres un geek, a about://network-error/-106. ¿Pero sabías que
se juegan
270 millones de partidas del juego del dinosaurio de Chrome cada mes?
Otro dato que podría ser más útil y que quizás no conozcas es que, en el modo arcade, puedes jugar con un control de juegos. La compatibilidad con controles de juegos se agregó hace aproximadamente un año, en el momento de escribir este artículo, en una confirmación de Reilly Grant. Como puedes ver, el juego, al igual que el resto del proyecto de Chromium, es completamente de código abierto. En esta publicación, quiero mostrarte cómo usar la API de Gamepad.
Usa la API de Gamepad
Detección de funciones y compatibilidad con navegadores
La API de Gamepad tiene una excelente compatibilidad con navegadores en computadoras de escritorio y dispositivos móviles. Puedes detectar si la API de Gamepad es compatible con el siguiente fragmento:
if ('getGamepads' in navigator) {
// The API is supported!
}
Cómo representa el navegador un control de juegos
El navegador representa los controles de juegos como objetos Gamepad. Un Gamepad tiene las siguientes propiedades:
id: Es una cadena de identificación para el control de juegos. Esta cadena identifica la marca o el estilo del dispositivo de control de juegos conectado.displayId: ElVRDisplay.displayIdde unVRDisplayasociado (si corresponde).index: Es el índice del control de juegos en el navegador.connected: Indica si el control de juegos aún está conectado al sistema.hand: Es una enumeración que define en qué mano se sostiene el control o en cuál es más probable que se sostenga.timestamp: Es la última vez que se actualizaron los datos de este control de juegos.mapping: Es la asignación de botones y ejes que se usa para este dispositivo, ya sea"standard"o"xr-standard".pose: Es un objetoGamepadPoseque representa la información de la pose asociada con un control de WebVR.axes: Es un array de valores para todos los ejes del control de juegos, normalizados de forma lineal al rango de-1.0a1.0.buttons: Es un array de estados de botones para todos los botones del control de juegos.
Ten en cuenta que los botones pueden ser digitales (presionados o no presionados) o analógicos (por ejemplo, presionados en un 78%). Por este motivo, los botones se informan como objetos GamepadButton, con los siguientes atributos:
pressed: Es el estado de presión del botón (truesi se presiona el botón yfalsesi no se presiona).touched: Es el estado de toque del botón. Si el botón puede detectar el tacto, esta propiedad estruesi se toca el botón yfalsede lo contrario.value: En el caso de los botones que tienen un sensor analógico, esta propiedad representa la cantidad de veces que se presionó el botón, normalizada de forma lineal en el rango de0.0a1.0.hapticActuators: Es un array que contiene objetosGamepadHapticActuator, cada uno de los cuales representa el hardware de respuesta táctil disponible en el control.
Otra cosa que podrías encontrar, según el navegador y el control de juegos que tengas,
es una propiedad vibrationActuator. Permite dos tipos de efectos de vibración:
- Vibración doble: Es el efecto de respuesta táctil que generan dos actuadores de masa giratoria excéntrica, uno en cada empuñadura del control de juegos.
- Vibración de gatillos: Es el efecto de respuesta táctil que generan dos motores independientes, con un motor ubicado en cada uno de los gatillos del control de juegos.
En el siguiente esquema general, tomado directamente de la especificación, se muestra la asignación y la disposición de los botones y ejes en un control de juegos genérico.
Notificación cuando se conecta un control de juegos
Para saber cuándo se conecta un control de juegos, escucha el evento gamepadconnected que se activa en el objeto window. Cuando el usuario conecta un control de juegos, lo que puede suceder a través de USB o Bluetooth,
se activa un GamepadEvent que tiene los detalles del control de juegos en una propiedad gamepad con un nombre adecuado.
A continuación, puedes ver un ejemplo de un control de Xbox 360 que tenía por ahí (sí, me gustan los juegos retro).
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"}
*/
});
Notificación cuando se desconecta un control de juegos
La notificación de desconexiones de controles de juegos se produce de forma análoga a la forma en que se detectan las conexiones.
Esta vez, la app escucha el evento gamepaddisconnected. Observa cómo en el siguiente ejemplo connected ahora es false cuando desconecto el control de 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
*/
});
El control de juegos en tu bucle de juego
Para obtener un control de juegos, se debe llamar a navigator.getGamepads(), que devuelve un array con elementos Gamepad. El array en Chrome siempre tiene una longitud fija de cuatro elementos. Si se conectan cero o menos
de cuatro controles de juegos, un elemento puede ser simplemente null. Asegúrate de verificar todos los elementos del array y ten en cuenta que los controles de juegos "recuerdan" su ranura y es posible que no siempre estén presentes en la primera ranura disponible.
// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]
Si se conectan uno o varios controles de juegos, pero navigator.getGamepads() aún informa null elementos, es posible que debas "activar" cada control de juegos presionando cualquiera de sus botones. Luego, puedes sondear los estados del control de juegos en el bucle de juego, como se muestra en el siguiente código.
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();
El accionador de vibración
La propiedad vibrationActuator devuelve un objeto GamepadHapticActuator, que corresponde a una configuración de motores o de otros actuadores que pueden aplicar una fuerza para los fines de la respuesta táctil. Los efectos táctiles se pueden reproducir llamando a Gamepad.vibrationActuator.playEffect(). Los únicos
tipos de efectos válidos son 'dual-rumble' y 'trigger-rumble'.
Efectos de vibración 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.
}
Vibración doble
La vibración doble describe una configuración de respuesta táctil con un motor de vibración de masa giratoria excéntrica en cada mango de un control de juegos estándar. En esta configuración, cualquiera de los motores puede hacer vibrar todo el control de juegos. Las dos masas son desiguales para que los efectos de cada una se puedan combinar y crear efectos táctiles más complejos. Los efectos de vibración doble se definen con cuatro parámetros:
duration: Establece la duración del efecto de vibración en milisegundos.startDelay: Establece la duración del retraso hasta que se inicia la vibración.strongMagnitudeyweakMagnitude: Establecen los niveles de intensidad de la vibración para los motores de masa giratoria excéntrica más pesados y más livianos, normalizados en el rango0.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,
});
};
Vibración de los gatillos
La vibración de los gatillos es el efecto de respuesta táctil que generan dos motores independientes, con un motor ubicado en cada uno de los gatillos del control de juegos.
// 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,
});
};
Integración con la política de permisos
La especificación de la API de Gamepad define una
función controlada por políticas identificada por la
cadena "gamepad". Su allowlist predeterminado es "self". La política de permisos de un documento determina
si se permite que cualquier contenido de ese documento acceda a navigator.getGamepads(). Si se inhabilita en
cualquier documento, no se permitirá que ningún contenido del documento use navigator.getGamepads(), ni se
activarán los eventos gamepadconnected y gamepaddisconnected.
<iframe src="index.html" allow="gamepad"></iframe>
Demostración
En el siguiente ejemplo, se incorpora una demostración de un verificador de controles de juegos. El código fuente está disponible en GitHub. Para probar la demostración, conecta un control de juegos con un cable USB o Bluetooth, y presiona cualquiera de sus botones o mueve cualquiera de sus ejes.
Bonus: Juega al dinosaurio de Chrome en web.dev
Puedes jugar al juego del dinosaurio de Chrome con tu control de juegos en este
mismo sitio. El código fuente está disponible en GitHub.
Consulta la implementación de sondeo del control de juegos en trex-runner.js y observa cómo emula las pulsaciones de teclas.
Para que funcione la demostración del control de juegos del dinosaurio de Chrome, extraje el juego del dinosaurio de Chrome del proyecto principal de Chromium (actualizando un esfuerzo anterior de Arnelle Ballane), lo coloqué en un sitio independiente, extendí la implementación existente de la API de Gamepad agregando efectos de agacharse y vibración, creé un modo de pantalla completa y Mehul Satardekar contribuyó con una implementación del modo oscuro. ¡Que disfrutes de los juegos!
Vínculos útiles
- Especificación de la API de Gamepad
- Especificaciones de las extensiones de la API de Gamepad
- Repositorio de GitHub
Agradecimientos
François Beaufort y Joe Medley revisaron este documento. La especificación de la API de Gamepad la editan Steve Agoston, James Hollyer y Matt Reynolds. Los editores de especificaciones anteriores son Brandon Jones, Scott Graham y Ted Mielczarek. La especificación de las extensiones de controles de juegos la edita Brandon Jones.