Aprenda a usar a API Gamepad para levar seus jogos da Web para o próximo nível.
O easter egg da página off-line do Chrome é um dos segredos mais mal guardados da história ([citation needed]
,
mas a alegação foi feita para o efeito dramático). Se você pressionar a tecla espaço ou, em dispositivos móveis, tocar no dinossauro, a página off-line vai se tornar um jogo de fliperama. Você pode não precisar ficar off-line quando quiser jogar: no Chrome, basta navegar
até about://dino
ou, para os mais geeks, até about://network-error/-106
. Mas você sabia
que são
270 milhões de jogos do dinossauro do Chrome jogados todo mês?
Outro fato que pode ser mais útil e que você talvez não saiba é que no modo arcade, é possível jogar com um gamepad. O suporte a gamepad foi adicionado há cerca de um ano, no momento em que este artigo foi escrito, em um commit de Reilly Grant. Como você pode ver, o jogo, assim como o restante do projeto Chromium, é totalmente de código aberto. Neste post, vou mostrar como usar a API Gamepad.
Usar a API Gamepad
Detecção de recursos e suporte a navegadores
A API Gamepad tem um ótimo suporte a navegadores em computadores e dispositivos móveis. É possível detectar se a API Gamepad tem suporte usando o snippet abaixo:
if ('getGamepads' in navigator) {
// The API is supported!
}
Como o navegador representa um gamepad
O navegador representa gamepads como objetos
Gamepad
. Um Gamepad
tem as seguintes propriedades:
id
: uma string de identificação para o gamepad. Essa string identifica a marca ou o estilo do dispositivo de gamepad conectado.displayId
: oVRDisplay.displayId
de umVRDisplay
associado (se relevante).index
: o índice do gamepad no navegador.connected
: indica se o gamepad ainda está conectado ao sistema.hand
: um tipo enumerado que define em qual mão o controle está sendo segurado ou em qual é mais provável que seja segurado.timestamp
: a última vez que os dados do gamepad foram atualizados.mapping
: o mapeamento de botões e eixos em uso para o dispositivo,"standard"
ou"xr-standard"
.pose
: um objetoGamepadPose
que representa as informações de pose associadas a um controlador do WebVR.axes
: uma matriz de valores para todos os eixos do gamepad, linearmente normalizada para o intervalo de-1.0
a1.0
.buttons
: uma matriz de estados de botões para todos os botões do gamepad.
Os botões podem ser digitais (pressionados ou não pressionados) ou analógicos (por exemplo, 78% pressionados). Por isso,
os botões são informados como objetos GamepadButton
, com os seguintes atributos:
pressed
: o estado pressionado do botão (true
se o botão for pressionado efalse
se não for.touched
: o estado tocado do botão. Se o botão for capaz de detectar toques, essa propriedade serátrue
se o botão estiver sendo tocado efalse
caso contrário.value
: para botões com um sensor analógico, essa propriedade representa a quantidade de vezes que o botão foi pressionado, normalizado linearmente para o intervalo de0.0
a1.0
.hapticActuators
: uma matriz que contém objetosGamepadHapticActuator
, cada um representando um hardware de feedback tátil disponível no controle.
Dependendo do navegador e do gamepad, você pode encontrar uma propriedade vibrationActuator
. Ele permite dois tipos de efeitos de vibração:
- Dual-Rumble: o efeito de retorno tátil gerado por dois atuadores de massa rotativa excêntricos, um em cada empunhadura do gamepad.
- Trigger-Rumble: o efeito de retorno tátil gerado por dois motores independentes, com um motor localizado em cada gatilho do gamepad.
A visão geral esquemática a seguir, tirada diretamente da especificação, mostra o mapeamento e a disposição dos botões e eixos em um gamepad genérico.
Notificação quando um gamepad é conectado
Para saber quando um gamepad está conectado, detecte o evento gamepadconnected
que é acionado no
objeto window
. Quando o usuário conecta um gamepad, o que pode acontecer usando USB ou Bluetooth,
um GamepadEvent
é acionado com os detalhes do gamepad em uma propriedade gamepad
com o nome adequado.
Confira abaixo um exemplo de um controle do Xbox 360 que eu tinha (sim, eu gosto de
jogos retrô).
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"}
*/
});
Notificação quando um gamepad é desconectado
A notificação de desconexão do gamepad acontece de forma análoga à forma como as conexões são detectadas.
Dessa vez, o app detecta o evento gamepaddisconnected
. Observe como, no exemplo a seguir,
connected
agora é false
quando eu desconecto o controle do 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
*/
});
O gamepad no loop de jogo
A captura de um gamepad começa com uma chamada para navigator.getGamepads()
, que retorna uma matriz
com itens Gamepad
. A matriz no Chrome sempre tem um comprimento fixo de quatro itens. Se zero ou menos
de quatro gamepads estiverem conectados, um item poderá ser null
. Sempre verifique todos os itens da
matriz e saiba que os gamepads "lembram" do slot e nem sempre estão presentes no
primeiro slot disponível.
// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]
Se um ou vários gamepads estiverem conectados, mas o navigator.getGamepads()
ainda informar itens null
,
talvez seja necessário "despertar" cada gamepad pressionando qualquer um dos botões. Em seguida, você pode consultar os estados
do gamepad no loop do jogo, conforme mostrado no código abaixo.
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();
Atuador de vibração
A propriedade vibrationActuator
retorna um objeto GamepadHapticActuator
, que corresponde a uma
configuração de motores ou outros acionadores que podem aplicar uma força para fins de
feedback tátil. Os efeitos táteis podem ser reproduzidos chamando Gamepad.vibrationActuator.playEffect()
. Os únicos
tipos de efeito válidos são 'dual-rumble'
e 'trigger-rumble'
.
Efeitos de vibração compatíveis
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.
}
Vibração dupla
O Dual-rumble descreve uma configuração háptica com um motor de vibração de massa rotativa excêntrica em cada alavanca de um gamepad padrão. Nessa configuração, ambos os motores podem vibrar o gamepad inteiro. As duas massas são desiguais para que os efeitos de cada uma possam ser combinados para criar efeitos táteis mais complexos. Os efeitos de vibração dupla são definidos por quatro parâmetros:
duration
: define a duração do efeito de vibração em milissegundos.startDelay
: define a duração do atraso até que a vibração seja iniciada.strongMagnitude
eweakMagnitude
: defina os níveis de intensidade da vibração para os motores de massa rotativa excêntrica mais pesados e mais leves, normalizados para o intervalo0.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,
});
};
Acionar vibração
O gatilho com vibração é o efeito de retorno tátil gerado por dois motores independentes, com um motor localizado em cada gatilho do gamepad.
// 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,
});
};
Integração com a Política de permissões
A especificação da API Gamepad define um
recurso controlado por política identificado pela
string "gamepad"
. O allowlist
padrão é "self"
. A política de permissões de um documento determina
se algum conteúdo nesse documento tem permissão para acessar navigator.getGamepads()
. Se desativado em
qualquer documento, nenhum conteúdo no documento poderá usar navigator.getGamepads()
, nem
os eventos gamepadconnected
e gamepaddisconnected
serão acionados.
<iframe src="index.html" allow="gamepad"></iframe>
Demonstração
Uma demonstração do testador de gamepad está incorporada no exemplo a seguir. O código-fonte está disponível no Glitch. Para testar a demonstração, conecte um gamepad usando USB ou Bluetooth e pressione qualquer botão ou mova qualquer eixo.
Bônus: jogue o dinossauro do Chrome no web.dev
Você pode jogar o Dinossauro do Chrome com seu gamepad neste
site. O código-fonte está disponível no GitHub.
Confira a implementação de pesquisa de gamepad em
trex-runner.js
e observe como ela emula pressionamentos de tecla.
Para que a demonstração do gamepad do dinossauro do Chrome funcione, removi o jogo do dinossauro do Chrome do projeto principal do Chromium (atualizei um esforço anterior do Arnelle Ballane), coloquei em um site independente, estendi a implementação da API do gamepad adicionando efeitos de abatimento e vibração, criei um modo de tela cheia e Mehul Satardekar contribuiu com uma implementação do modo escuro. Divirta-se!
Links úteis
Agradecimentos
Este documento foi revisado por François Beaufort e Joe Medley. A especificação da API Gamepad é editada por Steve Agoston, James Hollyer e Matt Reynolds. Os editores de especificações anteriores são Brandon Jones, Scott Graham e Ted Mielczarek. A especificação das extensões do Gamepad é editada por Brandon Jones. Imagem principal de Laura Torrent Puig.