Aprenda a usar a API Gamepad para levar seus jogos da Web a um novo patamar.
O easter egg da página off-line do Chrome é um dos segredos mais guardados na história ([citation needed]
,
mas afirma que o 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 arcade. Você pode estar ciente de que
não precisa ficar off-line quando quiser jogar: no Chrome, basta navegar
até about://dino
ou, caso seja um nerd, navegue até about://network-error/-106
. Mas você sabia
que ocorrem
270 milhões de jogos do dinossauro do Chrome todos os meses?
![A página off-line do Chrome com o jogo do dinossauro.](https://web.dev/static/articles/gamepad/image/chromes-offline-page-th-3d966d4a2f411.png?authuser=6&hl=pt)
Outro fato que pode ser considerado mais útil e que você talvez não conheça é que, no modo arcade, é possível jogar com um gamepad. O suporte a Gamepad foi adicionado há aproximadamente um ano, no momento em que este artigo foi escrito, em uma confirmação de Reilly Grant (em inglês). Como você pode notar, o jogo, assim como o restante do projeto do 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 a navegadores
A API Gamepad tem um suporte a navegadores universalmente excelente em computadores e dispositivos móveis. Você pode detectar se há suporte para a API Gamepad usando o seguinte snippet:
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 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
: uma enumeração que define em qual mão o controle está sendo segurado ou provavelmente será segurado.timestamp
: a última vez em que os dados deste gamepad foram atualizados.mapping
: o mapeamento de botões e eixos em uso no dispositivo,"standard"
ou"xr-standard"
.pose
: um objetoGamepadPose
que representa as informações de pose associadas a um controle da 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 para 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 que os botões são relatados como objetos GamepadButton
, com estes atributos:
pressed
: o estado pressionado do botão (true
se o botão for pressionado efalse
se ele não for pressionado.touched
: o estado de toque do botão. Se o botão for capaz de detectar toques, essa propriedade serátrue
se estiver sendo tocado. Caso contrário, seráfalse
.value
: para botões que têm um sensor analógico, essa propriedade representa a quantidade de pressionamento do botão, que é linearmente normalizada no intervalo de0.0
a1.0
.hapticActuators
: uma matriz contendo objetosGamepadHapticActuator
, cada um representando o hardware de retorno tátil disponível no controlador.
Outra coisa que você pode encontrar, dependendo do seu navegador e do gamepad que você tem,
é uma propriedade vibrationActuator
. Ela permite dois tipos de efeitos de ronco:
- Dual-Rumble: o efeito de retorno tátil gerado por dois atuadores de massa giratórios excêntricos, um em cada mão 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 (link em inglês), mostra o mapeamento e a organizaçã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
acionado no
objeto window
. Quando o usuário conecta um gamepad, o que pode acontecer por USB ou Bluetooth,
é disparado um GamepadEvent
com os detalhes do gamepad em uma propriedade gamepad
adequadamente nomeada.
A seguir, você pode ver um exemplo de um controle Xbox 360 em que eu estava (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
Receber notificações de desconexões do gamepad acontece de maneira semelhante à forma como as conexões são detectadas.
Desta vez, o app detecta o evento gamepaddisconnected
. Observe como, no exemplo a seguir,
connected
agora é false
quando desconecta 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 seu 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
que quatro gamepads estiverem conectados, um item poderá ser apenas null
. Verifique sempre todos os itens da
matriz e esteja ciente de que os gamepads "se lembram" do slot e podem não estar sempre 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 a navigator.getGamepads()
ainda informar itens null
,
talvez seja necessário "ativar" cada gamepad pressionando qualquer um dos botões dele. É possível pesquisar os estados
do gamepad no loop de 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();
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 retorno tátil. Os efeitos táteis podem ser reproduzidos chamando Gamepad.vibrationActuator.playEffect()
. Os únicos
tipos de efeitos válidos são 'dual-rumble'
e 'trigger-rumble'
.
Efeitos de rumble com suporte
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.
}
Ronco duplo
O "Dual Rumble" descreve uma configuração tátil com um motor de vibração de massa giratório excêntrico em cada alça de um gamepad padrão. Nessa configuração, qualquer um dos motores é capaz de 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 Dual Rumble são definidos por quatro parâmetros:
duration
: define a duração do efeito de vibração em milissegundos.startDelay
: define o tempo de espera até que a vibração seja iniciada.strongMagnitude
eweakMagnitude
: definem os níveis de intensidade de vibração para os motores de massa 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 sombreamento
O ruído no gatilho é 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 desse documento tem permissão para acessar o navigator.getGamepads()
. Se desativado em
qualquer documento, nenhum conteúdo nele poderá usar navigator.getGamepads()
, e
os eventos gamepadconnected
e gamepaddisconnected
não serão disparados.
<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 (link em inglês). Teste a demonstração conectando um gamepad por USB ou Bluetooth e pressionando qualquer um dos botões ou movendo qualquer um dos eixos dele.
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 (link em inglês).
Confira a implementação da pesquisa do gamepad em
trex-runner.js
e observe como ela está emulando o pressionamento de teclas.
Para a demonstração do gamepad do dinossauro do Chrome funcionar, removi o jogo do dinossauro do Chrome do projeto principal do Chromium (atualizando uma esforço anterior de Arnelle Ballane), coloquei-o em um site independente, estendemos a implementação da API do gamepad atual adicionando efeitos de redução e vibração, criamos 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 foi 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. Imagem principal de Laura Torrent Puig.