Blocco del puntatore e controlli per sparatutto in prima persona

John McCutchan
John McCutchan

Introduzione

L'API Pointer Lock consente di implementare correttamente i controlli degli sparatutto in prima persona in un gioco in browser. Senza il movimento relativo del mouse, il cursore del giocatore potrebbe, ad esempio, colpire il bordo destro dello schermo e tutti gli ulteriori movimenti verso destra verrebbero ignorati: la visualizzazione non continuerebbe a scorrere verso destra e il giocatore non sarebbe in grado di inseguire i cattivi e mitragliarli con la sua mitragliatrice. Il giocatore verrà ucciso e si sentirà frustrato. Con il blocco del cursore, questo comportamento non ottimale non può verificarsi.

L'API Pointer Lock consente alla tua applicazione di:

  • Accedere ai dati non elaborati del mouse, inclusi i movimenti relativi del mouse
  • Inoltra tutti gli eventi del mouse a un elemento specifico

Un effetto collaterale dell'attivazione del blocco del cursore è che il cursore del mouse viene nascosto, consentendoti di scegliere se disegnare un cursore specifico per l'applicazione o lasciare nascosto il cursore del mouse in modo che l'utente possa spostare il riquadro con il mouse. Il movimento relativo del mouse è il delta della posizione del cursore del mouse rispetto al frame precedente, indipendentemente dalla posizione assoluta. Ad esempio, se il cursore del mouse si è spostato da (640, 480) a (520, 490), il movimento relativo è stato (-120, 10). Di seguito è riportato un esempio interattivo che mostra i delta non elaborati della posizione del mouse.

Questo tutorial tratta due argomenti: i dettagli dell'attivazione e dell'elaborazione degli eventi di blocco del cursore e l'implementazione dello schema di controllo degli sparatutto in prima persona. Esatto, al termine della lettura di questo articolo saprai come utilizzare il blocco del cursore e implementare i controlli in stile Quake per il tuo gioco in browser.

Compatibilità del browser

Supporto dei browser

  • Chrome: 37.
  • Edge: 13.
  • Firefox: 50.
  • Safari: 10.1.

Origine

Meccanica del blocco del cursore

Rilevamento di elementi

Per determinare se il browser dell'utente supporta il blocco del cursore, devi verificare la presenza di pointerLockElement o di una versione con prefisso del fornitore nell'oggetto documento. Nel codice:

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

Al momento il blocco del cursore è disponibile solo in Firefox e Chrome. Opera e IE non la supportano ancora.

In fase di attivazione

L'attivazione del blocco del cursore è una procedura in due passaggi. Innanzitutto, l'applicazione richiede l'attivazione del blocco del cursore per un elemento specifico e, immediatamente dopo che l'utente ha concesso l'autorizzazione, viene attivato un evento pointerlockchange. L'utente può annullare il blocco del cursore in qualsiasi momento premendo il tasto Esc. L'applicazione può anche uscire dal blocco del cursore in modo programmatico. Quando il blocco del cursore viene annullato, viene attivato 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();

Basta il codice riportato sopra. Quando il browser blocca il cursore, viene visualizzata una finestra popup che informa l'utente che la tua applicazione ha bloccato il cursore e gli indica che può annullare l'operazione premendo il tasto "Esc".

Barra delle informazioni di Blocco cursore in Chrome.
Barra delle informazioni di Blocco cursore in Chrome.

Gestione degli eventi

Esistono due eventi per i quali la tua applicazione deve aggiungere ascoltatori. Il primo è pointerlockchange, che viene attivato ogni volta che si verifica una modifica dello stato di blocco del cursore. Il secondo è mousemove, che viene attivato ogni volta che il mouse si sposta.

// 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);

All'interno del tuo callback pointerlockchange devi verificare se il cursore è appena stato bloccato o sbloccato. Determinare se il blocco del cursore è stato attivato è semplice: controlla se document.pointerLockElement è uguale all'elemento per cui è stato richiesto il blocco del cursore. In caso affermativo, l'applicazione ha bloccato correttamente il cursore. In caso contrario, il cursore è stato sbloccato dall'utente o dal tuo codice.

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 il blocco del cursore è attivo, clientX, clientY, screenX e screenY rimangono costanti. movementX e movementY vengono aggiornati con il numero di pixel che il cursore avrebbe spostato dall'invio dell'ultimo evento. In pseudocodice:

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

