Introdução
A API Pointer Lock ajuda a implementar corretamente controles de tiro em primeira pessoa em um jogo de navegador. Sem o movimento relativo do mouse, o cursor do jogador poderia, por exemplo, atingir a borda direita da tela e quaisquer outros movimentos para a direita seriam desconsiderados. A visão não continuaria se movendo para a direita, e o jogador não poderia perseguir os vilões e usá-los como metralhadoras. O jogador ficará frágil e frustrado. Com o bloqueio do ponteiro, esse comportamento abaixo do ideal não pode acontecer.
A API Pointer Lock permite que seu aplicativo faça o seguinte:
- Tenha acesso a dados brutos do mouse, incluindo os movimentos relativos do mouse
- Rotear todos os eventos do mouse para um elemento específico
Como efeito colateral da ativação do bloqueio de ponteiro, o cursor do mouse fica oculto, permitindo que você desenhe um ponteiro específico do aplicativo, se quiser, ou deixe o ponteiro do mouse oculto para que o usuário possa mover o frame com o mouse. O movimento relativo do mouse é o delta 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 foi movido de (640, 480) para (520, 490), o movimento relativo foi (-120, 10). Veja abaixo um exemplo interativo que mostra deltas brutos de posição do mouse.
Este tutorial abrange dois tópicos: os princípios básicos da ativação e do processamento de eventos de bloqueio de ponteiro e a implementação do esquema de controle de tiro em primeira pessoa. Isso mesmo, quando terminar de ler este artigo, você saberá como usar o bloqueio de ponteiro e implementar controles no estilo Quake para seu próprio jogo de navegador.
Compatibilidade com navegadores
Mecânica de bloqueio do ponteiro
Detecção de recursos
Para determinar se o navegador do usuário é compatível com o bloqueio de ponteiro, verifique se há pointerLockElement
ou uma versão com o 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 de ponteiro está disponível apenas no Firefox e no Chrome. O Opera e o IE ainda não são compatíveis com ela.
Ativando
A ativação do bloqueio do ponteiro é um processo de duas etapas. Primeiro, o aplicativo solicita que o bloqueio do ponteiro seja ativado para um elemento específico e, logo após o usuário conceder a permissão, um evento pointerlockchange
é disparado. O usuário pode cancelar o bloqueio do ponteiro a qualquer momento pressionando a tecla Esc. Seu aplicativo também pode sair de forma proativa do bloqueio do ponteiro. Quando o bloqueio do ponteiro é cancelado, um evento pointerlockchange
é disparado.
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();
Basta usar o código acima. Quando o navegador bloqueia o ponteiro, um balão aparece informando ao usuário que o aplicativo bloqueou o ponteiro e informando que ele pode cancelá-lo pressionando a tecla "Esc" de dados.
Tratamento de eventos
Há dois eventos para os quais seu aplicativo precisa adicionar listeners. O primeiro é pointerlockchange
, que é disparado sempre que ocorre uma mudança no estado de bloqueio do ponteiro. O segundo é mousemove
, que é acionado sempre que o mouse se move.
// 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
, é necessário verificar se o ponteiro acabou de ser bloqueado ou desbloqueado. Determinar se o bloqueio de ponteiro foi ativado é simples: verifique se document.pointerLockElement é igual ao elemento para o qual o bloqueio de ponteiro foi solicitado. Se estiver, o aplicativo bloqueou o ponteiro e, se não estiver, ele 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 de 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 movido desde que o último evento foi enviado. Em pseudocódigo:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
Dentro do callback mousemove
, é possível extrair dados de movimento relativo do mouse 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;
}
Detecção de erros
Se um erro for gerado ao entrar ou sair do bloqueio de ponteiro, o evento pointerlockerror
será disparado. Não há dados anexados a este evento.
document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);
O modo de tela cheia é obrigatório?
Originalmente, o bloqueio de ponteiro estava vinculado à API FullScreen. Isso significa que um elemento precisa estar no modo de tela cheia antes de bloquear o ponteiro. Isso não é mais verdadeiro, e o bloqueio de ponteiro pode ser usado para qualquer elemento no aplicativo em tela cheia ou não.
Exemplo de controles para tiro em primeira pessoa
Agora que o bloqueio de ponteiro está ativado e recebendo eventos, é hora de um exemplo prático. Você já quis saber como funcionam os controles do Quake? Aperte o cinto porque vou explicar tudo com código!
Os controles de tiro em primeira pessoa têm quatro mecânicas básicas:
- Como avançar e voltar no vetor de visualização atual
- Movendo para a esquerda e para a direita ao longo do vetor de estratificação atual
- Girar a guinada da visualização (para a esquerda e para a direita)
- Girar a inclinação da visualização (para cima e para baixo)
Um jogo que implementa esse esquema de controle precisa de apenas três dados: a posição da câmera, o vetor de aparência da câmera e um vetor de aumento constante. O vetor superior é sempre (0, 1, 0). As quatro mecânicas acima manipulam a posição e o vetor de aparência da câmera de maneiras diferentes.
Movimento
O primeiro ponto é o movimento. Na demonstração abaixo, o movimento é mapeado para as teclas padrão W, A, S e D. As teclas W e S levam a câmera para frente e para trás. Enquanto as teclas A e D movem a câmera para a esquerda e para a direita. É simples mover a câmera para frente e para trás:
// 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 estiramento à esquerda e à direita exige uma direção de alongamento. A direção do estrafe pode ser calculada usando vários produtos:
// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);
Depois de definir a direção de estrangulamento, implementar o movimento de estrafe é o mesmo que avançar ou retroceder.
A próxima etapa é girar a visualização.
Yaw
A curva ou rotação horizontal da visualização da câmera é apenas uma rotação em torno do vetor superior constante. Veja abaixo um código geral para girar o vetor de aparência da câmera em torno de um eixo arbitrário. Ela cria um quatérnio que representa a rotação de deltaAngle
radianos em torno de axis
e usa o quatérnio para girar o vetor de aparência 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 da inclinação 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 largura. A primeira etapa é calcular o vetor de estratifa e, em seguida, girar o vetor de aparência da câmera em torno desse eixo.
Resumo
A API Pointer Lock permite controlar o cursor do mouse. Se você cria jogos na Web, seus jogadores vão adorar quando pararem de ser perfumados, porque tiraram o mouse da janela com entusiasmo e o jogo parou de receber atualizações. O uso é simples:
- Adicionar o listener de eventos
pointerlockchange
para rastrear o estado do bloqueio do ponteiro - Solicitar o bloqueio de ponteiro para um elemento específico
- Adicione o listener de eventos
mousemove
para receber atualizações
Demonstrações externas
Referências
- Documentação da API Mozilla Developer Network (em inglês)