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 des commandes de tir à la première personne dans un jeu sur navigateur. En l'absence de mouvements relatifs 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 annulé. La vue ne continuerait pas de se déplacer vers la droite, et le joueur ne pourrait pas poursuivre les méchants ni les imbriquer avec sa mitrailleuse. Le joueur va devenir fragile et 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 ses mouvements relatifs
  • Acheminer tous les événements de souris vers un élément spécifique

Si vous activez le verrouillage du pointeur, le curseur de la souris est masqué. Vous pouvez donc choisir de dessiner un pointeur spécifique à l'application si vous le souhaitez, ou de laisser le pointeur de la souris 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 par rapport à l'image précédente, quelle que soit la position absolue. Par exemple, si le pointeur de la souris est passé de (640, 480) à (520, 490), le mouvement relatif était (-120, 10). Vous trouverez ci-dessous un exemple interactif illustrant les deltas de position brute de la souris.

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

Compatibilité du navigateur

Navigateurs pris en charge

  • Chrome: 37 <ph type="x-smartling-placeholder">
  • Edge: 13 <ph type="x-smartling-placeholder">
  • Firefox: 50. <ph type="x-smartling-placeholder">
  • Safari: 10.1. <ph type="x-smartling-placeholder">

Source

Mécanisme 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 IE ne prennent pas encore en charge cette fonctionnalité.

Activation

L'activation du verrouillage du pointeur s'effectue en deux étapes. Votre application demande d'abord d'activer le verrouillage du pointeur pour un élément spécifique. Dè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 quitter par programmation le verrouillage du pointeur. 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 est suffisant. Lorsque le navigateur verrouille le pointeur, une info-bulle s'affiche pour indiquer à l'utilisateur que votre application a verrouillé le pointeur et lui demander de l'annuler en appuyant sur la touche "Échap". .

<ph type="x-smartling-placeholder">
</ph> Barre d&#39;informations de verrouillage du pointeur dans Chrome.
Barre d'informations du verrou du 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 de l'é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é. Pour déterminer si le verrouillage du pointeur a été activé, il suffit de vérifier 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. 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 relatives aux mouvements 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 par l'entrée ou la sortie 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 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 y être verrouillé. Ce n'est plus le cas, et le verrouillage du pointeur peut être utilisé ou non pour n'importe quel élément de votre application en plein écran.

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 ? Cernez parce que je m'apprête à les expliquer avec du code.

Les commandes de tir à la première personne s'articulent autour de quatre mécanismes de base:

  • Avancer et reculer le long du vecteur d'apparence actuel
  • Se déplacer vers la gauche et la droite le long du vecteur de strafe actuel
  • Faire pivoter le lacet (à gauche et à droite)
  • Faire pivoter l'inclinaison de la vue (vers le haut et vers le bas)

Un jeu mettant en œuvre ce schéma de contrôle ne requiert que trois éléments de données: la position de la caméra, le vecteur d'apparence de la caméra et un vecteur constant haut. Le vecteur "up" est toujours (0, 1, 0). Les quatre mécanismes ci-dessus manipulent simplement la position de la caméra et le vecteur d'image de la caméra de différentes manières.

Mouvement

Le premier est le mouvement. Dans la démonstration ci-dessous, le mouvement est mappé sur les touches standards W, A, S et D. Les touches W et S permettent de faire avancer et reculer l'appareil photo. Les touches A et D dirigent la caméra vers la gauche et vers la droite. Pour déplacer la caméra vers l'avant ou vers l'arrière, 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);

Pour délimiter un côté gauche et droit, il est nécessaire d'utiliser une direction d'intersection. Le sens du strafe peut être calculé à l'aide du produit croisé:

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

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

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

Yaw

Le lacet, ou rotation horizontale de la vue de la caméra, correspond simplement à une rotation autour du vecteur montant constant. Vous trouverez ci-dessous le code général permettant de faire pivoter le vecteur d'image de l'appareil photo autour d'un axe arbitraire. Elle consiste à construire un quaternion représentant la rotation de deltaAngle radians autour de axis, puis à faire pivoter le vecteur d'apparence de la caméra à l'aide du quaternion:

// 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 de strafe. La première étape consiste à calculer le vecteur de 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 quand ils cessent d'être frigés, car ils ont déplacé la souris hors de la fenêtre avec enthousiasme et votre jeu ne reçoit plus de mises à jour. Son utilisation est simple:

  • Ajout de l'é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 l'écouteur d'événements mousemove pour recevoir des informations

Démonstrations externes

Références