刷新的 Stadia 控制器就如同一般遊戲手把,因此並非所有按鈕都能透過 Gamepad API 存取。透過 WebHID,您現在可以存取缺少的按鈕。
隨著 Stadia 關機,許多控制器最終覺得,控制器會成為垃圾掩埋場的無用硬體。幸好,Stadia 團隊已決定改為開啟 Stadia 控制器,方法是提供自訂韌體。你可以在 Stadia 藍牙模式頁面中刷新裝置。讓 Stadia 控制器顯示為標準遊戲搖桿,你可以透過 USB 傳輸線或藍牙以無線方式連接。在 Project Fugu API 展示櫃中主打這項特色,Stadia 藍牙頁面本身使用 WebHID 和 WebUSB,但本文主題不完全提及。在這篇文章中,我想向你說明如何透過 WebHID 與 Stadia 控制器通訊。
將 Stadia 控制器當成標準遊戲搖桿
刷新後,控制器會以標準遊戲手把顯示在作業系統中。請參閱以下螢幕截圖,瞭解標準遊戲手把上的常用按鈕和軸排列方式。如 Gamepad API 規格定義,標準遊戲搖桿有 0 到 16 個按鈕,因此總共有 17 個按鈕 (D-Pad 計為四個按鈕)。如果您嘗試透過遊戲控制器測試版使用 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 控制器 Rev. A」裝置後,請將產生的 HIDDevice
物件記錄到控制台。這項操作會顯示 Stadia 控制器的 productId
(37888
,是十六進位的 0x9400
) 和 vendorId
(6353
,以十六進位為單位的 0x18d1
)。在官方 USB 供應商 ID 表格中查看 vendorID
時,您會看到 6353
對應至預期內容:Google Inc.
。
除了上述流程之外,你也可以透過網址列前往「chrome://device-log/
」、按下「清除」按鈕,將 Stadia 控制器接上電源,然後按下「重新整理」。如此一來,您也會得到相同的資訊。
但另一種方法是使用 HID Explorer 工具,進一步探索與您電腦連線的 HID 裝置詳細資料。
使用 vendorId
和 productId
這兩個 ID,即可正確篩選正確的 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 助理」的向下按鈕和「Google 助理向上按鈕」事件也會開始變成可解讀的事件。第一個整數 (兩個事件中的 8
) 似乎與按下按鈕動作有關,而第二個整數 (2
和 0
) 似乎與是否按下「Google 助理」按鈕有關。
按下「Capture」按鈕,而不是「Assistant」按鈕,當按鈕放開時,按下按鈕就會從 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;
}
操作示範
你可以在我製作的示範中,查看由 Gamepad API 和 WebHID API 共同控管的 Stadia 控制器。請務必查看以本文程式碼片段為基礎所構成的原始碼。為了方便起見,我只顯示「A」、「B」、「X」和「Y」按鈕 (由 Gamepad API 控制),以及「Assistant」和「Capture」按鈕 (由 WebHID API 控制)。控制器圖片下方會顯示原始 WebHID 資料,方便您感受控制器的所有按鈕和軸。
結論
透過新的韌體,Stadia 控制器現在可做為一般遊戲手把使用,而且在大多數情況下 (17 個按鈕) 就已足以控制一般網路遊戲。不論原因為何,如果您需要所有 19 個按鈕的資料,WebHID 都可讓您存取低階輸入報表,然後逐一進行反向工程,藉此加以解讀。如果您閱讀本文後,剛好編寫完整的 WebHID 驅動程式,請務必與我聯絡,我會將您的專案連結在一起。WebHIDing 快樂!
特別銘謝
本文是由 François Beaufort 審查。