Gamepad API

Utilizar mandos USB con Javascript


Los navegadores tienen a nuestra disposición una API para trabajar con los Gamepads o mandos USB que tengas conectados en la máquina. De esta forma, puedes utilizarlos para interactuar con la página, controlando elementos, realizando acciones o utilizando características del gamepad, como botones, joysticks o el motor de vibración.

Obtener gamepads conectados

Mediante el método getGamepads() incluido en el objeto navigator del navegador podemos obtener un de Gamepad conectados al USB. De esta forma, si no tenemos ninguno, nos devolverá un de valores null, pero si tenemos dos, nos devolverá un con dos objetos Gamepad (y el resto null).

Observa este ejemplo, en el caso de tener un gamepad conectado:

const gamepads = await navigator.getGamepads();   // [ Gamepad, null, null, null ]

Recuerda que se trata de una operación asíncrona, por lo que debes utilizar el await o gestionar la devolución de la promesa con un .then().

Eventos de Gamepad

Relacionado con este tema, tenemos a nuestra disposición dos eventos llamados gamepadconnected y gamepaddisconnected. Estos eventos se disparan en el momento que el navegador detecta que se ha conectado al USB un gamepad y está en funcionamiento (en algunos gamepad, debes pulsar el botón para sincronizarlo).

EventoDescripción
gamepadconnectedSe dispara cada vez que se conecta un gamepad.
gamepaddisconnectedSe dispara cada vez que se desconecta un gamepad.

La forma de escuchar estos eventos es a través de un addEventListener() y gestionar las tareas cuando se disparan. Observa que en mi ejemplo, he desestructurado la propiedad gamepad del objeto de evento que me devuelve, que es el mismo objeto que podemos obtener con el anteriormente mencionado getGamepads(), con la diferencia que ese método te devuelve un con todos los gamepads conectados, y en este evento tenemos la propiedad gamepad con sólo el gamepad que ha sido conectado:

globalThis.addEventListener("gamepadconnected", ({ gamepad }) => {
  console.log("Gamepad conectado: ", gamepad);
});

globalThis.addEventListener("gamepaddisconnected", ({ gamepad }) => {
  console.log("Gamepad desconectado: ", gamepad);
});

Siempre es conveniente escuchar estos eventos para responder o modificar nuestro juego o aplicación y que el usuario sepa que se ha conectado o desconectado el gamepad.

Objetos de Gamepad

Para trabajar con un Gamepad, básicamente tenemos tres objetos importantes: Gamepad (el objeto principal), GamepadButton (el objeto de cada botón) y GamepadHapticActuator (el motor de vibración del gamepad).

Veamos los detalles de cada uno de ellos.

El objeto Gamepad

Hemos mencionado varias veces que el método anterior o los eventos anteriores nos devuelven objetos Gamepad, pero aún no sabemos que contiene estos objetos. Cada gamepad conectado al sistema, podremos escucharlo a través de uno de estos objetos, que tienen información sobre el dispositivo:

Propiedades de GamepadDescripción
idIdentificación del controlador del gamepad.
indexNúmero identificativo del gamepad. Es persistente aunque se reconecte.
connectedIndica si el gamepad está conectado.
timestampFecha cuando se recibió datos por última vez. Es un DOMHighResTimeStamp (mejor precisión).
mappingSi se reconoce el layout del gamepad, devuelve standard. Sino vacío.
axesArray de valores por cada eje de la cruceta. Valores entre -1 y 1.
buttonsArray de GamepadButton, uno por cada botón del gamepad. De mayor a menor importancia.
vibrationActuatorRepresenta el sistema de vibración mediante un objeto GamepadHapticActuator.

Las primeras cuatro propiedades nos sirven para obtener información sobre el gamepad. La propiedad mapping nos puede servir para saber si el gamepad sigue el estándar oficial o no. Si se nos devuelve un que no es standard esto significa que el gamepad podría funcionar de forma diferente a lo esperado o devolver información donde no debería estar.

Sin embargo, las tres propiedades más importantes del mando son las siguientes:

  • 1️⃣ La propiedad axes con un con la dirección de los sticks direccionales.
  • 2️⃣ La propiedad buttons con un de botones del mando (gatillos, cruceta, etc...).
  • 3️⃣ La propiedad vibrationActuator que nos devuelve un objeto GamepadHapticActuator para interactuar con el motor de vibración.
const [ gamepad ] = await navigator.getGamepads();

gamepad.buttons[0]    // primer botón
gamepad.buttons[2]    // tercer botón
gamepad.axes[1]       // eje Y del primer pad direccional
gamepad.axes[2]       // eje X del segundo pad direccional

Es importante recalcar que el objeto gamepad es una «instantánea» del momento en que obtienes la información del gamepad. Si guardas en una variable estos objetos no se actualizarán en tiempo real, sino que debes crear un bucle y estar continuamente accediendo a getGamepads() para obtener la información actualizada.

El objeto GamepadButton

Hemos mencionado que la propiedad buttons anterior es un de objetos GamepadButton, pero no hemos mencionado que contienen. Estos objetos tienen 3 valores:

GamepadButtonDescripción
pressedEl botón está actualmente presionado.
touchedEl botón ha sido tocado (no necesariamente del todo). Útil para táctiles.
valuePresión del botón analógico, entre 0.0 y 1.0. Sólo en ciertos botones.

La propiedad pressed nos indica cuando un botón está siendo presionado. Por otro lado, la propiedad touched, aunque aparezca siempre, es útil en gamepad donde tenemos elementos táctiles, como los mandos de PlayStation. Por último, el valor value nos devuelve información sobre la presión con la que se ha pulsado el botón.

Ten en cuenta que estos valores se devuelven como y se deben comprobar en un bucle porque en general nos puede interesar saber si el usuario lo sigue presionando o ha dejado de hacerlo y realizar una acción en determinado momento.

El objeto GamepadHapticActuator

Por último, el objeto GamepadHapticActuator nos ofrece varios métodos interesantes para mandar efectos de vibración al gamepad, a través del motor de vibración del mismo.

GamepadHapticActuatorDescripción
effectsTipos de vibración admitidos: dual-rumble (posicional) y trigger-rumble (gatillos).
playEffects(type, params)Envía un patrón complejo de vibración: dual-rumble y trigger-rumble.
reset()Resetea y anula la vibración del gamepad.

Mediante la propiedad effects, por ejemplo, obtendremos un con dual-rumble y trigger-rumble si nuestro gamepad soporta estos tipos de vibración. Podemos utilizar esta propiedad para comprobar que soporta.

Por otro lado, los métodos playEffects() y reset() nos sirven para enviar al gamepad un efecto de vibración o detenerlo:

const [ gamepad ] = await navigator.getGamepads();

if (!gamepad.effects.includes("trigger-rumble")) {
  console.log("Tu gamepad no soporta vibración de gatillos.");
  return;
}

const params = {
  duration: 0,            /* En ms */
  startDelay: 0,          /* Retraso previo (en ms) */
  strongMagnitude: 0.0,   /* Intensidad de baja frecuencia */
  weakMagnitude: 0.5,     /* Intensidad de alta frecuencia */
  leftTrigger: 0.0,       /* Sólo para `triggle-rumble` */
  rightTrigger: 0.0,      /* Sólo para `triggle-rumble` */
};

gamepad.vibrationActuator.playEffects("trigger-rumble", params);

Los valores que tenemos en los params anteriores son los valores por defecto. Podemos modificarlos para cambiar su comportamiento.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev