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 给你的是状态,不是事件。