Gamepad API を使用してウェブゲームをレベルアップする方法をご紹介します。
Chrome のオフライン ページのイースター エッグは歴史上最も知られた秘密の 1 つです([citation needed]
ですが、劇的な効果があると主張しています)。Space キーを押すか、モバイル デバイスで恐竜をタップすると、オフライン ページがプレイ可能なアーケード ゲームになります。プレイしたいときにオフラインにする必要はありません。Chrome では about://dino
に移動するだけで、about://network-error/-106
から探すこともできます。Chrome の恐竜ゲームが毎月 2 億 7,000 万プレイされていることはご存じでしょうか。
もう一つ、アーケード モードではゲームパッドでゲームをプレイできるため、知っておくと便利であり、気づかないかもしれません。ゲームパッドのサポートは、本稿執筆時点で約 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 つの質量アクチュエータによって生成される触覚フィードバック効果です。
- Trigger-Rumble: ゲームパッドの各トリガーに 1 つのモーターが配置された、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 つのアイテムが固定長です。接続されているゲームパッドが 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 ゲームパッドのデモを機能させるために、Chrome dino ゲームをコア Chromium プロジェクトから削除し(Arnelle Ballane が以前の作業を更新)、スタンドアロン サイトに配置し、ダッキング エフェクトとバイブレーション エフェクトを追加して既存のゲームパッド API 実装を拡張し、全画面モードを作成しました。また、ダーク モードを実装しました。Mehul Satarde今後ともどうぞよろしくお願いいたします。
役に立つリンク
謝辞
このドキュメントは、François Beaufort と Joe Medley によってレビューされました。Gamepad API の仕様を編集しているのは、Steve Agoston、James Hollyer、Matt Reynolds です。以前の仕様編集者は、Brandon Jones、Scott Graham、Ted Mielczarek です。ゲームパッド拡張機能の仕様は、Brandon Jones によって編集されています。ヒーロー画像 by Laura Torrent Puig。