はじめに
Pointer Lock API は、ブラウザゲームにファーストパーソン シューティング コントロールを適切に実装するのに役立ちます。マウスの相対移動がないと、たとえばプレーヤーのカーソルが画面の右端に当たると、それ以上右への動きがあっても、視界が右にパンし続けなくなり、プレーヤーは悪者を追いかけ、マシンガンで追い出すことができなくなります。プレーヤーはフラストレーションを感じ、フラストレーションを感じます。ポインタロックを使用すると、この最適でない動作は発生しません。
Pointer Lock API を使用すると、アプリケーションで次のことが可能になります。
- マウスの相対移動を含むマウスの生データにアクセスできます
- すべてのマウスイベントを特定の要素にルーティングする
ポインタのロックを有効にする副作用として、マウスカーソルが非表示になり、必要に応じてアプリケーション固有のポインタを描画するか、ユーザーがマウスでフレームを移動できるようにマウスポインタを非表示にしたままにするかを選択できます。マウスの相対的な移動とは、絶対位置に関係なく、マウスポインタの位置を前のフレームからの差分として表したものです。たとえば、マウスポインタが (640, 480) から (520, 490) に移動した場合、相対移動は (-120, 10) でした。未加工のマウス位置のデルタを示すインタラクティブなサンプルについては、以下をご覧ください。
このチュートリアルでは、2 つのトピックを取り上げます。1 つはポインタのロック イベントの有効化と処理、もう 1 つはファースト パーソン シューティング コントロール スキームの実装です。そうです。この記事を読み終えると、ポインタロックの使用方法と、独自のブラウザゲームに 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 のコントロールの仕組みを知りたいと思ったことはありませんか?これからコードを使って説明するので、しっかりと理解しましょう。
ファーストパーソン シューティング ゲームのコントロールは、
- 現在のルックベクトルに沿って前後に移動する
- 現在のストローフ ベクトルに沿って左右に移動する
- ビューのヨーの回転(左右)
- ビューのピッチの回転(上下)
このコントロール スキームを実装するゲームに必要なデータは、カメラ位置、カメラのルック ベクトル、定数の上ベクトルの 3 つだけです。上方向ベクトルは常に (0, 1, 0) です。上記の 4 つのメカニズムはすべて、カメラの位置とカメラのルック ベクトルをさまざまな方法で操作するだけです。
様式
1 つ目はムーブメントです。以下のデモでは、動きが標準の 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
イベント リスナーを追加して更新を取得する