Aprende a usar la API de Gamepad para llevar tus juegos web 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 la afirmación se hizo por el efecto dramático). Si presionas la barra espaciadora o presionas el dinosaurio en un dispositivo móvil, la página sin conexión se convierte en un juego de arcade. Tal vez sepas que no tienes que desconectarte cuando tienes ganas de jugar: en Chrome, puedes navegar a about://dino
o, si eres un geek, dirígete a about://network-error/-106
. Pero ¿sabías que se juegan 270 millones de juegos del dinosaurio de Chrome todos los meses?
Otro hecho que podría ser más útil conocer y que quizás no conozcas es que, en el modo arcade, puedes jugar con un control de juegos. La compatibilidad con gamepads se agregó hace aproximadamente un año, en el momento de la escritura de este artículo, en un compromiso 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.
Cómo usar la API de Gamepad
Detección de funciones y compatibilidad con navegadores
La API de Gamepad tiene una compatibilidad universal 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 el navegador representa un control de juegos
El navegador representa los mandos de juegos como objetos Gamepad
. Un Gamepad
tiene las siguientes propiedades:
id
: Es una cadena de identificación para el mando de juegos. Esta cadena identifica la marca o el estilo del dispositivo de gamepad conectado.displayId
: Es elVRDisplay.displayId
de unVRDisplay
asociado (si corresponde).index
: Es el índice del mando de juegos en el navegador.connected
: Indica si el gamepad sigue conectado al sistema.hand
: Es una enumeración que define en qué mano se sostiene el controlador o en qué mano es más probable que se sostenga.timestamp
: 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 objetoGamepadPose
que representa la información de pose asociada con un controlador de WebVR.axes
: Un array de valores para todos los ejes del mando de juegos, normalizado de forma lineal al rango de-1.0
a1.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, un 78% presionado). Es por eso que los botones se informan como objetos GamepadButton
, con los siguientes atributos:
pressed
: Es el estado presionado del botón (true
si está presionado yfalse
si no lo está).touched
: Es el estado tocado del botón. Si el botón puede detectar toques, esta propiedad estrue
si se toca el botón yfalse
de lo contrario.value
: En el caso de los botones que tienen un sensor analógico, esta propiedad representa la cantidad en la que se presionó el botón, normalizada de forma lineal al rango de0.0
a1.0
.hapticActuators
: Es un array que contiene objetosGamepadHapticActuator
, cada uno de los cuales representa el hardware de respuesta táctil disponible en el controlador.
Según el navegador y el mando de juegos que tengas, es posible que encuentres una propiedad vibrationActuator
. Permite dos tipos de efectos de vibración:
- Dual-Rumble: El efecto de respuesta táctil que generan dos actuadores de masa rotativos excéntricos, uno en cada empuñadura del mando de juego.
- Trigger-Rumble: El efecto de respuesta táctil que generan dos motores independientes, con un motor ubicado en cada uno de los gatillos del mando de juegos.
En la siguiente descripción general esquemática, que se toma directamente de las especificaciones, se muestra la asignación y la disposición de los botones y los ejes en un gamepad genérico.
Notificación cuando se conecta un control de juegos
Para saber cuándo se conecta un gamepad, escucha el evento gamepadconnected
que se activa en el objeto window
. Cuando el usuario conecta un gamepad, lo que puede ocurrir a través de USB o Bluetooth, se activa un GamepadEvent
que tiene los detalles del gamepad 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 videojuegos 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 gamepad
Las notificaciones de desconexiones del control de mando sucede de forma análoga a la forma en que se detectan las conexiones.
Esta vez, la app estará a la espera del evento gamepaddisconnected
. Observa cómo, en el siguiente ejemplo, connected
ahora es false
cuando desenchufo el controlador 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
La obtención de un control de juegos comienza con una llamada a navigator.getGamepads()
, que muestra un array con elementos Gamepad
. El array en Chrome siempre tiene una longitud fija de cuatro elementos. Si no hay ninguno o menos de cuatro gamepads conectados, un elemento puede ser solo null
. Asegúrate siempre 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 hay uno o varios gamepads conectados, pero navigator.getGamepads()
aún informa elementos null
, es posible que debas presionar cualquiera de sus botones para activar cada gamepad. Luego, puedes sondear los estados del gamepad en tu 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
muestra un objeto GamepadHapticActuator
, que corresponde a una configuración de motores u otros accionadores que pueden aplicar una fuerza para la respuesta táctil. Para reproducir efectos táctiles, llama a Gamepad.vibrationActuator.playEffect()
. Los únicos tipos de efecto válidos son 'dual-rumble'
y 'trigger-rumble'
.
Efectos de ruido 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 dual
La vibración dual describe una configuración táctil con un motor de vibración de masa rotativa excéntrica en cada mango de un mando de juegos estándar. En esta configuración, cualquier motor puede hacer vibrar todo el mando de juegos. Las dos masas no son iguales, de modo que los efectos de cada una se pueden combinar para crear efectos táctiles más complejos. Los efectos de doble ruido 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.strongMagnitude
yweakMagnitude
: Establecen los niveles de intensidad de vibración para los motores de masa rotativa más pesados y ligeros, normalizados en el rango de0.0
a1.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,
});
};
Activar vibración
La vibración del gatillo es el efecto de respuesta táctil que generan dos motores independientes, con un motor ubicado en cada uno de los gatillos del mando 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 que se identifica con la cadena "gamepad"
. Su allowlist
predeterminado es "self"
. La política de permisos de un documento determina si el contenido de ese documento puede acceder 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 demo de probador de gamepad. El código fuente está disponible en Glitch. Para probar la demostración, conecta un gamepad mediante USB o Bluetooth y presiona cualquiera de sus botones o mueve cualquiera de sus ejes.
Contenido adicional: Juega al dinosaurio de Chrome en web.dev
Puedes jugar al Dinosaurio de Chrome con tu mando en este mismo sitio. El código fuente está disponible en GitHub.
Consulta la implementación de sondeo del gamepad en trex-runner.js
y observa cómo emula las presiones de teclas.
Para que funcione la demostración del Gamepad de Chrome Dino, quité 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 oclusión y vibración, creé un modo de pantalla completa y Mehul Satardekar contribuyó con una implementación de modo oscuro. ¡Que disfrutes el juego!
Vínculos útiles
- Especificación de la API de Gamepad
- Especificación de las extensiones de la API de Gamepad
- Repositorio de GitHub
Agradecimientos
François Beaufort y Joe Medley revisaron este documento. Steve Agoston, James Hollyer y Matt Reynolds editan la especificación de la API de Gamepad. Los antiguos editores de especificaciones son Brandon Jones, Scott Graham y Ted Mielczarek. Brandon Jones es el editor de las especificaciones de Gamepad Extensions. Imagen hero de Laura Torrent Puig.