Gamepad API を使用してウェブゲームを次のレベルに引き上げる方法を学びます。
Chrome のオフライン ページのイースター エッグは、歴史上最も秘密が守られていないものの 1 つです([citation needed]、ただし、劇的な効果を狙った主張です)。スペースキーを押すか、モバイル デバイスで恐竜をタップすると、オフライン ページがプレイ可能なアーケード ゲームに変わります。プレイしたいときにオフラインにする必要がないことはご存知かもしれません。Chrome で about://dino に移動するか、about://network-error/-106 にアクセスするだけでプレイできます。しかし、毎月 2 億 7,000 万回も Chrome Dino ゲームがプレイされていることはご存知でしたか?
アーケード モードではゲームパッドでゲームをプレイできるという事実も、知っておくと役立つかもしれません。ゲームパッドのサポートは、このドキュメントの執筆時点から約 1 年前に Reilly Grant によるコミットで追加されました。ご覧のとおり、このゲームは Chromium プロジェクトの他の部分と同様に、完全にオープンソースです。この記事では、Gamepad API の使い方を紹介します。
Gamepad API を使用する
機能の検出とブラウザのサポート
Gamepad API は、パソコンとモバイルの両方で、普遍的に優れたブラウザ サポートを備えています。次のスニペットを使用して、Gamepad API がサポートされているかどうかを検出できます。
if ('getGamepads' in navigator) {
// The API is supported!
}
ブラウザがゲームパッドをどのように表現するか
ブラウザはゲームパッドを Gamepad オブジェクトとして表します。Gamepad には次のプロパティがあります。
id: ゲームパッドの識別文字列。この文字列は、接続されたゲームパッド デバイスのブランドまたはスタイルを識別します。displayId: 関連付けられたVRDisplayのVRDisplay.displayId(該当する場合)。index: ナビゲーター内のゲームパッドのインデックス。connected: ゲームパッドがシステムに接続されているかどうかを示します。hand: コントローラがどちらの手で持たれているか、または持たれる可能性が高いかを定義する列挙型。timestamp: このゲームパッドのデータが最後に更新された日時。mapping: このデバイスで使用されているボタンと軸のマッピング("standard"または"xr-standard")。pose: WebVR コントローラに関連付けられたポーズ情報を表すGamepadPoseオブジェクト。axes: ゲームパッドのすべての軸の値の配列。-1.0~1.0の範囲に線形正規化されます。buttons: ゲームパッドのすべてのボタンのボタン状態の配列。
ボタンはデジタル(押されているか押されていないか)またはアナログ(78% 押されているなど)にできます。そのため、ボタンは GamepadButton オブジェクトとして報告され、次の属性を持ちます。
pressed: ボタンの押下状態(ボタンが押されている場合はtrue、押されていない場合はfalse)。touched: ボタンのタッチ状態。ボタンがタッチを検出できる場合、このプロパティは、ボタンがタッチされている場合はtrue、それ以外の場合はfalseになります。value: アナログ センサーを備えたボタンの場合、このプロパティは、ボタンが押された量を0.0~1.0の範囲に線形正規化した値を表します。hapticActuators:GamepadHapticActuatorオブジェクトを含む配列。各オブジェクトは、コントローラで使用可能な触覚フィードバック ハードウェアを表します。
ブラウザとゲームパッドによっては、vibrationActuator プロパティが表示されることがあります。2 種類のアクションを制御できます。
- デュアル ランブル: ゲームパッドの各グリップに 1 つずつある 2 つの偏心回転質量アクチュエータによって生成される触覚フィードバック効果。
- トリガー ランブル: 2 つの独立したモーターによって生成される触覚フィードバック効果。各モーターはゲームパッドのトリガーに配置されています。
次の概略図は、仕様から直接引用したもので、一般的なゲームパッドのボタンと軸のマッピングと配置を示しています。
ゲームパッドが接続されたときの通知
ゲームパッドが接続されたタイミングを知るには、window オブジェクトでトリガーされる gamepadconnected イベントをリッスンします。ユーザーがゲームパッドを接続すると(USB または Bluetooth を使用)、GamepadEvent が発生し、そのゲームパッドの詳細が gamepad プロパティに格納されます。以下は、手元にあった Xbox 360 コントローラの例です(レトロゲームが好きです)。
window.addEventListener('gamepadconnected', (event) => {
console.log('✅ 🎮 A gamepad was connected:', event.gamepad);
/*
gamepad: Gamepad
axes: (4) [0, 0, 0, 0]
buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
connected: true
id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
index: 0
mapping: "standard"
timestamp: 6563054.284999998
vibrationActuator: GamepadHapticActuator {type: "dual-rumble"}
*/
});
ゲームパッドの接続が解除されたときの通知
ゲームパッドの接続解除の通知は、接続の検出と同様の方法で行われます。今回は、アプリが gamepaddisconnected イベントをリッスンします。次の例では、Xbox 360 コントローラを抜くと connected が false に変わります。
window.addEventListener('gamepaddisconnected', (event) => {
console.log('❌ 🎮 A gamepad was disconnected:', event.gamepad);
/*
gamepad: Gamepad
axes: (4) [0, 0, 0, 0]
buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
connected: false
id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
index: 0
mapping: "standard"
timestamp: 6563054.284999998
vibrationActuator: null
*/
});
ゲームループ内のゲームパッド
ゲームパッドの取得は navigator.getGamepads() の呼び出しから始まり、Gamepad アイテムを含む配列が返されます。Chrome の配列は、常に 4 つの項目の固定長になります。接続されているゲームパッドが 4 つ未満の場合は、アイテムが null になることがあります。必ず配列のすべての項目を確認してください。ゲームパッドはスロットを「記憶」しており、常に最初に使用可能なスロットに存在するとは限りません。
// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]
1 つ以上のゲームパッドが接続されているにもかかわらず、navigator.getGamepads() が null アイテムをレポートする場合は、いずれかのボタンを押して各ゲームパッドを「起動」する必要があるかもしれません。次のコードに示すように、ゲームループでゲームパッドの状態をポーリングできます。
const pollGamepads = () => {
// Always call `navigator.getGamepads()` inside of
// the game loop, not outside.
const gamepads = navigator.getGamepads();
for (const gamepad of gamepads) {
// Disregard empty slots.
if (!gamepad) {
continue;
}
// Process the gamepad state.
console.log(gamepad);
}
// Call yourself upon the next animation frame.
// (Typically this happens every 60 times per second.)
window.requestAnimationFrame(pollGamepads);
};
// Kick off the initial game loop iteration.
pollGamepads();
バイブレーション アクチュエータ
vibrationActuator プロパティは GamepadHapticActuator オブジェクトを返します。これは、ハプティクス フィードバックの目的で力を加えることができるモーターやその他のアクチュエータの構成に対応します。触覚効果は、Gamepad.vibrationActuator.playEffect() を呼び出すことで再生できます。有効な効果タイプは 'dual-rumble' と 'trigger-rumble' のみです。
サポートされている振動効果
if (gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
// Trigger rumble supported.
} else if (gamepad.vibrationActuator.effects.includes('dual-rumble')) {
// Dual rumble supported.
} else {
// Rumble effects aren't supported.
}
デュアル ランブル
デュアル ランブルとは、標準のゲームパッドの各ハンドルに偏心回転質量振動モーターを備えたハプティクス構成のことです。この構成では、どちらのモーターもゲームパッド全体を振動させることができます。2 つの質量の値は異なるため、それぞれの効果を組み合わせて、より複雑な触覚効果を生み出すことができます。デュアル ランブル効果は、次の 4 つのパラメータで定義されます。
duration: バイブレーション効果の継続時間をミリ秒単位で設定します。startDelay: バイブレーションが開始されるまでの遅延時間を設定します。strongMagnitudeとweakMagnitude: 重い方と軽い方の偏心回転質量モーターのバイブレーション強度レベルを0.0~1.0の範囲に正規化して設定します。
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const dualRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
if (!('vibrationActuator' in gamepad)) {
return;
}
gamepad.vibrationActuator.playEffect('dual-rumble', {
// Start delay in ms.
startDelay: delay,
// Duration in ms.
duration: duration,
// The magnitude of the weak actuator (between 0 and 1).
weakMagnitude: weak,
// The magnitude of the strong actuator (between 0 and 1).
strongMagnitude: strong,
});
};
トリガーの振動
トリガー ランブルは、2 つの独立したモーターによって生成される触覚フィードバック効果です。各モーターはゲームパッドのトリガーに配置されています。
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const triggerRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
if (!('vibrationActuator' in gamepad)) {
return;
}
// Feature detection.
if (!('effects' in gamepad.vibrationActuator) || !gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
return;
}
gamepad.vibrationActuator.playEffect('trigger-rumble', {
// Duration in ms.
duration: duration,
// The left trigger (between 0 and 1).
leftTrigger: leftTrigger,
// The right trigger (between 0 and 1).
rightTrigger: rightTrigger,
});
};
権限に関するポリシーとの統合
Gamepad API 仕様では、文字列 "gamepad" で識別されるポリシー制御機能を定義しています。デフォルトの allowlist は "self" です。ドキュメントの権限ポリシーは、そのドキュメント内のコンテンツが navigator.getGamepads() にアクセスできるかどうかを決定します。いずれかのドキュメントで無効になっている場合、ドキュメント内のコンテンツは navigator.getGamepads() を使用できず、gamepadconnected イベントと gamepaddisconnected イベントも発生しません。
<iframe src="index.html" allow="gamepad"></iframe>
デモ
次の例には、ゲームパッド テスターのデモが埋め込まれています。ソースコードは GitHub で入手できます。USB または Bluetooth を使用してゲームパッドを接続し、ボタンを押すか軸を動かすことで、デモを試すことができます。
ボーナス: web.dev で Chrome Dino をプレイする
このサイトでゲームパッドを使って Chrome Dino をプレイできます。ソースコードは GitHub で入手できます。trex-runner.js のゲームパッド ポーリングの実装を確認し、キープレスがどのようにエミュレートされているかを確認します。
Chrome Dino ゲームパッドのデモを動作させるため、私はコア Chromium プロジェクトから Chrome Dino ゲームを抜き出し(Arnelle Ballane 氏による以前の取り組みを更新)、スタンドアロン サイトに配置し、既存のゲームパッド API 実装を拡張してダッキングとバイブレーション効果を追加し、全画面モードを作成しました。また、Mehul Satardekar 氏がダークモードの実装に貢献してくれました。ゲームをお楽しみください。
役に立つリンク
謝辞
このドキュメントは、François Beaufort と Joe Medley によってレビューされました。Gamepad API 仕様は、Steve Agoston、James Hollyer、Matt Reynolds によって編集されています。以前の仕様エディタは、Brandon Jones、Scott Graham、Ted Mielczarek です。Gamepad Extensions 仕様は Brandon Jones によって編集されています。ヒーロー画像: Laura Torrent Puig