Gamepad API を使用してウェブゲームを次のレベルに引き上げる方法を学びます。
Chrome のオフライン ページのイースター エッグは、史上最も秘密が漏洩した秘密の 1 つです([citation needed]
)。スペース キーを押すか、モバイル デバイスで恐竜をタップすると、オフライン ページがプレイ可能なアーケード ゲームになります。ゲームをプレイするときにオフラインにする必要がないことはご存じでしょう。Chrome では、about://dino
に移動するだけで、または about://network-error/-106
に移動するだけで、ところで、毎月 2 億 7,000 万人が Chrome Dino ゲームをプレイしていることをご存じですか?
アーケード モードではゲームパッドでゲームをプレイできることは、知っておくと便利な情報ですが、あまり知られていないかもしれません。ゲームパッド サポートは、この記事の執筆時点で約 1 年前に、Reilly Grant によるcommit で追加されました。ご覧のとおり、このゲームは他の 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 つの独立したモーターによって生成される触覚フィードバック エフェクト。1 つのモーターはゲームパッド トリガーのそれぞれに配置されています。
次の図は、仕様書から直接抜粋した、一般的なゲームパッドのボタンと軸のマッピングと配置を示しています。
ゲームパッドが接続されたときの通知
ゲームパッドが接続されたタイミングを把握するには、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 つの項目に固定されています。接続されているゲームパッドが 0 個または 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 つの独立したモーターによって生成される触覚フィードバック エフェクトです。1 つのモーターはゲームパッド トリガーのそれぞれに配置されています。
// 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>
デモ
次の例には、ゲームパッド テスターのデモが埋め込まれています。ソースコードは Glitch で入手できます。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 拡張機能の仕様は Brandon Jones が編集しています。ヒーロー画像は Laura Torrent Puig によるものです。