Saiba como usar a API Gamepad para turbinar seus jogos de navegador.
O easter egg da página off-line do Chrome é um dos segredos mais mal guardados da história ([citation needed],
mas a afirmação foi feita para dar um efeito dramático). Se você pressionar a tecla de espaço ou, em dispositivos móveis, tocar no dinossauro, a página off-line se tornará um jogo de fliperama. Você já deve saber que
não precisa ficar off-line para jogar: no Chrome, basta acessar
about://dino ou, se você for mais geek, about://network-error/-106. Mas você sabia
que o jogo do dinossauro do Chrome
é jogado 270 milhões de vezes por 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 ao 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. Nesta postagem, quero mostrar como usar a API Gamepad.
Usar a API Gamepad
Detecção de recursos e suporte do navegador
A API Gamepad tem um ótimo suporte de navegador universal em computadores e dispositivos móveis. É possível detectar se a API Gamepad é compatível usando o seguinte snippet:
if ('getGamepads' in navigator) {
// The API is supported!
}
Como o navegador representa um gamepad
O navegador representa os gamepads como objetos
Gamepad. Um Gamepad tem as seguintes propriedades:
id: uma string de identificação do gamepad. Essa string identifica a marca ou o estilo do dispositivo de gamepad conectado.displayId: oVRDisplay.displayIdde umVRDisplayassociado (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 é mais provável que esteja.timestamp: a última vez que os dados desse gamepad foram atualizados.mapping: o mapeamento de botões e eixos em uso para esse dispositivo, que pode ser"standard"ou"xr-standard".pose: um objetoGamepadPoseque representa as informações de pose associadas a um controlador WebVR.axes: uma matriz de valores para todos os eixos do gamepad, normalizados linearmente para o intervalo de-1.0–1.0.buttons: uma matriz de estados de todos os botões do gamepad.
Os botões podem ser digitais (pressionados ou não) 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 (truese o botão estiver pressionado efalsese não estiver).touched: o estado tocado do botão. Se o botão for capaz de detectar toque, essa propriedade serátruese o botão estiver sendo tocado efalsecaso contrário.value: para botões que têm um sensor analógico, essa propriedade representa o quanto o botão foi pressionado, normalizado linearmente para o intervalo de0.0a1.0.hapticActuators: uma matriz que contém objetosGamepadHapticActuator, cada um representando o hardware de retorno tátil disponível no controle.
Dependendo do navegador e do gamepad que você tem,
pode ser que você encontre 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êntrica, 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 um dos gatilhos do gamepad.
A visão geral esquemática a seguir, retirada 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 por USB ou Bluetooth,
um GamepadEvent é disparado com os detalhes do gamepad em uma propriedade gamepad com um nome apropriado.
A seguir, você pode ver um exemplo de um controle do Xbox 360 que eu tinha por aí (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ões de gamepad acontece de forma análoga à detecção de conexões.
Desta vez, o app detecta o evento gamepaddisconnected. No exemplo a seguir,
connected agora é false quando 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
Para acessar um gamepad, comece com uma chamada para navigator.getGamepads(), que retorna uma matriz
com itens Gamepad. A matriz no Chrome sempre tem um tamanho fixo de quatro itens. Se zero ou menos
de quatro gamepads estiverem conectados, um item poderá ser apenas null. Sempre verifique todos os itens da
matriz e lembre-se de que os gamepads "memorizam" o 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 navigator.getGamepads() ainda informar null itens,
talvez seja necessário "ativar" cada gamepad pressionando qualquer um dos botões. Em seguida, você pode consultar os estados do gamepad
no loop de jogo, conforme mostrado no código a seguir.
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();
O atuador de vibração
A propriedade vibrationActuator retorna um objeto GamepadHapticActuator, que corresponde a uma
configuração de motores ou outros atuadores que podem aplicar uma força para fins de resposta
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 termo "vibração dupla" descreve uma configuração tátil com um motor de vibração de massa rotativa excêntrica em cada alça de um gamepad padrão. Nessa configuração, qualquer um dos motores pode vibrar todo o gamepad. 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.strongMagnitudeeweakMagnitude: defina os níveis de intensidade de 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,
});
};
Vibração do gatilho
A vibração do gatilho é o efeito de retorno tátil gerado por dois motores independentes, com um motor localizado em cada um dos gatilhos 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 qualquer conteúdo nele pode acessar navigator.getGamepads(). Se desativado em
qualquer documento, nenhum conteúdo no documento poderá usar navigator.getGamepads(), e os eventos
gamepadconnected e gamepaddisconnected não 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 GitHub. Teste a demonstração conectando um gamepad por USB ou Bluetooth e pressionando qualquer um dos botões ou movendo qualquer um dos eixos.
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 da sondagem do gamepad em
trex-runner.js
e observe como ela emula as teclas pressionadas.
Para que a demonstração do gamepad do Dinossauro do Chrome funcione, extraí o jogo do Dinossauro do Chrome do projeto principal do Chromium (atualizando um esforço anterior de Arnelle Ballane), o coloquei em um site independente, estendi a implementação atual da API Gamepad adicionando efeitos de agachamento e vibração, criei um modo de tela cheia e Mehul Satardekar contribuiu com uma implementação do modo escuro. Bom jogo!
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 antigos editores de especificações são Brandon Jones, Scott Graham e Ted Mielczarek. A especificação das extensões do gamepad é editada por Brandon Jones.