刷過韌體的 Stadia 控制器會像標準遊戲搖桿一樣運作,也就是說,並非所有按鈕都能透過 Gamepad API 存取。有了 WebHID,您現在可以存取缺少的按鈕。
自從 Stadia 關閉後,許多人擔心控制器會變成垃圾場上的無用硬體。幸運的是,Stadia 團隊決定開放 Stadia 控制器,提供自訂韌體,讓你前往 Stadia 藍牙模式頁面,在控制器上刷新韌體。這樣一來,Stadia 控制器就會顯示為標準遊戲搖桿,你可以透過 USB 傳輸線或藍牙無線連線。這個頁面在 Project Fugu API Showcase 中備受推崇,使用 WebHID 和 WebUSB,但這不是本文的主題。在本篇文章中,我想說明如何透過 WebHID 與 Stadia 控制器互動。
將 Stadia 控制器當作標準遊戲搖桿
刷新後,控制器會在作業系統中顯示為標準遊戲控制器。請參閱下圖,瞭解標準遊戲控制器上常見的按鈕和軸配置。如Gamepad API 規範所定義,標準 Gamepad 的按鈕數量為 0 到 16 之間,因此總共 17 個 (方向鍵算為四個按鈕)。如果你在遊戲控制器測試器示範中試用 Stadia 控制器,就會發現它運作得非常順暢。
不過,如果算上 Stadia 控制器上的按鈕,總共有 19 個。如果你有系統地在遊戲控制器測試器中逐一嘗試,就會發現「Google 助理」和「拍照」按鈕無法正常運作。即使遊戲手把規格中定義的遊戲手把 buttons
屬性 是開放式的,由於 Stadia 控制器會顯示為標準遊戲手把,因此只會對應按鈕 0 到 16。您還是可以使用其他按鈕,但大多數遊戲不會提供這些按鈕。
WebHID 救星
有了 WebHID API,您就可以與缺少的按鈕 17 和 18 對話。如有需要,您甚至可以透過 Gamepad API 取得所有其他按鈕和軸的資料。首先,請找出 Stadia 控制器如何向作業系統回報自身狀態。其中一種方法是在任一隨機頁面上開啟 Chrome 開發人員工具主控台,然後透過 WebHID API 要求未經過篩選的裝置清單。接著,您可以手動選擇 Stadia 控制器進行進一步檢查。只要傳遞空白的 filters
選項陣列,即可取得未篩選的裝置清單。
const [device] = await navigator.hid.requestDevice({filters: []});
在挑選器中,倒數第二個項目看起來像是 Stadia 控制器。
選取「Stadia Controller rev. A」裝置後,請將產生的 HIDDevice
物件記錄到控制台。這會顯示 Stadia 控制器的 productId
(37888
,十六進制為 0x9400
) 和 vendorId
(6353
,十六進制為 0x18d1
)。在官方 USB 供應商 ID 表中查詢 vendorID
,您會發現 6353
對應的值與預期的 Google Inc.
相同。
除了上述流程,您也可以前往網址列中的 chrome://device-log/
,按下「Clear」按鈕,插入 Stadia 控制器,然後按下「Refresh」。這兩種方法會提供相同的資訊。
另一個替代方案是使用 HID Explorer 工具,進一步探索電腦上已連線的 HID 裝置。
使用這兩個 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);
});
當你按下並放開控制器上的「Google 助理」按鈕時,控制台會記錄兩個事件。您可以將這些事件視為「Google 助理按鈕按下」和「Google 助理按鈕按上」事件。除了 timeStamp
之外,這兩個事件乍看之下似乎沒有差異。
HIDInputReportEvent
介面的 reportId
屬性會傳回此報表的一字節識別字首碼,如果 HID 介面未使用報表 ID,則會傳回 0
。在這種情況下,是 3
。密鑰位於 data
屬性中,以大小為 10 的 DataView
表示。DataView
提供低階介面,可在二進位 ArrayBuffer
中讀取及寫入多個數字類型。如要從這項表示法中取得更易消化的內容,請使用 ArrayBuffer
建立 Uint8Array
,這樣您就能看到個別的 8 位元無號整數。
const data = new Uint8Array(event.data.buffer);
接著,當您再次記錄輸入報表事件資料時,系統就會開始解讀「Google 助理」按鈕按下和按鈕按下的事件。第一個整數 (兩個事件中的 8
) 似乎與按下按鈕有關,而第二個整數 (2
和 0
) 似乎與是否按下 Google 助理按鈕有關。
按下「Capture」按鈕,而不是「Google 助理」按鈕,您會發現第二個整數會在按下按鈕時切換為 1
,在放開按鈕時切換為 0
。這樣一來,您就能編寫非常簡單的「驅動程式」,以便使用缺少的兩個按鈕。
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;
}
示範
您可以在我建立的示範中,看到 Stadia 控制器同時受到 Gamepad API 和 WebHID API 控制。請務必查看原始碼,這是根據本文的程式碼片段建立而成。為了簡化說明,我只會顯示 A、B、X 和 Y 按鈕 (由 Gamepad API 控制),以及 Assistant 和 Capture 按鈕 (由 WebHID API 控制)。控制器圖片下方會顯示原始 WebHID 資料,讓您瞭解控制器上的所有按鈕和軸。
結論
有了新的韌體,Stadia 控制器現在可做為標準的 17 鍵遊戲搖桿使用,在大多數情況下,這足以控制常見的網路遊戲。無論出於何種原因,如果您需要控制器上所有 19 個按鈕的資料,WebHID 可讓您存取低階輸入報告,然後逐一反向工程解讀。如果您在閱讀本文後撰寫了完整的 WebHID 驅動程式,請務必與我聯絡,我很樂意將您的專案連結至此。祝您使用 WebHIDing 愉快!
特別銘謝
本文由 François Beaufort 審查。