Узнайте, как использовать Gamepad API, чтобы вывести свои браузерные игры на новый уровень.
Пасхалка на офлайн-странице Chrome – один из самых плохо скрываемых секретов в истории ([citation needed], но это утверждение сделано для драматического эффекта). Если нажать пробел или коснуться динозавра на мобильном устройстве, страница превратится в аркадную игру. Возможно, вы знаете, что для игры не обязательно отключаться от интернета. В Chrome можно просто перейти по адресу about://dino или about://network-error/-106. Но знаете ли вы, что
каждый месяц в Chrome Dino играют 270 миллионов раз?
Ещё один факт, который, возможно, будет более полезным и о котором вы, возможно, не знаете, заключается в том, что в режиме аркады вы можете играть в игру с помощью геймпада. Поддержка геймпада была добавлена примерно год назад (на момент написания этой статьи) в коммите Райли Гранта (Reilly Grant). Как видите, игра, как и весь проект Chromium, полностью ПО с открытым исходным кодом. В этой статье я хочу показать вам, как использовать Gamepad API.
Как использовать Gamepad API
Обнаружение функций и поддержка браузеров
Gamepad API поддерживается всеми основными браузерами как на компьютерах, так и на мобильных устройствах. Чтобы определить, поддерживается ли 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– состояние кнопки (true, если кнопка нажата, иfalse, если нет).touched– состояние кнопки при нажатии. Если кнопка способна распознавать касание, это свойство имеет значениеtrue, когда кнопка нажата, иfalseв противном случае.value. Для кнопок с аналоговым датчиком это свойство представляет собой степень нажатия кнопки, линейно нормализованную в диапазоне0.0–1.0.hapticActuators– массив, содержащий объектыGamepadHapticActuator, каждый из которых представляет аппаратное обеспечение виброотклика, доступное на контроллере.
В зависимости от браузера и геймпада вы можете столкнуться со свойством vibrationActuator. Доступно два типа вибрации:
- Dual-Rumble – эффект виброотклика, создаваемый двумя эксцентриковыми вращающимися приводами, по одному в каждой рукоятке геймпада.
- Вибрация триггеров – эффект виброотклика, создаваемый двумя независимыми моторами, по одному в каждом триггере геймпада.
На схеме ниже, взятой из спецификации, показано сопоставление и расположение кнопок и осей на стандартном геймпаде.
Уведомление о подключении геймпада
Чтобы узнать, когда геймпад подключен, прослушивайте событие gamepadconnected, которое активируется в объекте window. Когда пользователь подключает геймпад через USB или Bluetooth, активируется событие 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. Обратите внимание, что в следующем примере connected меняется на false, когда я отключаю контроллер Xbox 360.
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>
Демоверсии
Ниже встроена демонстрация тестера геймпада. Исходный код доступен на GitHub. Чтобы попробовать демоверсию, подключите геймпад через USB или Bluetooth и нажмите любую кнопку или переместите любую ось.
Бонус: играйте в Chrome Dino на web.dev
Вы можете играть в Chrome Dino с помощью геймпада прямо на этом сайте. Исходный код доступен на GitHub.
Посмотрите, как реализован опрос геймпада в trex-runner.js, и обратите внимание на то, как эмулируются нажатия клавиш.
Чтобы продемонстрировать геймпад для игры Chrome Dino, я извлек игру из основного проекта Chromium (обновив предыдущую версию, созданную Арнеллом Балланом), разместил ее на отдельном сайте, расширил существующую реализацию Gamepad API, добавив эффекты приседания и вибрации, создал полноэкранный режим, а Мехул Сатардекар добавил темную тему. Приятной игры!
Полезные ссылки
Благодарности
Этот документ проверили Франсуа Бофор (François Beaufort) и Джо Медли (Joe Medley). Спецификацию Gamepad API редактируют Стив Агостон (Steve Agoston), Джеймс Холлиер (James Hollyer) и Мэтт Рейнольдс (Matt Reynolds). Предыдущие редакторы спецификации: Брэндон Джонс (Brandon Jones), Скотт Грэм (Scott Graham) и Тед Мильчарек (Ted Mielczarek). Редактор спецификации Gamepad Extensions – Брэндон Джонс (Brandon Jones).