Web Gamepad API:DirectInput、XInput、そしてマッピングの混乱

Gamepad API が与えてくれるもの

ブラウザの Gamepad API は、接続されたコントローラーを navigator.getGamepads() で公開し、Gamepad オブジェクトの配列を返します。各オブジェクトには buttons 配列——各要素は pressedtouched、0 から 1 のアナログ value を持つオブジェクト——と、アナログスティックの位置を保持する axes 配列があり、各値は -1 から 1 です。gamepadconnectedgamepaddisconnected イベントはありますが、ボタンと軸の状態が押し付けられることはありません:自分でポーリングする必要があります。

実際にはこれは、requestAnimationFrame ループ内で1フレームに1回ゲームパッドを読み、今フレームを前フレームと比較して、たった今押された/離されたボタンを検出することを意味します。得られるオブジェクトは凍結したスナップショットなので、新しい値を得るには毎フレーム getGamepads() を呼び直します。このポーリングモデルは、キーボードやポインターイベントから来た開発者が最初に驚く点です。

「標準マッピング」

仕様は標準マッピング(standard mapping)と呼ばれる正準的なレイアウトを定義します。コントローラーがそれに一致すると gamepad.mapping は文字列 standard になり、インデックスは固定で予測可能です:buttons[0] は下の face ボタン(A / クロス)、buttons[1] は右(B / サークル)、バンパー・トリガー・方向キー・スティック押し込みはすべて定義された位置にあり、axes[0] から axes[3] が左右のスティックを担います。17 個のボタンと 4 個の軸が既知の順序で並びます。

gamepad.mapping が空文字列のとき、コントローラーは標準レイアウトに一致しなかったので、それらのインデックスは1つも保証されません。同じ物理ボタンが別のインデックスに来たり、トリガーが軸として現れたり、方向キーが4つのボタンではなく単一のハット軸になったりします。どのインデックスを信じる前にも、mapping プロパティが最も重要な確認すべきフィールドです。

DirectInput 対 XInput:混乱の根源

Windows では、ゲームがコントローラーと話す方法は2つあります。XInput は Microsoft の現代的な API で、Xbox コントローラーと、あらゆる Xbox 互換パッドが従うレイアウトを中心に作られています。そのレイアウトは固定なので、ブラウザは XInput デバイスをきれいに標準マッピングへ対応づけられます——だから Xbox コントローラーは Web ゲームで「そのまま動く」のです。

DirectInput は、あらゆる HID(ヒューマンインターフェースデバイス)コントローラー向けの、より古く汎用的な API です:多くのサードパーティパッド、アーケードスティック、フライトスティック、レーシングホイール、そして古いまたは無名のコントローラー。DirectInput は固定のボタン順を強制しないので、ブラウザは対応づける正準レイアウトを持たず、空の mapping を報告します。この1つの違いが、「ブラウザでボタンがめちゃくちゃ」というほぼすべての報告の出どころです。

なぜ同じボタンが違うインデックスを報告するのか

DirectInput パッドでは、どの物理コントロールが「ボタン 1」かを決めるのはメーカーなので、あなたの A ボタンが buttons[2] として来たり、トリガーがボタンではなく axes 配列の中に現れたり、方向キーが4つの別々のボタンではなく視点(POV)ハット——8 方向を持つ1つの軸——として報告されたりします。

これらのデバイスを使えるようにするため、各ブラウザは独自の再マッピングのヒューリスティックを適用し、それらは常に一致するわけではありません:同じ DirectInput コントローラーが Chrome と Firefox で違うインデックスを生むことがあります。XInput は Microsoft がレイアウトを固定したので問題全体を回避でき、だから「Xbox 型のコントローラーを使う」が Web ゲームで最も信頼できる助言なのです。

コードでの扱い方

堅牢なパターンは、まず gamepad.mapping を確認することです。standard と等しければ、名前付きインデックスを安全に使えます。空なら何も決め打ちせず——プレイヤーに各アクションを押してもらってそのデバイスのレイアウトを学ぶ再マッピング画面を提供し、結果を gamepad.id をキーに保存して、コントローラーごとに残るようにします。

マッピングに関わらず重要な詳細がもう2つあります。アナログスティックはちょうど0で休むことはまれなので、小さなデッドゾーン(よく約 0.1)を適用し、その内側はすべて入力なしとして扱います。さもないとドリフトでキャラが勝手に動きます。そして API はポーリング式なので、毎フレーム新しくゲームパッドを読み、前フレームと差分を取って連続状態を離散的な「たった今押した」イベントに変えます。本サイトのゲームパッドテスターは、あなたの特定のデバイスが報告する生のボタンインデックスと軸値を表示します——標準マッピングかどうかを見る最速の方法です。

要点

インデックスを信じる前に必ず mapping を確認すること。 standard は仕様のレイアウト、空はデバイス固有を意味します。

Web ゲームには XInput / Xbox 型のコントローラーを優先すること——標準レイアウトに対応づきますが、DirectInput パッドは対応づきません。

非標準パッドにインデックスを決め打ちしないこと。 再マッピング UI を用意し、結果を gamepad.id ごとに保存します。

軸にデッドゾーンを適用することで、休んでいるスティックが移動として登録されないようにします。

毎フレーム ポーリングして差分を取ること——ボタンと軸について Gamepad API が与えるのは状態であって、イベントではありません。