指標鎖定和第一人稱射擊控制項

John McCutchan
John McCutchan

簡介

Pointer Lock API 可協助您在瀏覽器遊戲中正確實作第一人稱射擊遊戲控制項。舉例來說,如果沒有相對滑鼠移動動作,玩家的游標就可能會撞到螢幕右側邊緣,而往右移動的任何動作都會被忽略,畫面不會繼續向右滑動,玩家也無法追捕壞人,並用機關槍掃射他們。玩家會被擊殺,並因此感到挫折。使用指標鎖定功能後,就不會發生這種不理想的行為。

Pointer Lock API 可讓應用程式執行下列操作:

  • 取得原始滑鼠資料,包括相對滑鼠動作
  • 將所有滑鼠事件轉送至特定元素

啟用游標鎖定功能後,滑鼠游標會隱藏起來,您可以視需要選擇繪製特定應用程式的游標,或是讓滑鼠游標保持隱藏狀態,讓使用者可以使用滑鼠移動影格。相對滑鼠動作是滑鼠游標相對於上一個影格的位置差異,不受絕對位置影響。舉例來說,如果滑鼠游標從 (640, 480) 移動到 (520, 490),相對移動量就是 (-120, 10)。請參閱下方的互動式範例,瞭解原始滑鼠位置差異。

本教學課程將介紹兩個主題:啟用及處理指標鎖定事件的細節,以及實作第一人稱射擊遊戲控制方案。沒錯,閱讀完本文後,您就會知道如何使用指標鎖定功能,並為自己的瀏覽器遊戲實作 Quake 風格的控制項!

瀏覽器相容性

瀏覽器支援

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

資料來源

指標鎖定機制

功能偵測

如要判斷使用者的瀏覽器是否支援指標鎖定功能,您需要在文件物件中檢查 pointerLockElement 或供應商前置字版本。程式碼:

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

目前只有 Firefox 和 Chrome 支援指標鎖定功能。Opera 和 IE 尚未支援這項功能。

啟用中

啟用指標鎖定功能的程序分為兩個步驟。首先,您的應用程式會要求為特定元素啟用指標鎖定功能,然後在使用者授予權限後立即觸發 pointerlockchange 事件。使用者只要按下 Esc 鍵,即可隨時取消指標鎖定。應用程式也可以透過程式設計退出指標鎖定。取消指標鎖定時,系統會觸發 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();

只要使用上述程式碼即可。當瀏覽器鎖定指標時,系統會彈出一個氣泡,讓使用者知道應用程式已鎖定指標,並指示使用者按下「Esc」鍵取消鎖定。

Chrome 中的指標鎖定資訊列。
Chrome 中的指標鎖定資訊列。

事件處理

應用程式必須為兩個事件新增監聽器。第一個是 pointerlockchange,每當指標鎖定狀態發生變更時就會觸發。第二個是 mousemove,會在滑鼠移動時觸發。

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

pointerlockchange 回呼中,您必須檢查指標是否剛剛已鎖定或解鎖。判斷是否已啟用指標鎖定功能很簡單:檢查 document.pointerLockElement 是否等於系統要求指標鎖定的元素。如果是,表示應用程式已成功鎖定指標;如果不是,表示指標已由使用者或您自己的程式碼解鎖。

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

啟用游標鎖定功能後,clientXclientYscreenXscreenY 會保持不變。movementXmovementY 會根據自上次事件傳送以來,游標移動的像素數量進行更新。在虛擬程式碼中:

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

mousemove 回呼中,您可以從事件的 movementXmovementY 欄位擷取相對滑鼠動作資料。

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

擷取錯誤

如果在進入或退出指標鎖定時發生錯誤,系統會觸發 pointerlockerror 事件。這個事件沒有附加任何資料。

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

是否需要全螢幕模式?

原本的指標鎖定功能與 FullScreen API 相關聯,也就是說,元素必須處於全螢幕模式,才能讓指標鎖定在該元素上。但現在已非如此,您可以在應用程式全螢幕模式或非全螢幕模式下,為任何元素使用指標鎖定功能。

第一人稱射擊遊戲控制項範例

我們已啟用指標鎖定功能並接收事件,現在來看看實際範例。您是否曾想過《Quake》中的控制項如何運作?準備好了,我要用程式碼來解釋這些概念!

第一人稱射擊遊戲的控制項以四個核心機制為基礎:

  • 沿著目前的觀看向量前後移動
  • 沿著目前的側移向量向左和向右移動
  • 旋轉檢視畫面偏航 (左右)
  • 旋轉檢視角度 (上下)

實作此控制配置的遊戲只需要三個資料:攝影機位置、攝影機觀看向量和常數向上向量。向上向量一律為 (0, 1, 0)。上述四種機制都只是以不同方式操控攝影機位置和攝影機觀看向量。

活動

首先是移動。在下方示範中,移動會對應至標準的 W、A、S 和 D 鍵。W 和 S 鍵可讓相機向前和向後移動。而 A 和 D 鍵則可讓相機向左和向右移動。移動相機鏡頭前後很簡單:

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

左右側移動需要指定側移方向。您可以使用交叉乘積來計算橫向移動方向:

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

有了側移方向後,實作側移動作的做法就與前進或後退相同。

接下來,我們要旋轉檢視畫面。

偏轉

偏轉或攝影機檢視畫面的水平旋轉,只是繞著常數向上向量旋轉。以下是用來旋轉攝影機觀看向量 (look vector) 的一般程式碼,其運作方式是建構四元數,代表以 axis 為中心旋轉 deltaAngle 弧度的旋轉,然後使用四元數旋轉相機觀看向量:

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

音調

實作相機視圖的俯仰或垂直旋轉效果時,做法類似,但您不是以向上向量為旋轉軸,而是以側向向量為旋轉軸。第一步是計算側移向量,然後以該軸旋轉相機觀看向量。

摘要

您可以使用指標鎖定 API 控制滑鼠游標。如果您製作的是網路遊戲,玩家在興奮地將滑鼠移出視窗時,如果遊戲停止接收滑鼠更新,玩家就不會因為這樣而遭到攻擊,這會讓玩家感到開心。使用方式很簡單:

  • 新增 pointerlockchange 事件監聽器,以追蹤指標鎖定的狀態
  • 要求特定元素的指標鎖定
  • 新增 mousemove 事件監聽器以接收更新

外部示範

參考資料