はじめに
PointerLock API は、ブラウザ ゲームに一人称視点のシューティング ゲームのコントロールを適切に実装するのに役立ちます。相対的なマウスの動きがないと、たとえば、プレーヤーのカーソルが画面の右端に当たると、右へのそれ以上の動きが無視され、ビューが右にパンされ続けず、プレーヤーは悪者を追いかけてマシンガンで攻撃できなくなります。プレイヤーは倒され、イライラするでしょう。ポインタロックを使用すると、この最適でない動作は発生しません。
Pointer Lock API を使用すると、アプリは次のことができます。
- 相対マウス移動など、元のマウスデータにアクセスする
- すべてのマウスイベントを特定の要素に転送する
ポインタロックを有効にすると、マウスポインタが非表示になります。必要に応じて、アプリ固有のポインタを描画するか、マウスポインタを非表示にしてユーザーがマウスでフレームを移動できるようにするかを選択できます。相対マウス移動は、絶対位置に関係なく、マウスポインタの位置が前のフレームからどれだけ移動したかを示します。たとえば、マウス ポインタが(640, 480)から(520, 490)に移動した場合、相対移動は(-120, 10)です。マウスの位置の差分(未加工)を示すインタラクティブな例を以下に示します。
このチュートリアルでは、ポインタ ロック イベントの有効化と処理の詳細と、ファースト パーソン シューティング ゲームのコントロール スキームの実装について説明します。はい、この記事を読み終えたら、ポインタ ロックを使用して、独自のブラウザゲームに Quake スタイルのコントロールを実装する方法を習得できます。
ブラウザの互換性
ポインタロックの仕組み
特徴検出
ユーザーのブラウザがポインタロックをサポートしているかどうかを確認するには、ドキュメント オブジェクトで pointerLockElement
またはベンダー接頭辞付きのバージョンを確認する必要があります。コードでは次のようになります。
var havePointerLock = 'pointerLockElement' in document ||
'mozPointerLockElement' in document ||
'webkitPointerLockElement' in document;
現在、ポインタ ロックは Firefox と Chrome でのみご利用いただけます。Opera と IE ではまだサポートされていません。
有効にしています
ポインタ ロックを有効にするには、2 つの手順が必要です。まず、アプリが特定の要素でポインタロックを有効にするようリクエストし、ユーザーが許可するとすぐに 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 キーを押すとロックを解除できることがユーザーに示されます。
イベント処理
アプリケーションでリスナーを追加する必要があるイベントは 2 つあります。1 つ目は pointerlockchange
で、ポインタ ロックの状態が変更されるたびに発生します。2 つ目は 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);
}
ポインタ ロックが有効になっている場合、clientX
、clientY
、screenX
、screenY
は一定のままです。movementX
と movementY
は、最後のイベントが配信されてからポインタが移動したピクセル数で更新されます。疑似コードの例:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
mousemove
コールバック内では、イベントの movementX
フィールドと movementY
フィールドから相対マウス モーション データを抽出できます。
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 の操作方法を知りたくありませんか?コードで説明しますので、ご準備ください。
ファーストパーソン シューティングの操作は、次の 4 つのコア メカニクスを中心に構築されています。
- 現在のルックベクトル沿って前後に移動する
- 現在の移動ベクトル沿って左右に移動する
- ビューのヨーを回転する(左右)
- ビューのピッチを回転する(上下)
この制御スキームを実装するゲームでは、カメラの位置、カメラのルック ベクトル、一定のアップ ベクトルの 3 つのデータのみが必要です。上向きベクトルは常に(0, 1, 0)です。上記の 4 つのメカニズムはすべて、カメラの位置とカメラのルック ベクトルをさまざまな方法で操作するだけです。
動き
まず、動きです。以下のデモでは、移動は標準の 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
イベント リスナーを追加