使用游戏手柄畅玩 Chrome 恐龙游戏

了解如何使用 Gamepad API 让网页游戏更上一层楼。

Chrome 的离线网页复活节彩蛋是历史上保存最严重的秘密之一([citation needed], 但声称具有戏剧效果)。按空格键或在移动设备上 设备,点按恐龙图标,离线网页就会变为可玩的街机游戏。你或许知道 但即使你想要玩游戏,也不必离线使用它们:在 Chrome 中 发送至 about://dino,或者,如果您是一位极客,请浏览至 about://network-error/-106。但你知道吗 确保 每月玩 Chrome dino 游戏达到 2.7 亿

<ph type="x-smartling-placeholder">
</ph> Chrome 的离线网页,其中包含 Chrome dino 游戏。 <ph type="x-smartling-placeholder">
</ph> 按空格键开始游戏!

了解而您可能不知道的另一个事实是, 支持在街机模式下使用游戏手柄玩游戏大约一年前,我们添加了对游戏手柄的支持, 这篇 commit(提交者: Reilly Grant。如您所见 Chromium 项目,已完整安装 开源。在 我想向大家展示如何使用 Gamepad API

使用 Gamepad API

功能检测和浏览器支持

Gamepad API 在 Android 和 Google Play 上提供普遍出色的浏览器支持 桌面设备和移动设备您可以使用以下代码段检测 Gamepad API 是否受支持:

if ('getGamepads' in navigator) {
  // The API is supported!
}

浏览器如何代表游戏手柄

浏览器将游戏手柄表示为 Gamepad 对象的操作。Gamepad 具有以下属性:

  • id:游戏手柄的标识字符串。此字符串用于标识 。
  • displayId: 第 VRDisplay.displayId 行,共 关联的VRDisplay(如果相关)。
  • index:游戏手柄在导航器中的索引。
  • connected:指示游戏手柄是否仍与系统连接。
  • hand:用于定义控制器持握或最有可能握持的手的枚举 位置
  • timestamp:上次更新此游戏手柄数据的时间。
  • mapping:此设备使用的按钮和轴映射,可以为 "standard""xr-standard"
  • pose:一个 GamepadPose 对象 表示与 WebVR 控制器相关联的姿势信息。
  • axes:游戏手柄所有轴的值数组,线性归一化为 -1.0 - 1.0
  • buttons:游戏手柄上所有按钮的按钮状态数组。

请注意,按钮可以是数字(已按下或未按下)按钮,也可以是模拟按钮(例如,按下 78% 的按钮)。本次 因此按钮被报告为 GamepadButton 对象的原因,这些对象具有以下属性:

  • pressed:按钮的按下状态(如果按下按钮,则为 truefalse
  • touched:按钮的轻触状态。如果按钮能够检测触摸,则 如果轻触按钮,则该属性为 true,否则为 false
  • value:对于配有模拟传感器的按钮,此属性表示 按钮,并以 0.0 - 1.0 这一范围线性归一化。
  • hapticActuators:包含以下内容的数组 GamepadHapticActuator 对象,每个对象都代表控制器上可用的触感反馈硬件。

根据您所使用的浏览器和游戏手柄,您可能还会遇到 是 vibrationActuator 属性。它支持两种混响效果:

  • 双重撞击:通过两个偏心旋转质量致动器(每次握持游戏手柄一个)产生的触感反馈效果。
  • 触发器-重鸣:由两个独立电机产生的触感反馈效果,其中每个游戏手柄触发器上都有一个电机。

以下示意图概览(截取自) 与规范完全不同 显示了一般游戏手柄上按钮和轴的映射及排列方式。

<ph type="x-smartling-placeholder">
</ph> 常见游戏手柄按钮和轴映射的示意图。 <ph type="x-smartling-placeholder">
</ph> 标准游戏手柄布局的直观表示 (来源)。

连接游戏手柄时收到通知

要了解游戏手柄的连接情况,请监听在游戏手柄上触发的 gamepadconnected 事件 window 对象。当用户通过 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 中的数组始终具有固定长度的四项内容。小于或等于 0 时 连接了四个游戏手柄,那么一个项目可能只是 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:设置振动开始前的延迟时长。
  • strongMagnitudeweakMagnitude:设置较重和 较轻的偏心旋转质量马达,标准化范围为 0.01.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() 就会触发 gamepadconnectedgamepaddisconnected 事件。

<iframe src="index.html" allow="gamepad"></iframe>

演示

以下示例中嵌入了游戏手柄测试工具演示。源代码 可在 Glitch 上获取。连接一个 使用 USB 或蓝牙并按下游戏手柄的任意按钮或移动游戏手柄的任意轴。

奖励:在 web.dev 上玩 Chrome dino

您可以在此设备上使用游戏手柄玩 Chrome dinoGitHub 上提供了源代码。 如需查看游戏手柄轮询实现,请参阅 trex-runner.js 并注意它是如何模拟按键操作的。

为了让 Chrome dino 游戏手柄演示正常运行,我 从核心 Chromium 项目中淘汰了 Chrome dino 游戏(更新 之前所做的努力 Arnelle Ballane)将其放置在一个独立网站上, 通过添加闪避和振动效果来实现现有的游戏手柄 API,从而创建全屏 模式,Mehul Satardekar 也贡献了深色模式 实施。祝您游戏愉快!

致谢

本文档由 François Beaufort 审核, Joe Medley。Gamepad API 规范的修改者 Steve AgostonJames HollyerMatt Reynolds。之前的规范编辑器是 Brandon JonesScott GrahamTed Mielczarek。游戏手柄扩展程序规范 Brandon Jones。主打图片:Laura Torrent Puig。