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

約翰麥卡奇
John McCutchan

引言

Pointer Lock API 有助於在瀏覽器遊戲中正確導入第一人稱射擊控制項。舉例來說,如果沒有相對的滑鼠移動操作,玩家的遊標就會產生折扣,如果向右移動到右側畫面,則無法繼續向右平移,玩家也無法追擊惡意男人,也無法利用機器槍枝抓住他們的目光。玩家會因為受挫而感到挫折。透過指標鎖定,就無法達成這項不理想的行為。

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

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

此外,系統會隱藏滑鼠遊標,這是啟用指標鎖定的副作用,可讓您視需要繪製應用程式專屬指標,或是隱藏滑鼠遊標,讓使用者可以用滑鼠移動頁框。相對滑鼠移動是滑鼠遊標與前一個頁框的差異 (無論絕對位置)。舉例來說,如果滑鼠遊標從 (640, 480) 移至 (520, 490),相對動作為 (-120, 10)。請參閱下列互動式範例,瞭解原始滑鼠位置差異。

本教學課程涵蓋兩個主題:啟動及處理指標鎖定事件的細節,以及實作第一人稱射擊遊戲控制機制。沒錯,當您讀完這篇文章時,您會瞭解如何為自己的瀏覽器遊戲使用指標鎖定功能,並實作 Quake 樣式的控制項!

瀏覽器相容性

瀏覽器支援

  • 37
  • 13
  • 50
  • 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);

設定引導線方向後,施行階梯運動就等於向前或向後移動。

接下來要旋轉視角

偏轉

相機檢視畫面的偏轉 (或水平旋轉) 只是常數上向量旋轉而已。以下的一般程式碼可用來沿著任意軸旋轉相機外觀向量。運作方式是建構「四元數」,表示在 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);

推薦

實作的傾斜或垂直旋轉鏡頭檢視畫面很類似,但不是在上向量周圍旋轉,而是在層級向量周圍使用旋轉。第一步是計算步向量,然後繞該軸旋轉相機外觀向量。

摘要

Pointer Lock API 可讓您控制滑鼠遊標。如果您正在製作網路遊戲,當玩家因興奮地將滑鼠移出視窗而遊戲停止收到滑鼠更新,就會愛不釋手,玩家會愛不釋手。使用方式很簡單:

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

外部示範

參考資料