了解如何使用 Gamepad API 将您的 Web 游戏提升到一个新的水平。
Chrome 的离线页面彩蛋是历史上保密最差的秘密之一([citation needed]
,但声明是为了达到戏剧性效果)。如果您按 Space 键(在移动设备上,请点按恐龙),离线页面就会变成可玩的街机游戏。您可能知道,其实您无需离线即可畅玩游戏:在 Chrome 中,您只需前往 about://dino
即可;如果您是技术爱好者,则可以前往 about://network-error/-106
。但您知道吗?每个月有 2.7 亿人玩 Chrome 恐龙游戏!
还有一个可能更实用且您可能不知道的事实是,在街机模式下,您可以使用游戏手柄玩游戏。本文撰写时,游戏手柄支持大约在一年前由 Reilly Grant 在commit中添加。正如您所见,该游戏与 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 中的数组始终固定为 4 项。如果连接的游戏手柄数量为零或少于 4 个,则某个项可能只是 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,
});
};
与“权限”政策的集成
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 提供。