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

John McCutchan
John McCutchan

Introduction

L'API PointerLock permet d'implémenter correctement les commandes de jeu de tir à la première personne dans un jeu dans le navigateur. Sans mouvement relatif de la souris, le curseur du joueur pourrait, par exemple, atteindre le bord droit de l'écran, et tout mouvement supplémentaire vers la droite serait ignoré. La vue ne continuerait pas à pivoter vers la droite, et le joueur ne pourrait pas poursuivre les méchants et les mitrailler avec sa mitrailleuse. Le joueur va se faire tuer et se sentir 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 aux mouvements relatifs de la souris
  • Router 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é. Vous pouvez alors choisir de dessiner un pointeur spécifique à l'application si vous le souhaitez, ou laisser le pointeur masqué afin que l'utilisateur puisse déplacer le cadre avec la souris. Le mouvement relatif de la souris correspond au delta de la position du pointeur de la souris par rapport au frame précédent, quelle que soit la position absolue. Par exemple, si le pointeur de la souris est passé de (640, 480) à (520, 490), le mouvement relatif est (-120, 10). Vous trouverez ci-dessous un exemple interactif montrant les deltas de position de la souris bruts.

Ce tutoriel aborde deux sujets: les principes de base de l'activation et du traitement des événements de verrouillage du pointeur, et l'implémentation du schéma de contrôle des jeux de tir en vue subjective. C'est bien cela. Lorsque vous aurez terminé de lire cet article, vous saurez comment utiliser le verrouillage du pointeur et implémenter des commandes de type Quake pour votre propre jeu dans le navigateur.

Compatibilité du navigateur

Navigateurs pris en charge

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

Source

Mécanique du 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 un préfixe du fournisseur dans l'objet de document. En code:

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

Actuellement, le verrouillage du pointeur n'est disponible que dans Firefox et Chrome. Opera et IE ne sont pas encore compatibles.

Activation

L'activation du verrouillage du pointeur se fait en deux étapes. Votre application demande d'abord que le verrouillage du pointeur soit activé pour un élément spécifique. Immédiatement après que l'utilisateur a donné 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 désactiver le verrouillage du pointeur de manière programmatique. 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 bulle s'affiche pour indiquer à l'utilisateur que votre application a verrouillé le pointeur et qu'il peut l'annuler en appuyant sur la touche Échap.

Barre d'informations sur le verrouillage du pointeur dans Chrome
Barre d'informations du verrouillage du pointeur dans Chrome.

Gestion des événements

Votre application doit ajouter des écouteurs pour deux événements. Le premier est pointerlockchange, qui se déclenche chaque fois qu'un changement d'état de verrouillage du pointeur se produit. Le second est mousemove, qui se déclenche chaque fois que la souris a bougé.

// 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é. Il est simple de déterminer si le verrouillage du pointeur a été activé: 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 réussi à verrouiller le pointeur. Si ce n'est pas le cas, le pointeur 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 déplacé depuis l'envoi 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;
}

Relever les erreurs

Si une erreur est générée lors de l'activation ou de la désactivation du verrouillage du 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 obligatoire ?

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

Exemple de commandes de jeu de tir à la première personne

Maintenant que le verrouillage du pointeur est activé et que nous recevons des événements, nous allons passer à un exemple pratique. Avez-vous déjà voulu savoir comment fonctionnent les commandes de Quake ? Attachez-vous, car je vais vous les expliquer avec du code !

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

  • Avancer et reculer le long du vecteur de regard actuel
  • Déplacement vers la gauche et vers la droite le long du vecteur de strafing actuel
  • Faire pivoter la vue (gauche et droite)
  • Faire pivoter la vue (haut et bas)

Un jeu implémentant ce schéma de contrôle n'a besoin que de trois données: la position de la caméra, le vecteur de vue de la caméra et un vecteur vers le haut constant. Le vecteur vers le haut est toujours (0, 1, 0). Les quatre mécanismes ci-dessus ne font que manipuler la position de la caméra et le vecteur de vue de la caméra de différentes manières.

Mouvement

La première chose à faire est de bouger. Dans la démonstration ci-dessous, les mouvements sont mappés sur les touches W, A, S et D standards. Les touches W et S permettent de déplacer la caméra vers l'avant et vers l'arrière. Les touches A et D permettent de déplacer la caméra vers la gauche et la droite. Il est simple de déplacer la caméra vers l'avant et l'arrière:

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

Le strafing vers la gauche et vers la droite nécessite une direction de strafing. La direction de strafing peut être calculée à l'aide du produit vectoriel croisé:

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

Une fois que vous avez déterminé la direction de la visée latérale, l'implémentation de ce mouvement est identique à celle de l'avancement ou du recul.

La prochaine étape 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 vertical constant. Vous trouverez ci-dessous un code général permettant de faire pivoter le vecteur de vue de la caméra autour d'un axe arbitraire. Il fonctionne en construisant un quaternion représentant la rotation de deltaAngle radians autour de axis, puis en utilisant le quaternion pour faire pivoter le vecteur de vue 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 "déplacement latéral". La première étape consiste à calculer le vecteur de strafing, puis à faire pivoter le vecteur de vue de la caméra autour de cette axe.

Résumé

L'API Pointer Lock vous permet de contrôler le pointeur de la souris. Si vous créez des jeux Web, vos joueurs apprécieront de ne plus être tués parce qu'ils ont déplacé la souris en dehors de la fenêtre et que votre jeu ne reçoit plus de mises à jour de la souris. L'utilisation est simple:

  • Ajouter 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 recevoir des mises à jour

Démonstrations externes

Références