All'interno del callback mousemove, i dati relativi al movimento del mouse possono essere estratti dai campi movementX e movementY dell'evento.

function moveCallback(e) {
  var movementX = e.movementX ||
      e.mozMovementX          ||
      e.webkitMovementX       ||
      0,
  movementY = e.movementY ||
      e.mozMovementY      ||
      e.webkitMovementY   ||
      0;
}

Catturare gli errori

Se viene generato un errore durante l'attivazione o la disattivazione del blocco del cursore, viene attivato l'evento pointerlockerror. Nessun dato associato a questo evento.

document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);

È richiesta la modalità a schermo intero?

In origine, il blocco del cursore era associato all'API FullScreen. Ciò significa che un elemento deve essere in modalità a schermo intero prima che il cursore possa essere bloccato. Non è più così e il blocco del cursore può essere utilizzato per qualsiasi elemento dell'applicazione, a schermo intero o meno.

Esempio di controlli per sparatutto in prima persona

Ora che abbiamo attivato il blocco del cursore e stiamo ricevendo eventi, è il momento di un esempio pratico. Ti è mai capitato di chiederti come funzionano i controlli in Quake? Allacciati le cinture perché sto per spiegartelo con il codice.

I controlli degli sparatutto in prima persona si basano su quattro meccaniche di base:

  • Spostarsi avanti e indietro lungo il vettore di look corrente
  • Spostamento a sinistra e a destra lungo il vettore di strafing corrente
  • Ruotare la visualizzazione in yaw (a sinistra e a destra)
  • Ruotare l'inclinazione della visualizzazione (su e giù)

Un gioco che implementa questo schema di controllo richiede solo tre dati: la posizione della fotocamera, il vettore di sguardo della fotocamera e un vettore verso l'alto costante. Il vettore verso l'alto è sempre (0, 1, 0). Tutte e quattro le modalità sopra descritte manipolano la posizione della videocamera e il vettore di sguardo della videocamera in modi diversi.

Movimento

Per prima cosa, il movimento. Nella demo di seguito, i movimenti sono mappati alle chiavi W, A, S e D standard. I tasti W e S fanno avanzare e retrocedere la videocamera. mentre i tasti A e D muovono la videocamera verso sinistra e destra. Spostare la fotocamera in avanti e indietro è semplice:

// 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);

Per strafe a sinistra e a destra è necessaria una direzione. La direzione di strafing può essere calcolata utilizzando il prodotto vettoriale:

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

Una volta stabilita la direzione di strafing, l'implementazione del movimento di strafing è la stessa di un movimento in avanti o all'indietro.

Ora ruota la visualizzazione.

Scarto

Lo scarto o la rotazione orizzontale della visuale della fotocamera è solo una rotazione attorno al vettore costante verso l'alto. Di seguito è riportato il codice generale per ruotare il vettore di sguardo della fotocamera attorno a un asse arbitrario. Funziona costruendo un quaternione che rappresenta la rotazione di deltaAngle radianti attorno a axis e poi utilizza il quaternione per ruotare il vettore di sguardo della fotocamera:

// 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

L'implementazione dell'inclinazione o della rotazione verticale della visuale della fotocamera è simile, ma anziché una rotazione attorno al vettore verso l'alto, viene applicata una rotazione attorno al vettore di strafe. Il primo passaggio consiste nel calcolare il vettore di strafing e poi ruotare il vettore di sguardo della fotocamera attorno a quell'asse.

Riepilogo

L'API Pointer Lock ti consente di assumere il controllo del cursore del mouse. Se crei giochi web, i tuoi giocatori apprezzeranno molto quando non verranno più uccisi perché hanno spostato il mouse fuori dalla finestra e il tuo gioco ha smesso di ricevere aggiornamenti del mouse. L'utilizzo è semplice:

  • Aggiungi un listener di eventi pointerlockchange per monitorare lo stato del blocco del cursore
  • Richiedere il blocco del cursore per un elemento specifico
  • Aggiungi un listener di eventi mousemove per ricevere aggiornamenti

Demo esterne

Riferimenti