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 peores guardados en la historia ([citation needed]
, pero se reclamó el efecto dramático). Si presionas la tecla de barra espaciadora o, en dispositivos móviles, presionas el dinosaurio, la página sin conexión se convertirá en un juego de arcade reproducible. Quizás sepas que, en realidad, no tienes que desconectarte cuando tienes ganas de jugar: en Chrome, puedes navegar a about://dino
o, si eres geek, navegar a about://network-error/-106
. Pero ¿sabías que se juegan 270 millones de juegos del dinosaurio de Chrome todos los meses?
![La página sin conexión de Chrome con el juego del dinosaurio de Chrome.](https://web.dev/static/articles/gamepad/image/chromes-offline-page-th-3d966d4a2f411.png?authuser=6&hl=es)
Otro hecho que es más útil saber y que quizás no sepas es que en el modo arcade puedes jugar con un control de juegos. La compatibilidad con Gamepad se agregó hace aproximadamente un año al momento de escribir este documento 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.
Cómo usar la API de Gamepad
Detección de funciones y compatibilidad con navegadores
La API de Gamepad ofrece una excelente compatibilidad con navegadores tanto para computadoras de escritorio como para dispositivos móviles. Puedes usar el siguiente fragmento para detectar si la API de Gamepad es compatible:
if ('getGamepads' in navigator) {
// The API is supported!
}
Cómo el navegador representa 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
: Es elVRDisplay.displayId
de unVRDisplay
asociado (si es relevante).index
: Es el índice del control de juegos en el navegador.connected
: Indica si el control de juegos todavía está conectado al sistema.hand
: Es una enumeración que define en qué mano se sostiene el control o en qué mano es más probable que se retenga.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 objetoGamepadPose
que representa la información de poses asociada con un controlador de WebVR.axes
: Es un array de valores para todos los ejes del control 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) o analógicos (por ejemplo, 78% presionados). 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 se presiona el botón yfalse
si no se presiona.touched
: Es el estado en el que se tocó el botón. Si el botón es capaz de detectar el tacto, esta propiedad estrue
si lo tocas yfalse
en el caso contrario.value
: En el caso de los botones que tienen un sensor analógico, esta propiedad representa la cantidad de pulsación del botón, normalizada linealmente 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 control.
Algo adicional que puedes encontrar, según el navegador y el control de juegos que tengas, es una propiedad vibrationActuator
. Permite dos tipos de efectos de rumble:
- Rumbo doble: El efecto de la respuesta táctil que generan dos accionadores de masa excéntricos y rotativos, uno en cada control del control de juegos.
- Trigger-Rumble: El efecto de la respuesta táctil que generan dos motores independientes, con uno ubicado en cada uno de los gatillos del control de juegos.
En la siguiente descripción general del esquema, tomada 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 que aparece cuando se conecta un control de juegos
Para saber si un control de juegos está conectado, busca 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 el mismo nombre.
A continuación, puedes ver un ejemplo de un control de Xbox 360 que tenía tirado (sí, me encantan 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 que aparece cuando se desconecta un control de juegos
Las notificaciones de desconexiones del control de juegos suceden de forma análoga a la forma en que se detectan las conexiones.
Esta vez, la app detecta el evento gamepaddisconnected
. Observa que, en el siguiente ejemplo, connected
ahora es false
cuando desenchufo 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 mando en el bucle de juego
La conexió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 hay cero o menos de cuatro controles de juegos conectados, un elemento puede ser solo null
. Asegúrate siempre de verificar todos los elementos del array y de 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 uno o varios controles de juegos están conectados, pero navigator.getGamepads()
aún informa elementos null
, es posible que debas "activar" cada control de juegos presionando cualquiera de los 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();
Accionador de vibración
La propiedad vibrationActuator
muestra un objeto GamepadHapticActuator
, que corresponde a una configuración de motores y otros accionadores que pueden aplicar una fuerza para fines de respuesta táctil. Para reproducir los efectos táctiles, llama a Gamepad.vibrationActuator.playEffect()
. Los únicos tipos de efecto válidos son 'dual-rumble'
y 'trigger-rumble'
.
Efectos 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.
}
Rumbo doble
La función Dual-rumble describe una configuración táctil con un excéntrico motor de vibración de masa giratoria en cada control 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 no son iguales, por lo que los efectos de cada una se pueden combinar y crear efectos táctiles más complejos. Los efectos de sonido doble se definen con cuatro parámetros:
duration
: Establece la duración del efecto de vibración en milisegundos.startDelay
: Establece la duración de la demora hasta que se inicia la vibración.strongMagnitude
yweakMagnitude
: Establecen los niveles de intensidad de vibración de los motores de masa rotativa excéntricos, más pesados y más livianos, normalizados al 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 ruido
El ruido de disparo es el efecto de respuesta táctil que generan dos motores independientes, con uno 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
En la especificación de la API de Gamepad se define una función controlada por política que la cadena "gamepad"
identifica. Su allowlist
predeterminado es "self"
. La política de permisos de un documento determina
si algún contenido de ese documento puede acceder a navigator.getGamepads()
. Si se inhabilita en
cualquier documento, ningún contenido de este podrá usar navigator.getGamepads()
. Tampoco se activarán
los eventos gamepadconnected
ni gamepaddisconnected
.
<iframe src="index.html" allow="gamepad"></iframe>
Demostración
En el siguiente ejemplo, se incorpora una demostración para verificadores de controles de juegos. El código fuente está disponible en Glitch. Para probar la demostración, conecta un control de juegos mediante USB o Bluetooth, y presiona cualquiera de los botones o mueve cualquiera de sus ejes.
Contenido adicional: Juega al dinosaurio de Chrome en web.dev
En este sitio, puedes jugar al dinosaurio de Chrome con tu control de juegos. El código fuente está disponible en GitHub.
Consulta la implementación del sondeo de 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, saqué el juego del dinosaurio de Chrome del proyecto principal de Chromium (que actualizó un esfuerzo anterior de Arnelle Ballane), lo coloqué en un sitio independiente, amplió la implementación existente de la API de control de juegos mediante efectos de atenuación y vibración, creé un modo de pantalla completa y Mehul Satardekar contribuyó con una implementación del modo oscuro. ¡Que disfrutes de los videojuegos!
Vínculos útiles
- Especificaciones de la API de Gamepad
- Especificación de 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 editaron las especificaciones de la API de Gamepad. Los editores de especificaciones anteriores son Brandon Jones, Scott Graham y Ted Mielczarek. Brandon Jones editó la especificación de extensiones para Gamepad. Hero image de Laura Torrent Puig.