Blokada wskaźnika i elementy sterujące strzelanki FPP

John McCutchan
John McCutchan

Wprowadzenie

Interfejs API Lock Pointer pomaga prawidłowo wdrożyć sterowanie w strzelankach FPP w grze w przeglądarce. Bez względnego ruchu myszy kursor gracza mógłby na przykład uderzyć w prawą krawędź ekranu, a dalsze ruchy w prawo byłyby ignorowane – obraz nie przesuwałby się dalej w prawo, a gracz nie mógłby ścigać złych facetów i strzelać do nich z karabinu maszynowego. Gracz zostanie zabity i sfrustrowany. Dzięki blokowaniu kursora nie będzie dochodziło do takich nieoptymalnych zachowań.

Interfejs Pointer Lock API umożliwia aplikacji:

  • Uzyskaj dostęp do nieprzetworzonych danych myszy, w tym do względnych ruchów myszy.
  • Przekierowywanie wszystkich zdarzeń myszy do określonego elementu

Włączenie blokady wskaźnika powoduje ukrycie kursora myszy, co pozwala na narysowanie wskaźnika specyficznego dla aplikacji lub pozostawienie go w stanie ukrytego, aby użytkownik mógł przesuwać ramkę za pomocą myszy. Względny ruch myszy to zmiana pozycji wskaźnika myszy w porównaniu z poprzednią klatką niezależnie od pozycji bezwzględnej. Jeśli np. wskaźnik myszy przesunął się z (640, 480) do (520, 490), ruch względny wynosił (-120, 10). Poniżej znajdziesz interaktywny przykład pokazujący surowe wartości delta pozycji myszy.

W tym samouczku omawiamy 2 tematy: szczegóły aktywowania i przetwarzania zdarzeń blokowania wskaźnika oraz implementację schematu sterowania w strzelankach pierwszoosobowych. Dobrze. Po przeczytaniu tego artykułu będziesz wiedzieć, jak zablokować kursor i wdrożyć w swojej grze w przeglądarce sterowanie w stylu Quake.

Zgodność z przeglądarką

Obsługa przeglądarek

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

Źródło

Mechanizm blokady wskaźnika

Wykrywanie cech

Aby sprawdzić, czy przeglądarka użytkownika obsługuje blokadę kursora, w obiekcie dokumentu należy sprawdzić, czy jest obecna wartość pointerLockElement lub wersja z preiksem dostawcy. W kodzie:

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

Obecnie blokada kursora jest dostępna tylko w Firefox i Chrome. Opera i Internet Explorer nie obsługują jeszcze tej funkcji.

Aktywuję

Aktywowanie blokady wskaźnika to proces dwuetapowy. Aplikacja najpierw prosi o włączenie blokady wskaźnika dla określonego elementu, a zaraz po udzieleniu przez użytkownika zgody uruchamia zdarzenie pointerlockchange. Użytkownik może w dowolnym momencie anulować blokadę kursora, naciskając klawisz Escape. Aplikacja może też za pomocą kodu zamknąć wskaźnik. Gdy anulujesz blokadę wskaźnika, zostanie uruchomione zdarzenie 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();

Wystarczy powyższy kod. Gdy przeglądarka zablokuje kursor, pojawi się okno z informacją, że aplikacja zablokowała kursor, i wskazaniem, że można to cofnąć, naciskając klawisz Esc.

Pasek informacji Blokada wskaźnika w Chrome
Pasek informacji Blokada wskaźnika w Chrome.

Obsługa zdarzeń

Aplikacja musi dodać słuchaczy do 2 zdarzeń. Pierwszy to pointerlockchange, który jest wywoływany, gdy nastąpi zmiana stanu blokady wskaźnika. Drugi to mousemove, który jest uruchamiany, gdy użytkownik porusza myszką.

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

W zwrotnym wywołaniu pointerlockchange musisz sprawdzić, czy wskaźnik został właśnie zablokowany czy odblokowany. Sprawdzenie, czy blokada wskaźnika jest włączona, jest proste: sprawdź, czy document.pointerLockElement jest równy elementowi, dla którego żądano blokady wskaźnika. Jeśli tak, aplikacja zablokowała wskaźnik. Jeśli nie, wskaźnik został odblokowany przez użytkownika lub Twój kod.

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

