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

Gamepad API を使用してウェブゲームをレベルアップする方法をご紹介します。

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

Chrome Dino ゲームが表示された Chrome のオフライン ページ。
Space キーを押してください。

もう一つ、アーケード モードではゲームパッドでゲームをプレイできるため、知っておくと便利であり、気づかないかもしれません。ゲームパッドのサポートは、本稿執筆時点で約 1 年前に Reilly Grant によるコミットとして追加されました。ご覧のとおり、このゲームは他の 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 つの質量アクチュエータによって生成される触覚フィードバック効果です。
  • 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 コントローラを取り外すと、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' のみです。デュアルランブルは、標準的なゲームパッドの各ハンドルに偏心回転質量振動モーターがある触覚構成を表します。この構成では、いずれかのモーターでゲームパッド全体を振動させることができます。2 つの質量は等しくないため、それぞれの効果を組み合わせてより複雑な触覚効果を作成できます。デュアルランブル エフェクトは、次の 4 つのパラメータで定義されます。

  • duration: バイブレーション効果の持続時間をミリ秒単位で設定します。
  • startDelay: バイブレーションが開始されるまでの遅延時間を設定します。
  • strongMagnitudeweakMagnitude: 重くて軽量の偏心回転質量モーターの振動強度レベルを設定します。0.01.0 の範囲に正規化されます。

サポートされているランブル効果

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.
}

デュアルランブル

// 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,
  });
};

ランブルをトリガー

// 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 BeaufortJoe Medley によってレビューされました。Gamepad API の仕様を編集しているのは、Steve AgostonJames HollyerMatt Reynolds です。以前の仕様編集者は、Brandon JonesScott GrahamTed Mielczarek です。ゲームパッド拡張機能の仕様は、Brandon Jones によって編集されています。ヒーロー画像 by Laura Torrent Puig。