ゲームパッドで Chrome Dino ゲームをプレイする

Gamepad API を使用してウェブゲームを次のレベルに引き上げる方法を学びます。

Chrome のオフライン ページのイースター エッグは、史上最も秘密が漏洩した秘密の 1 つです([citation needed]、ただし、ドラマチックな効果を狙って主張されたものです)。スペース キーを押すか、モバイル デバイスで恐竜をタップすると、オフライン ページがプレイ可能なアーケード ゲームになります。ゲームをプレイするときにオフラインにする必要がないことはご存じでしょう。Chrome では、about://dino に移動するだけで、または about://network-error/-106 に移動するだけで、ところで、毎月 2 億 7,000 万人が Chrome Dino ゲームをプレイしていることをご存じですか?

Chrome の恐竜ゲームが表示されている Chrome のオフライン ページ。
Space キーを押すとプレイできます。

アーケード モードではゲームパッドでゲームをプレイできることは、知っておくと便利な情報ですが、あまり知られていないかもしれません。ゲームパッド サポートは、この記事の執筆時点で約 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: 関連付けられている VRDisplayVRDisplay.displayId(該当する場合)。
  • index: ナビゲータ内のゲームパッドの索引。
  • connected: ゲームパッドがシステムに接続されているかどうかを示します。
  • hand: コントローラが保持されている手、または保持される可能性が高い手を定義する列挙型。
  • timestamp: このゲームパッドのデータを最後に更新した日時。
  • mapping: このデバイスで使用されているボタンと軸のマッピング("standard" または "xr-standard")。
  • pose: WebVR コントローラに関連付けられたポーズ情報を表す GamepadPose オブジェクト。
  • axes: ゲームパッドのすべての軸の値の配列。-1.01.0 の範囲に線形正規化されます。
  • buttons: ゲームパッドのすべてのボタンのボタン状態の配列。

ボタンはデジタル(押されている、押されていない)またはアナログ(78% 押されているなど)にできます。そのため、ボタンは GamepadButton オブジェクトとして報告され、次の属性が設定されます。

  • pressed: ボタンの押された状態(ボタンが押されている場合は true、押されていない場合は false)。
  • touched: ボタンのタップ状態。ボタンがタップ検出に対応している場合、このプロパティは、ボタンがタップされている場合は true、それ以外の場合は false です。
  • value: アナログ センサーを備えたボタンの場合、このプロパティは、ボタンが押された量を表します。この値は、0.01.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 コントローラを取り外すと、connectedfalse に変わっています。

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: バイブレーションが開始されるまでの遅延時間を設定します。
  • strongMagnitudeweakMagnitude: 0.01.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,
  });
};

トリガー ランブル

トリガー ランブルは、ゲームパッドのトリガーにそれぞれ 1 つのモーターが配置された 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>

デモ

次の例には、ゲームパッド テスターのデモが埋め込まれています。ソースコードは 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 BeaufortJoe Medley が確認しました。Gamepad API の仕様は、Steve AgostonJames HollyerMatt Reynolds によって編集されます。以前の仕様編集者は、Brandon JonesScott GrahamTed Mielczarek です。Gamepad Extensions の仕様は Brandon Jones が編集しています。ヒーロー画像: Laura Torrent Puig によるもの。