Bloqueo del puntero y controles de disparos en primera persona

Introducción

La API de Pointer Lock ayuda a implementar correctamente los controles 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 cualquier movimiento que se realizara hacia la derecha se descontaría. La vista no seguiría desplazándose hacia la derecha y el jugador no podría perseguir a los malos ni atravesarlos con su ametralladora. El jugador se va a desordenar y frustrar. Con el bloqueo del puntero, no se puede producir este comportamiento subóptimo.

La API de Pointer Lock permite que tu aplicación haga lo siguiente:

  • Obtén acceso a los datos sin procesar del mouse, incluidos los movimientos relativos del mouse
  • Enrutar todos los eventos del mouse a un elemento específico

Como efecto secundario de la habilitación del bloqueo del puntero, el cursor del mouse está oculto, lo que te permite elegir dibujar un puntero específico de la aplicación si lo deseas o dejar el puntero del mouse oculto para que el usuario pueda mover el marco con él. El movimiento relativo del mouse es el delta de la posición del puntero del mouse con respecto al 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, puedes ver un ejemplo interactivo que muestra deltas de posición del mouse sin procesar.

En este instructivo, se abarcan dos temas: los aspectos básicos de la activación y el procesamiento de eventos de bloqueo del puntero y la implementación del esquema de control de los disparos en primera persona. Así es. Cuando termines de leer este artículo, sabrás cómo usar el bloqueo del puntero e implementar controles al estilo Quake en tu propio juego de navegador.

Compatibilidad del navegador

Navegadores compatibles

  • Chrome: 37.
  • Límite: 13.
  • Firefox: 50.
  • Safari: 10.1.

Origen

Mecánica del bloqueo del puntero

Detección de atributos

Para determinar si el navegador del usuario admite el bloqueo del puntero, debes verificar pointerLockElement o una versión con el prefijo del proveedor en el objeto del documento. En el código:

var havePointerLock = 'pointerLockElement' in document ||
    'mozPointerLockElement' in document ||
    'webkitPointerLockElement' in document;

Por el momento, el bloqueo del puntero solo está disponible en Firefox y Chrome. Opera e 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 el 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 de manera programática del bloqueo del puntero. 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();

Usa el código anterior. Cuando el navegador bloquea el puntero, aparece un cuadro emergente que le informa al usuario que tu aplicación bloqueó el puntero y le indica que puede cancelarlo presionando la tecla "Esc". .

Barra de información de bloqueo del puntero en Chrome.
Barra de información del bloqueo del puntero en Chrome.

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 pointerlockchange, debes verificar si se bloqueó o desbloqueó el puntero. 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. De lo contrario, el usuario o tu propio código desbloqueó el puntero.

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 el bloqueo del puntero está habilitado, clientX, clientY, screenX y screenY permanecen constantes. movementX y movementY se actualizan con la cantidad de píxeles que habría movido el puntero desde que se entregó el último evento. En seudocódigo:

event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;

Dentro de los datos de movimiento relativo del mouse de la devolución de llamada mousemove, 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;
}

Detección de errores

Si se produce un error cuando se ingresa o se sale 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);

¿El modo de pantalla completa es obligatorio?

Originalmente, el bloqueo del puntero estaba vinculado a la API de FullScreen. Esto significa que un elemento debe estar en el modo de pantalla completa antes de poder tener el puntero bloqueado. Esto ya no es verdadero, y el bloqueo del puntero se puede usar para cualquier elemento de la aplicación en pantalla completa o no.

Ejemplo de controles de un juego de disparos en primera persona

Ahora que habilitamos el bloqueo del puntero y recibimos eventos, es momento 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 disparos en primera persona se basan en cuatro mecánicas centrales:

  • Desplazarse hacia delante y hacia atrás en el vector de vista actual
  • Movimiento hacia la izquierda y la derecha a lo largo del vector strafe actual
  • Cómo rotar la guiñada de la vista (izquierda y derecha)
  • Rotar la inclinación de la vista (hacia arriba y hacia 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 y el vector de aspecto de la cámara de diferentes maneras.

Movimiento

Lo primero en la presentación es el movimiento. En la siguiente demostración, el movimiento se asigna a las teclas W, A, S y D estándar. Las teclas W y S impulsan la cámara hacia delante y hacia atrás. Mientras que las teclas A y D conducen la cámara a la izquierda y a la derecha. Mover la cámara hacia delante 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);

La orientación de apertura hacia la izquierda y la derecha requiere una dirección de estragos. La dirección estrafal se puede calcular mediante el producto cruzado:

// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);

Una vez que se tiene la dirección correcta, implementar el movimiento de estrafa es lo mismo que avanzar o retroceder.

Lo siguiente es rotar la vista.

Yaw

La guiñada, o la rotación horizontal de la vista de la cámara, es solo una rotación alrededor del vector de arriba constante. A continuación, se muestra un código general para rotar el vector de aspecto 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 aspecto 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 de la inclinación 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, se aplica una rotación alrededor del vector de banda ancha. 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 controlar el cursor del mouse. Si creas juegos web, a tus jugadores les encantará que dejen de funcionar porque han movido 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 el objeto de escucha de eventos mousemove para obtener actualizaciones

Demostraciones externas

Referencias