Introdução
A API PointerLock ajuda a implementar corretamente os controles de jogos de tiro em primeira pessoa em um jogo para navegador. Sem o movimento relativo do mouse, o cursor do jogador poderia, por exemplo, atingir a borda direita da tela, e qualquer movimento adicional para a direita seria descartado. A visualização não continuaria a ser movida para a direita, e o jogador não poderia perseguir os bandidos e atirar neles com a metralhadora. O jogador vai ser morto e ficar frustrado. Com o bloqueio do ponteiro, esse comportamento não ideal não acontece.
A API Pointer Lock permite que seu aplicativo faça o seguinte:
- Acessar dados brutos do mouse, incluindo movimentos relativos
- Encaminhar todos os eventos do mouse para um elemento específico
Como efeito colateral da ativação do bloqueio do ponteiro, o cursor do mouse fica oculto, permitindo que você escolha desenhar um ponteiro específico do aplicativo, se desejar, ou deixar o cursor oculto para que o usuário possa mover o frame com o mouse. O movimento relativo do mouse é a diferença da posição do ponteiro do mouse em relação ao frame anterior, independentemente da posição absoluta. Por exemplo, se o ponteiro do mouse se moveu de (640, 480) para (520, 490), o movimento relativo foi (-120, 10). Confira abaixo um exemplo interativo que mostra deltas de posição bruta do mouse.
Este tutorial aborda dois tópicos: os detalhes da ativação e do processamento de eventos de bloqueio do ponteiro e a implementação do esquema de controle de jogos de tiro em primeira pessoa. Isso mesmo, ao terminar de ler este artigo, você vai saber como usar o bloqueio do ponteiro e implementar controles no estilo Quake para seu próprio jogo no navegador.
Compatibilidade com navegadores
Mecânicas de bloqueio de ponteiro
Detecção de recursos
Para determinar se o navegador do usuário oferece suporte ao bloqueio do ponteiro, verifique se há pointerLockElement
ou uma versão com prefixo do fornecedor no objeto do documento. No código:
var havePointerLock = 'pointerLockElement' in document ||
'mozPointerLockElement' in document ||
'webkitPointerLockElement' in document;
No momento, o bloqueio do ponteiro está disponível apenas no Firefox e no Chrome. O Opera e o IE ainda não oferecem suporte a ele.
Ativando
Ativar o bloqueio do ponteiro é um processo de duas etapas. Primeiro, seu aplicativo solicita que o bloqueio do ponteiro seja ativado para um elemento específico. Imediatamente após o usuário conceder permissão, um evento pointerlockchange
é acionado. O usuário pode cancelar o bloqueio do ponteiro a qualquer momento pressionando a tecla de escape. O aplicativo também pode sair do bloqueio do ponteiro de forma programática. Quando o bloqueio do ponteiro é cancelado, um evento pointerlockchange
é acionado.
element.requestPointerLock = element.requestPointerLock ||
element.mozRequestPointerLock ||
element.webkitRequestPointerLock;
// Ask the browser to lock the pointer
element.requestPointerLock();
// Ask the browser to release the pointer
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
O código acima é tudo o que você precisa. Quando o navegador bloqueia o ponteiro, uma bolha aparece para informar ao usuário que o aplicativo bloqueou o ponteiro e instruindo-o a cancelar pressionando a tecla "Esc".
Tratamento de eventos
Há dois eventos para os quais o app precisa adicionar listeners. O primeiro é pointerlockchange
, que é acionado sempre que ocorre uma mudança no estado de bloqueio do ponteiro. O segundo é mousemove
, que é acionado sempre que o mouse é movido.
// Hook pointer lock state change events
document.addEventListener('pointerlockchange', changeCallback, false);
document.addEventListener('mozpointerlockchange', changeCallback, false);
document.addEventListener('webkitpointerlockchange', changeCallback, false);
// Hook mouse move events
document.addEventListener("mousemove", this.moveCallback, false);
No callback pointerlockchange
, você precisa verificar se o ponteiro foi bloqueado ou desbloqueado. Determinar se o bloqueio do ponteiro foi ativado é simples: verifique se document.pointerLockElement é igual ao elemento para o qual o bloqueio do ponteiro foi solicitado. Se estiver, o aplicativo bloqueou o ponteiro com sucesso. Caso contrário, o ponteiro foi desbloqueado pelo usuário ou pelo seu próprio código.
if (document.pointerLockElement === requestedElement ||
document.mozPointerLockElement === requestedElement ||
document.webkitPointerLockElement === requestedElement) {
// Pointer was just locked
// Enable the mousemove listener
document.addEventListener("mousemove", this.moveCallback, false);
} else {
// Pointer was just unlocked
// Disable the mousemove listener
document.removeEventListener("mousemove", this.moveCallback, false);
this.unlockHook(this.element);
}
Quando o bloqueio do ponteiro está ativado, clientX
, clientY
, screenX
e screenY
permanecem constantes. movementX
e movementY
são atualizados com o número de pixels que o ponteiro teria se movido desde o último evento. Em pseudocódigo:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
Dentro do callback mousemove
, os dados relativos ao movimento do mouse podem ser extraídos dos campos movementX
e movementY
do evento.
function moveCallback(e) {
var movementX = e.movementX ||
e.mozMovementX ||
e.webkitMovementX ||
0,
movementY = e.movementY ||
e.mozMovementY ||
e.webkitMovementY ||
0;
}
Como detectar erros
Se um erro for gerado ao entrar ou sair do bloqueio do ponteiro, o evento pointerlockerror
será acionado. Não há dados associados a este evento.
document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);
Tela cheia obrigatória?
Originalmente, o bloqueio do ponteiro estava vinculado à API FullScreen. Isso significa que um elemento precisa estar no modo de tela cheia antes que o ponteiro possa ser bloqueado nele. Isso não é mais verdade, e o bloqueio do ponteiro pode ser usado para qualquer elemento no seu aplicativo, em tela cheia ou não.
Exemplo de controles de jogo de tiro em primeira pessoa
Agora que o bloqueio do ponteiro está ativado e recebendo eventos, é hora de um exemplo prático. Você já quis saber como os controles do Quake funcionam? Prepare-se, porque vou explicar isso com código.
Os controles de jogos de tiro em primeira pessoa são criados com base em quatro mecânicas principais:
- Mover para frente e para trás ao longo do vetor de look atual
- Mover para a esquerda e direita ao longo do vetor de strafe atual
- Girar a vista (esquerda e direita)
- Como girar a inclinação da visualização (para cima e para baixo)
Um jogo que implementa esse esquema de controle precisa apenas de três dados: posição da câmera, vetor de visão da câmera e um vetor de cima constante. O vetor para cima é sempre (0, 1, 0). Todas as quatro mecânicas acima manipulam a posição e o vetor de visão da câmera de maneiras diferentes.
Movimento
O primeiro item da pauta é o movimento. Na demonstração abaixo, o movimento é mapeado para as teclas W, A, S e D padrão. As teclas W e S movem a câmera para frente e para trás. Já as teclas A e D movem a câmera para a esquerda e para a direita. Mover a câmera para frente e para trás é simples:
// Forward direction
var forwardDirection = vec3.create(cameraLookVector);
// Speed
var forwardSpeed = dt * cameraSpeed;
// Forward or backward depending on keys held
var forwardScale = 0.0;
forwardScale += keyState.W ? 1.0 : 0.0;
forwardScale -= keyState.S ? 1.0 : 0.0;
// Scale movement
vec3.scale(forwardDirection, forwardScale * forwardSpeed);
// Add scaled movement to camera position
vec3.add(cameraPosition, forwardDirection);
O movimento de strafar para a esquerda e para a direita requer uma direção de strafar. A direção de deslocamento lateral pode ser calculada usando o produto vetorial:
// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);
Depois de definir a direção do movimento de strafe, a implementação é a mesma para ir para frente ou para trás.
Agora vamos girar a visualização.
Guinada
A guinada ou a rotação horizontal da visualização da câmera é apenas uma rotação em torno do vetor de subida constante. Confira abaixo o código geral para girar o vetor de visão da câmera em torno de um eixo arbitrário. Ele funciona construindo um quaternion que representa a rotação de deltaAngle
radianos em torno de axis
e, em seguida, usa o quatérnion para girar o vetor de visão da câmera:
// Extract camera look vector
var frontDirection = vec3.create();
vec3.subtract(this.lookAtPoint, this.eyePoint, frontDirection);
vec3.normalize(frontDirection);
var q = quat4.create();
// Construct quaternion
quat4.fromAngleAxis(deltaAngle, axis, q);
// Rotate camera look vector
quat4.multiplyVec3(q, frontDirection);
// Update camera look vector
this.lookAtPoint = vec3.create(this.eyePoint);
vec3.add(this.lookAtPoint, frontDirection);
Apresentar
A implementação do pitch ou da rotação vertical da visualização da câmera é semelhante, mas, em vez de uma rotação em torno do vetor para cima, você aplica uma rotação em torno do vetor de strafe. A primeira etapa é calcular o vetor de deslocamento e girar o vetor de visão da câmera em torno desse eixo.
Resumo
A API Pointer Lock permite que você assuma o controle do cursor do mouse. Se você estiver criando jogos para a Web, seus jogadores vão adorar quando não forem mais mortos porque moveram o mouse para fora da janela e o jogo parou de receber atualizações do mouse. O uso é simples:
- Adicionar o listener de eventos
pointerlockchange
para rastrear o estado do bloqueio do ponteiro - Solicitar bloqueio do ponteiro para um elemento específico
- Adicionar um listener de eventos
mousemove
para receber atualizações