フラッシュされた Stadia コントローラは標準のゲームパッドのように動作します。つまり、Gamepad API を使用してアクセスできないボタンもあります。WebHID を使用すると、見つからなかったボタンにアクセスできるようになりました。
Stadia のサービス終了後、多くの人がコントローラが埋め立て地の無用の長物になることを恐れていました。幸いなことに、Stadia チームは、Stadia Bluetooth モードのページにアクセスしてコントローラに書き込めるカスタム ファームウェアを提供することで、Stadia コントローラをオープンにすることを決定しました。これにより、Stadia コントローラが標準のゲームパッドとして認識され、USB ケーブルまたは Bluetooth 経由でワイヤレスに接続できるようになります。Stadia Bluetooth ページは Project Fugu API Showcase で紹介されています。このページ自体は WebHID と WebUSB を使用していますが、この記事では取り上げません。この投稿では、WebHID を介して Stadia コントローラと通信する方法について説明します。
標準のゲームパッドとしての Stadia コントローラ
フラッシュ後、コントローラはオペレーティング システムに標準のゲームパッドとして認識されます。標準的なゲームパッドの一般的なボタンと軸の配置については、次のスクリーンショットをご覧ください。Gamepad API 仕様で定義されているように、標準のゲームパッドには 0 ~ 16 のボタン(合計 17 個)があります(方向パッドは 4 個のボタンとしてカウントされます)。ゲームパッド テスターのデモで Stadia コントローラを試すと、問題なく動作することがわかります。
ただし、Stadia コントローラのボタンの数を数えると 19 個あります。ゲームパッド テスターでボタンを 1 つずつ試していくと、アシスタント ボタンとキャプチャ ボタンが機能しないことがわかります。Gamepad 仕様で定義されているゲームパッドの buttons
属性がオープンエンドの場合でも、Stadia コントローラは標準のゲームパッドとして認識されるため、ボタン 0 ~ 16 のみがマッピングされます。他のボタンは引き続き使用できますが、ほとんどのゲームではこれらのボタンの存在は想定されていません。
WebHID の活用
WebHID API により、欠落しているボタン 17 と 18 に対応できます。必要に応じて、Gamepad API で利用可能な他のすべてのボタンと軸に関するデータを取得することもできます。まず、Stadia コントローラがオペレーティング システムにどのようにレポートされるかを確認します。その方法の 1 つは、任意のページで Chrome DevTools Console を開き、WebHID API からフィルタリングされていないデバイスのリストをリクエストすることです。その後、手動で Stadia コントローラを選択してさらに検査します。空の filters
オプション配列を渡すだけで、フィルタリングされていないデバイスのリストを取得できます。
const [device] = await navigator.hid.requestDevice({filters: []});
選択ツールで、最後から 2 番目のエントリが Stadia コントローラのように表示されます。
「Stadia Controller rev. A」デバイスを選択したら、結果の HIDDevice
オブジェクトをコンソールに記録します。これにより、Stadia コントローラの productId
(37888
、16 進数では 0x9400
)と vendorId
(6353
、16 進数では 0x18d1
)が表示されます。公式の USB ベンダー ID テーブルで vendorID
を検索すると、6353
が Google Inc.
にマッピングされていることがわかります。
上記のフローの代替手段として、URL バーで chrome://device-log/
に移動し、[クリア] ボタンを押して、Stadia コントローラを接続し、[更新] を押す方法もあります。これにより、同じ情報が提供されます。
もう 1 つの代替手段として、HID Explorer ツールを使用する方法があります。このツールを使用すると、パソコンに接続されている HID デバイスの詳細をさらに詳しく調べることができます。
これらの 2 つの ID(vendorId
と productId
)を使用して、適切な WebHID デバイスを正しくフィルタリングすることで、ピッカーに表示される内容を絞り込みます。
const [stadiaController] = await navigator.hid.requestDevice({filters: [{
vendorId: 6353,
productId: 37888,
}]});
これで、関係のないデバイスのノイズが消え、Stadia コントローラのみが表示されます。
次に、open()
メソッドを呼び出して HIDDevice
を開きます。
await stadiaController.open();
HIDDevice
を再度ログに記録すると、opened
フラグが true
に設定されます。
デバイスが開いている状態で、イベント リスナーをアタッチして、受信した inputreport
イベントをリッスンします。
stadiaController.addEventListener('inputreport', (e) => {
console.log(e);
});
コントローラの [アシスタント] ボタンを押して離すと、2 つのイベントがコンソールに記録されます。これらは「アシスタント ボタンを押す」イベントと「アシスタント ボタンを離す」イベントと考えることができます。timeStamp
を除くと、2 つのイベントは一見区別できません。
HIDInputReportEvent
インターフェースの reportId
プロパティは、このレポートの 1 バイトの識別接頭辞を返します。HID インターフェースがレポート ID を使用しない場合は 0
を返します。この場合は 3
です。シークレットは data
プロパティにあり、サイズ 10 の DataView
として表されます。DataView
は、バイナリ ArrayBuffer
で複数の数値型を読み書きするための低レベル インターフェースを提供します。この表現からより理解しやすいものを得るには、ArrayBuffer
から Uint8Array
を作成して、個々の 8 ビット符号なし整数を確認します。
const data = new Uint8Array(event.data.buffer);
入力レポート イベントデータを再度記録すると、事態がより明確になり、「アシスタント ボタンダウン」イベントと「アシスタント ボタンアップ」イベントが解読可能になります。最初の整数(両方のイベントで 8
)はボタンの押下に関連していると思われ、2 番目の整数(2
と 0
)は アシスタント ボタンが押されたかどうかに関連していると思われます。
アシスタント ボタンではなく、キャプチャ ボタンを押すと、ボタンを押したときに 1
から 0
に切り替わる 2 番目の整数が表示されます。これにより、欠落している 2 つのボタンを利用できるようにする非常にシンプルな「ドライバ」を作成できます。
stadia.addEventListener('inputreport', (event) => {
if (!e.reportId === 3) {
return;
}
const data = new Uint8Array(event.data.buffer);
if (data[0] === 8) {
if (data[1] === 1) {
hidButtons[1].classList.add('highlight');
} else if (data[1] === 2) {
hidButtons[0].classList.add('highlight');
} else if (data[1] === 3) {
hidButtons[0].classList.add('highlight');
hidButtons[1].classList.add('highlight');
} else {
hidButtons[0].classList.remove('highlight');
hidButtons[1].classList.remove('highlight');
}
}
});
このようなリバース エンジニアリングのアプローチを使用すると、ボタンごと、軸ごとに、WebHID を使用して Stadia コントローラと通信する方法を把握できます。コツさえつかめば、あとはほとんど機械的な整数マッピング作業です。
現在不足しているのは、Gamepad API が提供するスムーズな接続エクスペリエンスです。セキュリティ上の理由から、Stadia コントローラなどの WebHID デバイスを使用するには、初回に一度だけピッカーの操作を行う必要がありますが、次回以降の接続では、既知のデバイスに再接続できます。そのためには、getDevices()
メソッドを呼び出します。
let stadiaController;
const [device] = await navigator.hid.getDevices();
if (device && device.vendorId === 6353 && device.productId === 37888) {
stadiaController = device;
}
デモ
私が作成したデモで、Gamepad API と WebHID API によって共同で制御される Stadia コントローラを確認できます。この記事のスニペットを基に作成されたソースコードもぜひご覧ください。簡略化のため、A、B、X、Y ボタン(Gamepad API で制御)と、アシスタント ボタンとキャプチャ ボタン(WebHID API で制御)のみを表示しています。コントローラの画像の下に WebHID の生データが表示されるため、コントローラのすべてのボタンと軸を把握できます。
まとめ
新しいファームウェアにより、Stadia コントローラは 17 個のボタンを備えた標準のゲームパッドとして使用できるようになりました。これは、ほとんどの場合、一般的なウェブゲームを操作するのに十分な数です。なんらかの理由でコントローラの 19 個のボタンすべてのデータが必要な場合、WebHID を使用すると、低レベルの入力レポートにアクセスできます。これらのレポートは、リバース エンジニアリングによって 1 つずつ解読できます。この記事を読んだ後に完全な WebHID ドライバを作成した場合は、ぜひご連絡ください。このページでプロジェクトへのリンクを掲載させていただきます。WebHID をお楽しみください。
謝辞
この記事は François Beaufort によってレビューされました。