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 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, en realidad, no tienes que jugar sin conexión cuando te apetezca: en Chrome, puedes navegar a about://dino
o, si eres un geek, ir a about://network-error/-106
. Sin embargo, ¿sabías que se juegan 270 millones de juegos del dinosaurio de Chrome todos los meses?
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 gamepad. La compatibilidad con gamepads se agregó hace aproximadamente un año, en el momento de la escritura de este artículo, en una commit 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 gamepad 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
: Es la última vez que se actualizaron los datos de este gamepad.mapping
: Es la asignación de botones y ejes en uso para este dispositivo, ya sea"standard"
o"xr-standard"
.pose
: 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 mando de juegos.
Ten en cuenta que los botones pueden ser digitales (presionados o no) 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 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 gamepad 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 rotativa 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 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 gamepad
Las notificaciones de desconexiones del mando de juegos ocurren de manera 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 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
Para obtener un gamepad, 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 de revisar todos los elementos del array y ten en cuenta que los gamepads “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 actuador de vibración
La propiedad vibrationActuator
muestra 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. Para reproducir efectos táctiles, llama 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 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 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 de la demora hasta que se inicia la vibración.strongMagnitude
yweakMagnitude
: Establece los niveles de intensidad de vibración para los motores de masa rotativa excéntrica más pesados y más ligeros, normalizados al 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,
});
};
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.
Bonificación: 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 atenuació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 editores de especificaciones anteriores 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.