Verrouillage du pointeur et commandes de tir à la première personne

John McCutchan
John McCutchan

Introduction

L'API Pointer Lock permet d'implémenter correctement les commandes de tir à la première personne dans un jeu par navigateur. Sans mouvement relatif de la souris, le curseur du joueur pourrait, par exemple, toucher le bord droit de l'écran et tout autre mouvement vers la droite serait réduit. La vue ne continuerait alors pas à faire un panoramique vers la droite, et le joueur ne serait pas en mesure de poursuivre les méchants et de les écraser avec sa mitrailleuse. Le joueur va devenir frustré ou frustré. Avec le verrouillage du pointeur, ce comportement non optimal ne peut pas se produire.

L'API Pointer Lock permet à votre application d'effectuer les opérations suivantes:

  • Accéder aux données brutes de la souris, y compris les mouvements relatifs de la souris
  • Acheminer tous les événements de souris vers un élément spécifique

Lorsque vous activez le verrouillage du pointeur, le curseur de la souris est masqué, ce qui vous permet de dessiner un pointeur spécifique à l'application si vous le souhaitez ou de le laisser masqué afin que l'utilisateur puisse déplacer le cadre avec la souris. Les mouvements relatifs de la souris correspondent au delta de la position du pointeur de la souris par rapport à l'image précédente, quelle que soit la position absolue. Par exemple, si le pointeur de la souris passe de (640, 480) à (520, 490), le mouvement relatif était (-120, 10). Vous trouverez ci-dessous un exemple interactif présentant les deltas bruts de position de la souris.

Ce tutoriel aborde deux sujets: les rouages de l'activation et du traitement des événements de verrouillage du pointeur, et l'implémentation du schéma de contrôle du jeu de tir à la première personne. Lorsque vous aurez fini de lire cet article, vous saurez comment utiliser le verrouillage du pointeur et implémenter des commandes de type Quake pour votre propre jeu sur navigateur.

Compatibilité du navigateur

Navigateurs pris en charge

  • 37
  • 13
  • 50
  • 10.1

Source

Mécanismes de verrouillage du pointeur

Détection de caractéristiques

Pour déterminer si le navigateur de l'utilisateur est compatible avec le verrouillage du pointeur, vous devez rechercher pointerLockElement ou une version avec préfixe du fournisseur dans l'objet Document. Dans le code:

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

Pour le moment, le verrouillage du pointeur n'est disponible que dans Firefox et Chrome. Opera et Internet Explorer ne les prennent pas encore en charge.

Activation

L'activation du verrouillage du pointeur s'effectue en deux étapes. Votre application demande d'abord l'activation du verrouillage du pointeur pour un élément spécifique. Dès que l'utilisateur donne son autorisation, un événement pointerlockchange se déclenche. L'utilisateur peut annuler le verrouillage du pointeur à tout moment en appuyant sur la touche Échap. Votre application peut également quitter le verrouillage du pointeur de manière progressive. Lorsque le verrouillage du pointeur est annulé, un événement pointerlockchange se déclenche.

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

Le code ci-dessus suffit. Lorsque le navigateur verrouille le pointeur, une info-bulle s'affiche pour informer l'utilisateur que votre application a verrouillé le pointeur et lui demander de l'annuler en appuyant sur la touche "Échap".

Barre d'informations de verrouillage du pointeur dans Chrome.
Barre d'informations du verrouillage de pointeur dans Chrome

Gestion des événements

Votre application doit ajouter des écouteurs pour deux événements. La première est pointerlockchange, qui se déclenche chaque fois qu'un changement d'état de verrouillage du pointeur se produit. La seconde est mousemove, qui se déclenche chaque fois que la souris bouge.

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

Dans votre rappel pointerlockchange, vous devez vérifier si le pointeur vient d'être verrouillé ou déverrouillé. Déterminer si le verrouillage du pointeur a été activé est simple: vérifiez si document.pointerLockElement est égal à l'élément pour lequel le verrouillage du pointeur a été demandé. Si c'est le cas, votre application a bien verrouillé le pointeur et, si ce n'est pas le cas, il a été déverrouillé par l'utilisateur ou par votre propre code.

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

