フラッシュされた Stadia コントローラは標準のゲームパッドのように動作します。つまり、Gamepad API を使用してアクセスできるボタンはすべてではありません。WebHID を使用すると、見つからなかったボタンにアクセスできるようになります。
Stadia の終了後、コントローラがゴミ埋め立て地に捨てられるのではないかと多くの人が心配していました。幸い、Stadia チームは、Stadia Bluetooth モードのページにアクセスしてコントローラに書き込むことができるカスタム ファームウェアを提供することで、Stadia コントローラをオープンソース化することにしました。これにより、Stadia コントローラが標準のゲームパッドとして表示され、USB ケーブルまたは Bluetooth 経由でワイヤレスで接続できるようになります。Project Fugu API Showcase で紹介されている Stadia Bluetooth ページ自体は WebHID と WebUSB を使用していますが、この記事では取り上げません。この記事では、WebHID を介して Stadia コントローラと通信する方法について説明します。
標準のゲームパッドとしての Stadia コントローラ
フラッシュ後、コントローラはオペレーティング システムに標準ゲームパッドとして表示されます。標準のゲームパッドの一般的なボタンと軸の配置については、次のスクリーンショットを参照してください。Gamepad API の仕様で定義されているように、標準のゲームパッドは 0 ~ 16 個のボタン(合計 17 個)を備えています(十字キーは 4 個のボタンとしてカウントされます)。ゲームパッド テスターのデモで Stadia コントローラを試すと、問題なく動作することがわかります。
ただし、Stadia コントローラのボタンの数は 19 個です。ゲームパッド テスターでボタンを 1 つずつ試すと、アシスタントボタンとキャプチャボタンが機能しないことがわかります。ゲームパッド仕様で定義されているゲームパッド buttons
属性がオープンエンドの場合でも、Stadia コントローラは標準のゲームパッドとして表示されるため、ボタン 0 ~ 16 のみがマッピングされます。他のボタンは引き続き使用できますが、ほとんどのゲームでは存在しないものと見なされます。
WebHID の活用
WebHID API を使用すると、不足しているボタン 17 と 18 に話しかけることができます。必要に応じて、Gamepad API ですでに利用可能な他のすべてのボタンと軸に関するデータも取得できます。最初のステップは、Stadia コントローラがオペレーティング システムに自身を報告する方法を確認することです。たとえば、任意のページで Chrome DevTools コンソールを開き、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 コントローラを接続し、[更新] を押します。同じ情報が提供されます。
別の方法として、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
)は [アシスタント] ボタンが押されたかどうかに関連しているようです。
アシスタント ボタンではなくキャプチャ ボタンを押すと、2 番目の整数値が、ボタンを押すと 1
から 0
に切り替わり、ボタンを離すと 0
から 1
に切り替わります。これにより、不足している 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 デバイスを使用するには、最初の選択プロセスを必ず 1 回行う必要がありますが、今後の接続では、既知のデバイスに再接続できます。そのためには、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 さんが確認しました。