了解如何使用 Gamepad API 将您的 Web 游戏提升到新的水平。
Chrome 的离线页面彩蛋是历史上最不为人知的秘密之一([citation needed]
,但这说法是为了戏剧效果)。如果您按下 space 键,或者在移动设备上,点击恐龙,离线页面就会变成可玩的游戏机游戏。您可能知道,当您想玩游戏时,实际上不必离线:在 Chrome 中,您只需导航到 about://dino
,或者对于您这样的技术爱好者,浏览到 about://network-error/-106
。但是您知道吗,每个月有 2.7 亿 Chrome 恐龙游戏被游玩?

另一个可能更有用且您可能不知道的事实是,在游戏机模式下,您可以使用手柄玩游戏。手柄支持大约在一年前添加到游戏中,撰写本文时,提交 者是 Reilly Grant。正如您所见,这款游戏就像 Chromium 项目的其余部分一样,完全是开源的。在这篇文章中,我想向您展示如何使用 Gamepad API。
使用 Gamepad API
功能检测和浏览器支持
Gamepad API 在桌面和移动设备上都具有普遍良好的浏览器支持。您可以使用以下代码段检测是否支持 Gamepad API
if ('getGamepads' in navigator) {
// The API is supported!
}
浏览器如何表示手柄
浏览器将手柄表示为 Gamepad
对象。Gamepad
具有以下属性
id
:手柄的标识字符串。此字符串标识已连接手柄设备的品牌或样式。displayId
:关联的VRDisplay
的VRDisplay.displayId
(如果相关)。index
:导航器中手柄的索引。connected
:指示手柄是否仍连接到系统。hand
:一个枚举,定义控制器被握在哪个手上,或最有可能被握在哪个手上。timestamp
:上次更新此手柄数据的时间。mapping
:此设备使用的按钮和轴映射,可以是"standard"
或"xr-standard"
。pose
:一个GamepadPose
对象,表示与 WebVR 控制器关联的姿势信息。axes
:手柄所有轴的值数组,线性归一化到-1.0
–1.0
范围。buttons
:手柄所有按钮的按钮状态数组。
请注意,按钮可以是数字式的(按下或未按下)或模拟式的(例如,按下 78%)。这就是为什么按钮被报告为 GamepadButton
对象,具有以下属性
pressed
:按钮的按下状态(如果按钮被按下,则为true
,如果未按下,则为false
)。touched
:按钮的触摸状态。如果按钮能够检测触摸,则当按钮被触摸时,此属性为true
,否则为false
。value
:对于具有模拟传感器的按钮,此属性表示按钮被按下的量,线性归一化到0.0
–1.0
范围。hapticActuators
:包含GamepadHapticActuator
对象的数组,每个对象代表控制器上可用的触觉反馈硬件。
您可能会遇到的另一个额外的东西,取决于您的浏览器和您拥有的手柄,是 vibrationActuator
属性。它允许两种隆隆声效果
- 双重隆隆声:由两个偏心旋转质量致动器生成的触觉反馈效果,每个致动器位于手柄的一个握把中。
- 扳机隆隆声:由两个独立的电机生成的触觉反馈效果,一个电机位于手柄的每个扳机中。
以下示意图概述,取自规范,显示了通用手柄上按钮和轴的映射和排列。
手柄连接时的通知
要了解手柄何时连接,请监听在 window
对象上触发的 gamepadconnected
事件。当用户连接手柄时(可以通过 USB 或蓝牙连接),会触发一个 GamepadEvent
,其中手柄的详细信息在一个恰如其分的 gamepad
属性中。在下面,您可以看到我手边的一个 Xbox 360 控制器的示例(是的,我喜欢复古游戏)。
window.addEventListener('gamepadconnected', (event) => {
console.log('✅ 🎮 A gamepad was connected:', event.gamepad);
/*
gamepad: Gamepad
axes: (4) [0, 0, 0, 0]
buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
connected: true
id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
index: 0
mapping: "standard"
timestamp: 6563054.284999998
vibrationActuator: GamepadHapticActuator {type: "dual-rumble"}
*/
});
手柄断开连接时的通知
手柄断开连接的通知方式与检测连接的方式类似。这次应用程序监听 gamepaddisconnected
事件。请注意,在以下示例中,当我拔下 Xbox 360 控制器时,connected
现在为 false
。
window.addEventListener('gamepaddisconnected', (event) => {
console.log('❌ 🎮 A gamepad was disconnected:', event.gamepad);
/*
gamepad: Gamepad
axes: (4) [0, 0, 0, 0]
buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
connected: false
id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
index: 0
mapping: "standard"
timestamp: 6563054.284999998
vibrationActuator: null
*/
});
游戏循环中的手柄
获取手柄的第一步是调用 navigator.getGamepads()
,它返回一个包含 Gamepad
项的数组。Chrome 中的数组始终具有四个项的固定长度。如果连接的手柄为零个或少于四个,则项可能只是 null
。始终确保检查数组的所有项,并注意手柄“记住”它们的插槽,并且可能并非始终出现在第一个可用插槽中。
// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]
如果连接了一个或多个手柄,但 navigator.getGamepads()
仍然报告 null
项,您可能需要通过按下其任何按钮来“唤醒”每个手柄。然后,您可以像以下代码所示,在游戏循环中轮询手柄状态。
const pollGamepads = () => {
// Always call `navigator.getGamepads()` inside of
// the game loop, not outside.
const gamepads = navigator.getGamepads();
for (const gamepad of gamepads) {
// Disregard empty slots.
if (!gamepad) {
continue;
}
// Process the gamepad state.
console.log(gamepad);
}
// Call yourself upon the next animation frame.
// (Typically this happens every 60 times per second.)
window.requestAnimationFrame(pollGamepads);
};
// Kick off the initial game loop iteration.
pollGamepads();
振动致动器
vibrationActuator
属性返回一个 GamepadHapticActuator
对象,该对象对应于电机或其他致动器的配置,这些致动器可以施加力以实现触觉反馈。可以通过调用 Gamepad.vibrationActuator.playEffect()
来播放触觉效果。唯一有效的效果类型是 'dual-rumble'
和 'trigger-rumble'
。
支持的隆隆声效果
if (gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
// Trigger rumble supported.
} else if (gamepad.vibrationActuator.effects.includes('dual-rumble')) {
// Dual rumble supported.
} else {
// Rumble effects aren't supported.
}
双重隆隆声
双重隆隆声描述了一种触觉配置,其中标准手柄的每个手柄中都有一个偏心旋转质量振动电机。在这种配置中,任一电机都能够振动手柄整体。两个质量不相等,因此可以组合每个质量的效果以创建更复杂的触觉效果。双重隆隆声效果由四个参数定义
duration
:设置振动效果的持续时间,以毫秒为单位。startDelay
:设置延迟的持续时间,直到振动开始。strongMagnitude
和weakMagnitude
:设置较重和较轻的偏心旋转质量电机的振动强度级别,归一化到0.0
–1.0
范围。
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const dualRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
if (!('vibrationActuator' in gamepad)) {
return;
}
gamepad.vibrationActuator.playEffect('dual-rumble', {
// Start delay in ms.
startDelay: delay,
// Duration in ms.
duration: duration,
// The magnitude of the weak actuator (between 0 and 1).
weakMagnitude: weak,
// The magnitude of the strong actuator (between 0 and 1).
strongMagnitude: strong,
});
};
扳机隆隆声
扳机隆隆声是由两个独立的电机生成的触觉反馈效果,一个电机位于手柄的每个扳机中。
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const triggerRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
if (!('vibrationActuator' in gamepad)) {
return;
}
// Feature detection.
if (!('effects' in gamepad.vibrationActuator) || !gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
return;
}
gamepad.vibrationActuator.playEffect('trigger-rumble', {
// Duration in ms.
duration: duration,
// The left trigger (between 0 and 1).
leftTrigger: leftTrigger,
// The right trigger (between 0 and 1).
rightTrigger: rightTrigger,
});
};
与 Permissions Policy 集成
Gamepad API 规范定义了一个由字符串 "gamepad"
标识的策略控制功能。其默认 allowlist
是 "self"
。文档的权限策略确定该文档中的任何内容是否被允许访问 navigator.getGamepads()
。如果在任何文档中禁用,则该文档中的任何内容都将不允许使用 navigator.getGamepads()
,并且 gamepadconnected
和 gamepaddisconnected
事件也不会触发。
<iframe src="index.html" allow="gamepad"></iframe>
演示
以下示例中嵌入了一个 手柄测试器演示。源代码可在 Glitch 上 找到。通过 USB 或蓝牙连接手柄并按下其任何按钮或移动其任何轴来尝试演示。
奖励:在 web.dev 上玩 Chrome 恐龙游戏
您可以在本网站上使用手柄玩Chrome 恐龙。源代码可在 GitHub 上 找到。查看 trex-runner.js
中的手柄轮询实现,并注意它是如何模拟按键的。
为了使 Chrome 恐龙手柄 演示能够工作,我从核心 Chromium 项目中提取了 Chrome 恐龙游戏(更新了 Arnelle Ballane 的早期工作),将其放在一个独立的站点上,通过添加躲避和振动效果扩展了现有的手柄 API 实现,创建了全屏模式,并且 Mehul Satardekar 贡献了黑暗模式实现。祝您游戏愉快!
实用链接
致谢
本文档由 François Beaufort 和 Joe Medley 审阅。Gamepad API 规范由 Steve Agoston、James Hollyer 和 Matt Reynolds 编辑。以前的规范编辑是 Brandon Jones、Scott Graham 和 Ted Mielczarek。Gamepad Extensions 规范由 Brandon Jones 编辑。英雄图片由 Laura Torrent Puig 提供。