Introdução
A API Pointer Lock ajuda a implementar corretamente os controles 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 movimentos adicionais para a direita seriam descontados. A visualização não continuaria se movendo para a direita e o jogador não seria capaz de perseguir os criminosos nem tropeçar neles com sua metralhadora. O jogador vai ficar cansado e frustrado. Com o bloqueio de ponteiro, esse comportamento abaixo do ideal não pode acontecer.
A API Pointer Lock permite que o aplicativo faça o seguinte:
- Ter acesso a dados brutos do mouse, incluindo movimentos relativos do mouse
- Encaminhar todos os eventos do mouse para um elemento específico
Como efeito colateral de ativar o bloqueio do ponteiro, o cursor do mouse fica oculto, permitindo que você escolha desenhar 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 passar de (640, 480) para (520, 490), o movimento relativo será (-120, 10). Veja abaixo um exemplo interativo que mostra deltas brutos de posição do mouse.
Este tutorial aborda dois tópicos: os detalhes de ativar e processar 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ê vai saber como usar o bloqueio com ponteiro e implementar controles estilo Quake no seu próprio jogo de navegador.
Compatibilidade com navegadores
Mecânica do Pointer Lock
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. Opera e IE ainda não são compatíveis com ele.
Ativando
A ativação do bloqueio de ponteiro é um processo de duas etapas. Primeiro, seu aplicativo solicita que o bloqueio de ponteiro para um elemento específico e, imediatamente após o usuário conceder a permissão, um evento pointerlockchange
seja disparado. O usuário pode cancelar o bloqueio de ponteiro a qualquer momento pressionando a tecla Esc. O app também pode sair progrmaticamente do bloqueio do ponteiro. Quando o bloqueio de 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();
O código acima é tudo o que você precisa. Quando o navegador bloquear o ponteiro, um balão será exibido, informando ao usuário que seu aplicativo bloqueou o ponteiro e informando que é possível cancelá-lo pressionando a tecla "Esc".
Manipulação de eventos
Há dois eventos para os quais o aplicativo precisa adicionar listeners. A primeira é pointerlockchange
, que é disparada 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
, verifique 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 for, seu aplicativo bloqueou o ponteiro e, se não estiver, o ponteiro foi desbloqueado pelo usuário ou 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 entregue. Em pseudocódigo:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
Dentro do callback mousemove
, os dados relacionados 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 pela entrada ou saída 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);
Tela cheia necessária?
Originalmente, o bloqueio do ponteiro estava vinculado à API FullScreen. Um elemento precisa estar no modo de tela cheia para que o cursor possa ser bloqueado. Isso não é mais verdade, e o bloqueio do ponteiro pode ser usado para qualquer elemento do aplicativo em tela cheia ou não.
Exemplo de controles de tiro em primeira pessoa
Agora que o bloqueio de ponteiro está ativado e recebe eventos, é hora de conferir um exemplo prático. Você já quis saber como funcionam os controles do Quake? Aperte o cinto, porque vou explicar com código!
Os controles de tiro em primeira pessoa são criados em torno de quatro mecânicas principais:
- Como avançar e retroceder ao longo do vetor de aparência atual
- Como mover para a esquerda e para a direita ao longo do vetor de movimentação atual
- Girar a guinada da visualização (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 de apenas três dados: a posição da câmera, o vetor de aparência da câmera e um vetor de up constante. O vetor superior é sempre (0, 1, 0). As quatro mecânicas acima apenas manipulam a posição e o vetor de aparência da câmera de maneiras diferentes.
Movimento
A primeira coisa na apresentação é o movimento. Na demonstração abaixo, o movimento está associado às teclas W, A, S e D padrão. As teclas W e S levam a câmera para frente e para trás. Enquanto as teclas A e D orientam a câmera para a esquerda e para a direita. É muito 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 uso da esquerda e da direita requer uma direção de strafe. A direção do strafe pode ser calculada usando o produto cruzado:
// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);
Depois de ter a direção da strafe, implementar o movimento da strafe é o mesmo que mover para frente ou para trás.
O próximo passo é girar a visualização.
Guinada
A declinação, ou rotação horizontal da visualização da câmera, é apenas uma rotação em torno do vetor para cima constante. Confira abaixo o código geral para girar o vetor de aparência da câmera em torno de um eixo arbitrário. Ela construa um quatérnio que representa a rotação de deltaAngle
radianos em torno de axis
e, em seguida, 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);
Faça sugestões
A implementação da inclinação ou rotação vertical da visualização da câmera é semelhante. No entanto, 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 dispersão e, em seguida, girar o vetor de aparência da câmera em torno desse eixo.
Resumo
A API Pointer Lock permite que você controle o cursor do mouse. Ao criar jogos para a Web, seus jogadores vão adorar quando pararem de ficar fragmentados porque moveram o mouse para fora da janela e o jogo parou de receber atualizações. O uso é simples:
- O listener de eventos
pointerlockchange
foi adicionado para rastrear o estado do bloqueio do ponteiro. - Solicitar o bloqueio do ponteiro para um elemento específico
- Adicionar o listener de eventos
mousemove
para receber atualizações
Demonstrações externas
Referências
- Documentação da API Mozilla Developer Network (em inglês)