了解如何使用 Gamepad API 让您的网页游戏更上一层楼。
Chrome 的离线页面复活节彩蛋是历史上保存最差的秘密之一([citation needed]
,但声称可以实现惊人的效果)。如果您按空格键或在移动设备上点按恐龙,离线页面就变成了街机游戏。您可能知道,在享受游戏乐趣时,其实并不需要离线:在 Chrome 中,只需转到 about://dino
即可;如果您是极客,也可以浏览到 about://network-error/-106
。但您知道吗?每月有 2.7 亿 Chrome 恐龙游戏玩。
![包含 Chrome 恐龙游戏的 Chrome 离线网页。](https://web.developers.google.cn/static/articles/gamepad/image/chromes-offline-page-th-3d966d4a2f411.png?authuser=5&hl=sv)
另一个事实可能更值得了解,但您可能不知道,那就是在街机模式下,您可以使用游戏手柄来玩游戏。游戏手柄支持大约在一年前就由 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
属性。它支持两种混战效果:
- Dual-Rumble:一种触感反馈效果,由两个偏心的旋转质量致动器产生,在游戏手柄上每个手柄各有一个。
- Trigger-Rumble:一种触感反馈效果,由两个独立的电机产生,一个电机位于游戏手柄的每个触发器上。
以下示意图直接取自规范,显示了通用游戏手柄上按钮和轴的映射及排列方式。
游戏手柄连接时的通知
如需了解游戏手柄的连接时间,请监听对 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 个,则 item 可能只是 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.
}
对决
Dual-Rumble 是指标准游戏手柄的每个手柄上有一个离心的旋转质量振动电机。在此配置中,任一电机都能振动整个游戏手柄。这两个质量不相等,因此可以组合各自的效果来制作更复杂的触感反馈效果。双重 rumble 效果由四个参数定义:
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 dino
您可以在这个网站上使用游戏手柄玩 Chrome dino。GitHub 上提供了源代码。查看 trex-runner.js
中的游戏手柄轮询实现,并注意它如何模拟按键操作。
为了让 Chrome dino 游戏手柄演示能够正常发挥作用,我从核心 Chromium 项目中剥离了 Chrome 恐龙游戏(更新了 Arnelle Ballane 之前所做的一项工作),将其放置在独立网站上,通过添加闪避和振动效果来扩展现有的游戏手柄 API 实现,创建了全屏深色模式,并贡献了 karMehul Satar 模式。祝你游戏愉快!
实用链接
致谢
本文档由 François Beaufort 和 Joe Medley 审核。Gamepad API 规范由 Steve Agoston、James Hollyer 和 Matt Reynolds 修改。之前的规范编辑者是 Brandon Jones、Scott Graham 和 Ted Mielczarek。游戏手柄扩展程序规范由 Brandon Jones 修改。主打图片:Laura Torrent Puig。