Lorsque le verrouillage du pointeur est activé, clientX, clientY, screenX et screenY restent constants. movementX et movementY sont mis à jour avec le nombre de pixels que le pointeur aurait pu déplacer depuis la diffusion du dernier événement. En pseudo-code :

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

Dans le rappel mousemove, les données de mouvement relatif de la souris peuvent être extraites des champs movementX et movementY de l'événement.

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

Détecter les erreurs

Si une erreur est générée lors de l'entrée ou de la sortie du verrou de pointeur, l'événement pointerlockerror se déclenche. Aucune donnée n'est associée à cet événement.

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

Plein écran requis ?

À l'origine, le verrouillage du pointeur était lié à l'API FullScreen. Cela signifie qu'un élément doit être en mode plein écran pour que le pointeur puisse être verrouillé. Ce n'est plus vrai, et le verrouillage du pointeur peut être utilisé ou non en plein écran pour n'importe quel élément de votre application.

Exemple de commandes de tir à la première personne

Maintenant que le verrouillage du pointeur est activé et que nous recevons des événements, il est temps de passer à un exemple pratique. Avez-vous déjà rêvé de savoir comment fonctionnent les commandes de Quake ? Je m'apprête à les expliquer en codant !

Les commandes de tir à la première personne reposent sur quatre mécanismes de base:

  • Avancer et reculer le long du vecteur de vue actuel
  • En se déplaçant vers la gauche et vers la droite le long du vecteur strafe actuel
  • Faire pivoter le lacet (à gauche et à droite)
  • Faire pivoter la hauteur de la vue (vers le haut et vers le bas)

Un jeu qui implémente ce schéma de contrôle n'a besoin que de trois éléments de données: la position de la caméra, le vecteur d'apparence de la caméra et un vecteur vers le haut constant. Le vecteur haut est toujours (0, 1, 0). Les quatre mécanismes ci-dessus manipulent simplement la position et le vecteur d'apparence de la caméra de différentes manières.

Mouvement

Tout d'abord, le mouvement. Dans la démonstration ci-dessous, le mouvement correspond aux touches W, A, S et D standard. Les touches W et S déplacent la caméra vers l'avant et vers l'arrière. Les touches A et D dirigent la caméra vers la gauche et vers la droite. Pour avancer ou reculer la caméra, rien de plus simple:

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

Les dérives de gauche et de droite nécessitent une direction de biais. La direction du rectangle peut être calculée à l'aide du produit croisé:

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

Une fois que vous avez défini la direction du rectangle, implémenter un mouvement de rectangle revient à avancer ou à reculer.

L'étape suivante consiste à faire pivoter la vue.

Lacet

Le lacet ou rotation horizontale de la vue de la caméra n'est qu'une rotation autour du vecteur constant vers le haut. Vous trouverez ci-dessous le code général permettant de faire pivoter le vecteur d'apparence de la caméra autour d'un axe arbitraire. Son fonctionnement est de construire un quaternion représentant la rotation de deltaAngle radians autour de axis, puis il utilise le quaternion pour faire pivoter le vecteur d'apparence de la caméra:

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

Présenter

L'implémentation de l'inclinaison ou de la rotation verticale de la vue de la caméra est similaire, mais au lieu d'une rotation autour du vecteur haut, vous appliquez une rotation autour du vecteur strafe. La première étape consiste à calculer le vecteur strafe, puis à faire pivoter le vecteur d'image de la caméra autour de cet axe.

Résumé

L'API Pointer Lock vous permet de prendre le contrôle du curseur de la souris. Si vous créez des jeux Web, vos joueurs vont adorer s'affronter, car ils ont déplacé avec enthousiasme la souris hors de la fenêtre et votre jeu a cessé de recevoir des mises à jour de souris. L'utilisation est simple:

  • Ajout d'un écouteur d'événements pointerlockchange pour suivre l'état du verrouillage du pointeur.
  • Demander le verrouillage du pointeur pour un élément spécifique
  • Ajouter un écouteur d'événements mousemove pour obtenir des informations

Démonstrations externes

Références