Web Gamepad API:DirectInput、XInput,與對應的混亂

Gamepad API 給你什麼

瀏覽器的 Gamepad API 透過 navigator.getGamepads() 暴露已連接的控制器,它回傳一個 Gamepad 物件的陣列。每個物件有一個 buttons 陣列——每一項都是含 pressedtouched 和 0 到 1 類比 value 的物件——以及一個裝著類比搖桿位置的 axes 陣列,每個值從 -1 到 1。有 gamepadconnectedgamepaddisconnected 事件,但按鍵與軸的狀態永遠不會主動推給你:你必須輪詢它。

實務上這意味著在 requestAnimationFrame 迴圈裡每幀讀一次手把,並把這一幀和上一幀比較,以偵測剛被按下或放開的按鍵。你拿到的物件是一個凍結的快照,所以你每幀都要再呼叫一次 getGamepads() 取得新值。這種輪詢模型,是從鍵盤或指標事件過來的開發者第一個會驚訝的地方。

「標準對應」

規格定義了一個稱為標準對應(standard mapping)的標準佈局。當控制器符合它,gamepad.mapping 就是字串 standard,而索引是固定且可預期的:buttons[0] 是底部的面鍵(A / 叉),buttons[1] 是右邊那個(B / 圈),肩鍵、扳機、方向鍵和搖桿按壓全都在定義好的位置,axes[0]axes[3] 則承載左右搖桿。十七個按鍵、四個軸,順序已知。

gamepad.mapping 是空字串,代表控制器沒有符合標準佈局,那些索引一個都不保證。同一個實體按鍵可能落在不同索引、扳機可能以軸的形式出現、方向鍵可能是單一的帽狀軸而非四個按鍵。在你信任任何索引之前,mapping 屬性是最重要、要先檢查的欄位。

DirectInput 對 XInput:混亂的根源

在 Windows 上,遊戲和控制器溝通有兩種方式。XInput 是微軟的現代 API,圍繞 Xbox 控制器和每個 Xbox 相容手把所遵循的佈局而建。因為那個佈局是固定的,瀏覽器能把 XInput 裝置乾淨地對應到標準對應——這就是為何 Xbox 控制器在網頁遊戲裡「直接就能用」。

DirectInput 是較舊、通用的 API,給任何 HID(人機介面裝置)控制器用:許多第三方手把、街機搖桿、飛行搖桿、賽車方向盤,以及較舊或無名的控制器。DirectInput 不強制固定的按鍵順序,所以瀏覽器沒有標準佈局可對應,會回報一個空的 mapping。這單一差異,幾乎是每一則「我的按鍵在瀏覽器裡錯亂了」回報的來源。

為何同一個按鍵回報不同索引

用 DirectInput 手把時,是製造商決定哪個實體控制是「按鍵 1」,所以你的 A 鍵可能以 buttons[2] 抵達、扳機可能出現在 axes 陣列裡而非按鍵、方向鍵可能被回報成一個視點(POV)帽狀軸——一個有八個方向的軸——而非四個分開的按鍵。

為了讓這些裝置可用,每個瀏覽器都套用自己的重映射啟發法,而它們並不總是一致:同一個 DirectInput 控制器在 Chrome 和 Firefox 裡可能產生不同索引。XInput 繞開了整個問題,因為微軟把佈局固定了,這就是為何「用 Xbox 型的控制器」是網頁遊戲最可靠的建議。

在程式碼裡處理

穩健的做法是先檢查 gamepad.mapping。如果它等於 standard,你就能安全使用具名索引。如果它是空的,別寫死任何東西——提供一個重映射畫面,請玩家按下每個動作,讓你學到那個裝置的佈局,並把結果以 gamepad.id 為鍵儲存,使它能逐個控制器持久保存。

另有兩個細節,無論對應為何都重要。類比搖桿很少剛好停在零,所以套一個小的死區(dead zone)(通常約 0.1),把死區內的一切當成無輸入,否則漂移會讓角色自己移動。而因為這個 API 是輪詢式的,每幀重新讀手把,並和上一幀比對,把連續狀態變成離散的「剛按下」事件。本站的手把測試器會顯示你的特定裝置回報的原始按鍵索引和軸值——這是看出它是否為標準對應的最快方法。

要點

信任索引之前,永遠先檢查 mapping standard 代表規格佈局;空的代表裝置特定。

網頁遊戲優先用 XInput / Xbox 型控制器——它們對應到標準佈局,而 DirectInput 手把不會。

絕不為非標準手把寫死索引。 提供重映射 UI,並以 gamepad.id 為鍵儲存結果。

對軸套用死區,讓靜止的搖桿不會被當成移動。

每幀輪詢並比對——對按鍵和軸而言,Gamepad API 給你的是狀態,不是事件。