Introducción
La API de Pointer Lock ayuda a implementar correctamente los controles de los juegos de disparos en primera persona en un juego de navegador. Sin el movimiento relativo del mouse, el cursor del jugador podría, por ejemplo, llegar al borde derecho de la pantalla y se descontarían los demás movimientos hacia la derecha; la vista no seguiría desplazándose hacia la derecha y el jugador no podría perseguir a los malos y dispararles con su ametralladora. El jugador se frustrará y se sentirá frustrado. Con el bloqueo del puntero, no puede ocurrir este comportamiento subóptimo.
La API de bloqueo del puntero permite que tu aplicación haga lo siguiente:
- Obtén acceso a los datos sin procesar del mouse, incluidos los movimientos relativos del mouse
- Envía todos los eventos del mouse a un elemento específico
Como efecto secundario de habilitar el bloqueo del puntero, se oculta el cursor del mouse, lo que te permite dibujar un puntero específico de la aplicación si lo deseas o dejar oculto el puntero del mouse para que el usuario pueda mover el marco con el mouse. El movimiento relativo del mouse es la delta de la posición del puntero del mouse desde el fotograma anterior, independientemente de la posición absoluta. Por ejemplo, si el puntero del mouse se movió de (640, 480) a (520, 490), el movimiento relativo fue (-120, 10). A continuación, se muestra un ejemplo interactivo que muestra las deltas de posición del mouse sin procesar.
En este instructivo, se abordan dos temas: los aspectos básicos para activar y procesar eventos de bloqueo del puntero, y la implementación del esquema de control de los juegos de disparos en primera persona. Así es, cuando termines de leer este artículo, sabrás cómo usar el bloqueo del puntero y cómo implementar controles al estilo Quake para tu propio juego de navegador.
Compatibilidad del navegador
Mecánica del bloqueo del puntero
Detección de atributos
Para determinar si el navegador del usuario admite el bloqueo del puntero, debes buscar pointerLockElement
o una versión con prefijo del proveedor en el objeto de documento. En código:
var havePointerLock = 'pointerLockElement' in document ||
'mozPointerLockElement' in document ||
'webkitPointerLockElement' in document;
Actualmente, el bloqueo del puntero solo está disponible en Firefox y Chrome. Opera y IE aún no lo admiten.
Activando
La activación del bloqueo del puntero es un proceso de dos pasos. Primero, tu aplicación solicita que se habilite el bloqueo del puntero para un elemento específico y, inmediatamente después de que el usuario otorga permiso, se activa un evento pointerlockchange
. El usuario puede cancelar el bloqueo del puntero en cualquier momento presionando la tecla Escape. Tu aplicación también puede salir del bloqueo del puntero de forma programática. Cuando se cancela el bloqueo del puntero, se activa un evento pointerlockchange
.
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();
Solo necesitas el código anterior. Cuando el navegador bloquee el puntero, aparecerá una burbuja para informarle al usuario que tu aplicación lo bloqueó y que puede cancelarlo presionando la tecla "Esc".
Control de eventos
Hay dos eventos para los que tu aplicación debe agregar objetos de escucha. El primero es pointerlockchange
, que se activa cada vez que se produce un cambio en el estado de bloqueo del puntero. El segundo es mousemove
, que se activa cada vez que se mueve el mouse.
// 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);
Dentro de la devolución de llamada de pointerlockchange
, debes verificar si el puntero se acaba de trabar o destrabar. Determinar si se habilitó el bloqueo del puntero es sencillo: comprueba si document.pointerLockElement es igual al elemento para el que se solicitó el bloqueo del puntero. Si es así, tu aplicación bloqueó correctamente el puntero y, si no es así, el usuario o tu propio código lo desbloqueó.
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);
}
Cuando se habilita el bloqueo del puntero, clientX
, clientY
, screenX
y screenY
permanecen constantes. movementX
y movementY
se actualizan con la cantidad de píxeles que el puntero habría movido desde que se entregó el último evento. En seudocódigo:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
Dentro de la devolución de llamada mousemove
, los datos de movimiento del mouse relativos se pueden extraer de los campos movementX
y movementY
del evento.
function moveCallback(e) {
var movementX = e.movementX ||
e.mozMovementX ||
e.webkitMovementX ||
0,
movementY = e.movementY ||
e.mozMovementY ||
e.webkitMovementY ||
0;
}
Cómo detectar errores
Si se produce un error al ingresar o salir del bloqueo del puntero, se activa el evento pointerlockerror
. No hay datos adjuntos a este evento.
document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);
¿Se requiere pantalla completa?
Originalmente, el bloqueo del puntero estaba vinculado a la API de FullScreen. Esto significa que un elemento debe estar en modo de pantalla completa para que el puntero pueda bloquearse en él. Eso ya no es cierto, y el bloqueo del puntero se puede usar para cualquier elemento de tu aplicación, ya sea en pantalla completa o no.
Ejemplo de controles de un juego de disparos en primera persona
Ahora que tenemos habilitado el bloqueo del puntero y recibimos eventos, es hora de ver un ejemplo práctico. ¿Alguna vez quisiste saber cómo funcionan los controles de Quake? Abróchate el cinturón porque voy a explicarlos con código.
Los controles de los juegos de disparos en primera persona se basan en cuatro mecánicas principales:
- Avanzar y retroceder a lo largo del vector de vista actual
- Se mueve hacia la izquierda y la derecha a lo largo del vector de desplazamiento actual.
- Rotación del balanceo de la vista (izquierda y derecha)
- Rotación del ángulo de visión (hacia arriba y abajo)
Un juego que implemente este esquema de control solo necesita tres datos: la posición de la cámara, el vector de vista de la cámara y un vector hacia arriba constante. El vector hacia arriba siempre es (0, 1, 0). Las cuatro mecánicas anteriores solo manipulan la posición de la cámara y el vector de vista de la cámara de diferentes maneras.
Movimiento
Lo primero es el movimiento. En la siguiente demostración, el movimiento se asigna a las teclas estándar W, A, S y D. Las teclas W y S mueven la cámara hacia delante y hacia atrás. Mientras que las teclas A y D mueven la cámara hacia la izquierda y la derecha. Mover la cámara hacia adelante y hacia atrás es sencillo:
// 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);
El desplazamiento lateral hacia la izquierda y la derecha requiere una dirección de desplazamiento lateral. La dirección de desplazamiento lateral se puede calcular con el producto cruzado:
// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);
Una vez que tengas la dirección de desplazamiento lateral, implementar el movimiento lateral es lo mismo que avanzar o retroceder.
A continuación, rotaremos la vista.
Guiñada
El balanceo o la rotación horizontal de la vista de la cámara es solo una rotación alrededor del vector ascendente constante. A continuación, se muestra el código general para rotar el vector de vista de la cámara alrededor de un eje arbitrario. Funciona construyendo un cuaternión que representa la rotación de deltaAngle
radianes alrededor de axis
y, luego, usa el cuaternión para rotar el vector de vista de la cámara:
// 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);
Tono
La implementación del tono o la rotación vertical de la vista de la cámara es similar, pero en lugar de una rotación alrededor del vector hacia arriba, aplicas una rotación alrededor del vector de desplazamiento lateral. El primer paso es calcular el vector de desplazamiento lateral y, luego, rotar el vector de vista de la cámara alrededor de ese eje.
Resumen
La API de Pointer Lock te permite tomar el control del cursor del mouse. Si creas juegos web, a los jugadores les encantará dejar de morir porque movieron el mouse fuera de la ventana con entusiasmo y el juego dejó de recibir actualizaciones del mouse. El uso es sencillo:
- Agrega un objeto de escucha de eventos
pointerlockchange
para hacer un seguimiento del estado del bloqueo del puntero - Cómo solicitar el bloqueo del puntero para un elemento específico
- Agrega un objeto de escucha de eventos
mousemove
para obtener actualizaciones