Узнайте, как использовать API Gamepad, чтобы вывести ваши веб-игры на новый уровень.
Пасхальное яйцо офлайн-страницы Chrome — один из самых плохо охраняемых секретов в истории ( [citation needed]
, но это сделано ради драматического эффекта). Если нажать пробел или, на мобильных устройствах, коснуться динозавра, офлайн-страница превратится в игровую аркадную игру. Возможно, вы знаете, что вам не обязательно выходить в офлайн-режим, чтобы поиграть: в Chrome можно просто перейти на about://dino
или, если вы гик, перейти на about://network-error/-106
. Но знаете ли вы, что каждый месяц в Chrome запускается 270 миллионов игр с динозаврами ?

Ещё один факт, который, пожалуй, полезнее знать, но о котором вы могли не знать, — это то, что в аркадном режиме можно играть с помощью геймпада. Поддержка геймпада была добавлена примерно год назад (на момент написания этой статьи) в коммите Райли Гранта . Как видите, игра, как и весь проект Chromium, имеет полностью открытый исходный код . В этой публикации я хочу показать вам, как использовать API геймпада.
Используйте API геймпада
Обнаружение функций и поддержка браузера
API Gamepad имеет отличную поддержку во всех браузерах, как на настольных компьютерах, так и на мобильных устройствах. Вы можете проверить, поддерживается ли API Gamepad, с помощью следующего фрагмента кода:
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
1.0
для кнопок с аналоговым датчиком это свойство представляет собой величину нажатия кнопки, линейно нормализованную в диапазоне0.0
. -
hapticActuators
: массив, содержащий объектыGamepadHapticActuator
, каждый из которых представляет собой оборудование тактильной обратной связи, доступное на контроллере.
Ещё один параметр, с которым вы можете столкнуться, в зависимости от вашего браузера и геймпада, — это свойство vibrationActuator
. Оно позволяет реализовать два типа эффектов вибрации:
- Dual-Rumble: эффект тактильной обратной связи, создаваемый двумя эксцентриковыми вращающимися массовыми приводами, по одному в каждой рукоятке геймпада.
- Trigger-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
. Обратите внимание, что в следующем примере при отключении контроллера 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
: устанавливают уровни интенсивности вибрации для более тяжелых и более легких двигателей с эксцентриковой вращающейся1.0
, нормализованные в диапазоне0.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,
});
};
Интеграция с политикой разрешений
В спецификации API геймпада определена функция, контролируемая политикой, которая идентифицируется строкой "gamepad"
. Её allowlist
по умолчанию — "self"
. Политика прав доступа документа определяет, разрешён ли доступ к navigator.getGamepads()
какому-либо содержимому этого документа. Если эта политика отключена в каком-либо документе, то ни одному содержимому документа не будет разрешено использовать navigator.getGamepads()
, а также не будут срабатывать события gamepadconnected
и gamepaddisconnected
.
<iframe src="index.html" allow="gamepad"></iframe>
Демо
Демонстрация для тестирования геймпада встроена в следующий пример. Исходный код доступен на Glitch . Попробуйте демонстрацию, подключив геймпад по USB или Bluetooth и нажав любую из его кнопок или переместив любую из его осей.
Бонус: играйте в Chrome dino на web.dev
Вы можете поиграть в Chrome Dino с помощью геймпада прямо на этом сайте. Исходный код доступен на GitHub . Посмотрите на реализацию опроса геймпада в trex-runner.js
и обратите внимание, как она эмулирует нажатия клавиш.
Чтобы демо-версия геймпада Chrome Dino заработала, я вытащил игру Chrome Dino из основного проекта Chromium (обновив более раннюю работу Арнелла Баллейна ), разместил её на отдельном сайте, расширил существующую реализацию API геймпада, добавив эффекты приседания и вибрации, создал полноэкранный режим, а Мехул Сатардекар добавил реализацию тёмной темы. Приятной игры!
Полезные ссылки
Благодарности
Этот документ был проверен Франсуа Бофортом и Джо Медли . Спецификацию API геймпада редактировали Стив Агостон , Джеймс Холлиер и Мэтт Рейнольдс . Предыдущие редакторы спецификации — Брэндон Джонс , Скотт Грэм и Тед Мельчарек . Спецификацию расширений геймпада редактировал Брэндон Джонс . Изображение главного героя — Лора Торрент Пуиг.