Gdy włączona jest blokada wskaźnika, wartości clientX, clientY, screenXscreenY pozostają stałe. Wartości movementXmovementY są aktualizowane o liczbę pikseli, o które wskaźnik przesunąłby się od czasu ostatniego zdarzenia. W pseudokodzie:

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

W przywołaniu zwrotnym mousemove dane dotyczące względnego ruchu kursora myszy można pobrać z pol movementXmovementY zdarzenia.

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

Wykrywanie błędów

Jeśli podczas włączania lub wyłączania blokady kursora wystąpi błąd, zostanie wywołane zdarzenie pointerlockerror. Do tego zdarzenia nie są dołączone żadne dane.

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

Czy wymagany jest tryb pełnoekranowy?

Pierwotnie blokada wskaźnika była powiązana z interfejsem FullScreen API. Oznacza to, że element musi być w trybie pełnoekranowym, aby można było zablokować na nim wskaźnik. Nie jest to już prawda, ponieważ blokady wskaźnika można używać w przypadku dowolnego elementu w aplikacji, niezależnie od tego, czy jest on wyświetlany na pełnym ekranie.

Przykład sterowania w strzelance FPP

Teraz, gdy mamy włączone blokowanie wskaźnika i odbieramy zdarzenia, czas na praktyczny przykład. Czy kiedykolwiek zastanawiałeś/zastanawiałaś się, jak działają elementy sterujące w Quake? Przygotuj się, bo zaraz wyjaśnię je na przykładzie kodu.

Mechanika sterowania w strzelankach FPP opiera się na 4 głównych elementach:

  • przesuwanie się do przodu i do tyłu wzdłuż bieżącego wektora wyglądu;
  • poruszanie się w lewo i w prawo wzdłuż bieżącego wektora strafe;
  • Obrócenie widoku w osi yaw (w lewo i w prawo)
  • obracanie widoku (w górę i w dół),

Gra wykorzystująca ten schemat sterowania potrzebuje tylko 3 elementów danych: pozycji kamery, wektora kierunku kamery i stałego wektora w górę. Wektor skierowany w górę to zawsze (0, 1, 0). Wszystkie 4 wymienione wyżej mechanizmy różnie manipulują położeniem kamery i jej wektorem kierowania.

Ruch

Najpierw na pokładzie jest ruch. W poniżej zaprezentowanym pokazie ruchy są przypisane do standardowych klawiszy W, A, S i D. Klawisze W i S przesuwają kamerę do przodu i do tyłu. Klawisze A i D przesuwają kamerę w lewo i w prawo. Przesuwanie kamery do przodu i do tyłu jest proste:

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

Aby poruszać się w lewo i w prawo, musisz wybrać kierunek. Kierunek ruchu bocznego można obliczyć za pomocą mnożenia wektorów:

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

Gdy określisz kierunek ruchu bocznego, implementacja ruchu bocznego będzie taka sama jak ruch do przodu lub do tyłu.

Kolejnym krokiem jest obrócenie widoku.

odchylenie

Odchylenie lub obrót poziomy widoku kamery to tylko obrót wokół stałego wektora w górę. Poniżej znajduje się ogólny kod służący do obracania wektora kierunku kamery wokół dowolnej osi. Polega ono na utworzeniu kwaternionu reprezentującego obrót o deltaAngle radianów wokół osi axis, a następnie na użyciu tego kwaternionu obraca wektor kierunku kamery:

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

Ton

Wdrożenie pochylenia lub obrotu pionowego widoku kamery jest podobne, ale zamiast obrotu wokół wektora w górę stosujesz obrót wokół wektora strafe. Najpierw należy obliczyć wektor ruchu bocznego, a następnie obrócić wektor kierunku kamery wokół tej osi.

Podsumowanie

Interfejs Pointer Lock API umożliwia przejęcie kontroli nad kursorem myszy. Jeśli tworzysz gry internetowe, Twoi gracze będą zachwyceni, gdy przestaną ginąć, ponieważ z podniecenia przesuną kursor poza okno i gra przestanie otrzymywać aktualizacje dotyczące kursora. Korzystanie z niego jest proste:

  • Dodaj detektor zdarzeń pointerlockchange, aby śledzić stan blokady wskaźnika
  • Prośba o blokadę wskaźnika dla konkretnego elementu
  • Dodaj detektor zdarzenia mousemove, aby otrzymywać powiadomienia

Demonstracje zewnętrzne

Pliki referencyjne