Использование API Gamepad
Базовая Широко доступна *
Эта функция хорошо зарекомендовала себя и работает на многих устройствах и версиях браузеров. Она доступна во всех браузерах с марта 2017 года.
* Некоторые части этой функции могут иметь различный уровень поддержки.
HTML предоставляет необходимые компоненты для создания богатых и интерактивных игр. Технологии, такие как <canvas>, WebGL, <audio>, и <video>, наряду с реализациями на JavaScript, поддерживают задачи, которые обеспечивают аналогичные, если не те же самые, возможности, что и нативный код. API Gamepad позволяет разработчикам и дизайнерам получать доступ к геймпадам и другим игровым контроллерам и использовать их.
API Gamepad добавляет новые события к объекту Window для чтения состояния геймпада и контроллера (далее — геймпад). В дополнение к этим событиям API также добавляет объект Gamepad, который можно использовать для запроса состояния подключенного геймпада, и метод navigator.getGamepads(), с помощью которого можно получить список известных странице геймпадов.
Подключение к геймпаду
При подключении нового геймпада к компьютеру, страница, на которой фокус, сначала получает событие gamepadconnected. Если геймпад уже подключен при загрузке страницы, событие gamepadconnected отправляется на страницу с фокусом, когда пользователь нажимает кнопку или перемещает ось.
Примечание: В Firefox геймпады доступны странице только при взаимодействии пользователя с ними, когда страница видна. Это помогает предотвратить использование геймпадов для отслеживания пользователя. После взаимодействия с одним геймпадом другие подключенные геймпады автоматически станут видимыми.
Вы можете использовать gamepadconnected следующим образом:
window.addEventListener("gamepadconnected", (e) => {
console.log(
"Gamepad connected at index %d: %s. %d buttons, %d axes.",
e.gamepad.index,
e.gamepad.id,
e.gamepad.buttons.length,
e.gamepad.axes.length,
);
});
Каждый геймпад имеет уникальный ID, доступный в свойстве события gamepad.
Отключение геймпада
При отключении геймпада, если страница ранее получала данные для этого геймпада (например, gamepadconnected), второе событие отправляется на окно с фокусом, gamepaddisconnected:
window.addEventListener("gamepaddisconnected", (e) => {
console.log(
"Gamepad disconnected from index %d: %s",
e.gamepad.index,
e.gamepad.id,
);
});
Свойство index геймпада будет уникальным для каждого подключенного к системе устройства, даже если используются несколько контроллеров одного типа. Свойство index также служит индексом в массиве Array, возвращаемом методом Navigator.getGamepads().
const gamepads = {};
function gamepadHandler(event, connected) {
const gamepad = event.gamepad;
// Note:
// gamepad === navigator.getGamepads()[gamepad.index]
if (connected) {
gamepads[gamepad.index] = gamepad;
} else {
delete gamepads[gamepad.index];
}
}
window.addEventListener(
"gamepadconnected",
(e) => {
gamepadHandler(e, true);
},
false,
);
window.addEventListener(
"gamepaddisconnected",
(e) => {
gamepadHandler(e, false);
},
false,
);
Этот предыдущий пример также демонстрирует, как свойство gamepad может сохраняться после завершения события — техника, которую мы будем использовать для запроса состояния устройства позже.
Запрос объекта Gamepad
Как вы можете видеть, события gamepad, обсуждаемые выше, содержат свойство gamepad в объекте события, которое возвращает объект Gamepad. Мы можем использовать это, чтобы определить, какой геймпад (то есть его ID) вызвал событие, поскольку одновременно может быть подключено несколько геймпадов. Мы можем сделать гораздо больше с объектом Gamepad, включая хранение ссылки на него и запрос информации о нажатых кнопках и осях в любой момент времени. Это часто желательно для игр или других интерактивных веб-страниц, которым нужно знать текущее состояние геймпада, а не состояние в следующий момент срабатывания события.
Выполнение таких проверок обычно включает использование объекта Gamepad в сочетании с циклом анимации (например, requestAnimationFrame), где разработчики хотят принять решения для текущего кадра на основе состояния геймпада или геймпадов.
Метод Navigator.getGamepads() возвращает массив всех устройств, в настоящее время видимых веб-странице, как объекты Gamepad (первое значение всегда null, поэтому null будет возвращено, если геймпады не подключены). Это можно использовать для получения той же информации. Например, первый код-пример выше можно переписать следующим образом:
window.addEventListener("gamepadconnected", (e) => {
const gp = navigator.getGamepads()[e.gamepad.index];
console.log(
"Gamepad connected at index %d: %s. %d buttons, %d axes.",
gp.index,
gp.id,
gp.buttons.length,
gp.axes.length,
);
});
Свойства объекта Gamepad следующие:
-
id: Строка, содержащая информацию о контроллере. Она не строго специфицирована, но в Firefox она будет содержать три части информации, разделенные дефисами (-): две 4-значные шестнадцатеричные строки, содержащие идентификаторы поставщика и продукта USB контроллера, и имя контроллера, предоставленное драйвером. Эта информация предназначена для того, чтобы позволить вам найти соответствие управления на устройстве, а также отображать полезные отзывы пользователю. -
index: Целое число, уникальное для каждого геймпада, подключенного к системе. Его можно использовать для различения нескольких контроллеров. Обратите внимание, что отключение устройства и подключение нового устройства могут повторно использовать предыдущий индекс. -
mapping: Строка, указывающая, перемапировал ли браузер управление на устройстве на известную схему. В настоящее время поддерживается только одна известная схема — стандартный геймпад. Если браузер может сопоставить управление на устройстве с этой схемой, свойствоmappingбудет установлено в строкуstandard. -
connected: Логическое значение, указывающее, подключен ли геймпад к системе. Если это так, значениеTrue; в противном случае —False. -
buttons: Массив объектовGamepadButton, представляющих кнопки на устройстве. Каждый объектGamepadButtonимеет свойствоpressedи свойствоvalue:- Свойство
pressed— это логическое значение, указывающее, нажата ли кнопка (true) или нет (false). - Свойство
value— это число с плавающей точкой, используемое для представления аналоговых кнопок, таких как спусковые крючки на многих современных геймпадах. Значения нормализованы к диапазону 0.0..1.0, где 0.0 соответствует не нажатой кнопке, а 1.0 — полностью нажатой.
- Свойство
-
axes: Массив, представляющий оси управления на устройстве (например, аналоговые джойстики). Каждый элемент массива — значение с плавающей точкой в диапазоне -1.0 - 1.0, представляющее положение оси от минимального значения (-1.0) до максимального (1.0). -
timestamp: Возвращает объектDOMHighResTimeStamp, представляющий последний момент обновления данных для этого геймпада, позволяя разработчикам определить, были ли обновлены данныеaxesиbuttonиз оборудования. Значение должно быть относительно атрибутаnavigationStartинтерфейсаPerformanceTiming. Значения монотонно возрастают, что означает, что их можно сравнивать для определения порядка обновлений, так как новые значения всегда будут больше или равны старым. Обратите внимание, что это свойство в настоящее время не поддерживается в Firefox.
Примечание: Объект Gamepad доступен для события gamepadconnected, а не для самого объекта Window, по соображениям безопасности. После получения ссылки на него мы можем запросить его свойства для получения информации о текущем состоянии геймпада. Под капотом этот объект будет обновляться каждый раз, когда изменяется состояние геймпада.
Использование информации о кнопках
Рассмотрим пример, отображающий информацию о подключении одного геймпада (он игнорирует последующие подключения геймпадов) и позволяющий перемещать шар по экрану с помощью четырёх кнопок геймпада на правой стороне. Вы можете посмотреть демо-версию и найти исходный код на GitHub.
Для начала, мы объявляем некоторые переменные: абзац gamepadInfo, в который будет записываться информация о подключении, ball , который мы хотим перемещать, переменную start, которая служит идентификатором для requestAnimation Frame, переменные a и b, которые действуют как модификаторы позиции для перемещения шара, а также сокращённые переменные, которые будут использоваться для кроссбраузерных функций requestAnimationFrame() и cancelAnimationFrame().
const gamepadInfo = document.getElementById("gamepad-info");
const ball = document.getElementById("ball");
let start;
let a = 0;
let b = 0;
Далее мы используем событие gamepadconnected для проверки подключения геймпада. При подключении мы получаем геймпад с помощью navigator.getGamepads()[0], выводим информацию о геймпаде в наш блок информации о геймпаде div, и запускаем функцию gameLoop(), которая инициирует весь процесс перемещения шара.
window.addEventListener("gamepadconnected", (e) => {
const gp = navigator.getGamepads()[e.gamepad.index];
gamepadInfo.textContent = `Gamepad connected at index ${gp.index}: ${gp.id}. It has ${gp.buttons.length} buttons and ${gp.axes.length} axes.`;
gameLoop();
});
Теперь мы используем событие gamepaddisconnected, чтобы проверить, отключён ли геймпад. В этом случае, мы останавливаем цикл requestAnimationFrame() (см. ниже) и восстанавливаем информацию о геймпаде до первоначальных значений.
window.addEventListener("gamepaddisconnected", (e) => {
gamepadInfo.textContent = "Waiting for gamepad.";
cancelAnimationFrame(start);
});
Теперь перейдём к главному циклу игры. В каждом цикле мы проверяем, нажата ли одна из четырёх кнопок; если да, то мы обновляем значения переменных перемещения a и b, а затем обновляем свойства left и top, изменяя их значения на текущие значения a и b соответственно. Это приводит к перемещению шара по экрану.
После этого мы используем requestAnimationFrame(), чтобы запросить следующий кадр анимации, снова запустив gameLoop().
function gameLoop() {
const gamepads = navigator.getGamepads();
if (!gamepads) {
return;
}
const gp = gamepads[0];
if (gp.buttons[0].pressed) {
b--;
}
if (gp.buttons[2].pressed) {
b++;
}
if (gp.buttons[1].pressed) {
a++;
}
if (gp.buttons[3].pressed) {
a--;
}
ball.style.left = `${a * 2}px`;
ball.style.top = `${b * 2}px`;
start = requestAnimationFrame(gameLoop);
}
Полный пример: Отображение состояния геймпада
Этот пример демонстрирует, как использовать объект Gamepad, а также события gamepadconnected и gamepaddisconnected для отображения состояния всех подключенных к системе геймпадов. Пример основан на демонстрации работы с геймпадом, у которой исходный код доступен на GitHub.
let loopStarted = false;
window.addEventListener("gamepadconnected", (evt) => {
addGamepad(evt.gamepad);
});
window.addEventListener("gamepaddisconnected", (evt) => {
removeGamepad(evt.gamepad);
});
function addGamepad(gamepad) {
const d = document.createElement("div");
d.setAttribute("id", `controller${gamepad.index}`);
const t = document.createElement("h1");
t.textContent = `gamepad: ${gamepad.id}`;
d.append(t);
const b = document.createElement("ul");
b.className = "buttons";
gamepad.buttons.forEach((button, i) => {
const e = document.createElement("li");
e.className = "button";
e.textContent = `Button ${i}`;
b.append(e);
});
d.append(b);
const a = document.createElement("div");
a.className = "axes";
gamepad.axes.forEach((axis, i) => {
const p = document.createElement("progress");
p.className = "axis";
p.setAttribute("max", "2");
p.setAttribute("value", "1");
p.textContent = i;
a.append(p);
});
d.appendChild(a);
// See https://github.com/luser/gamepadtest/blob/master/index.html
const start = document.querySelector("#start");
if (start) {
start.style.display = "none";
}
document.body.append(d);
if (!loopStarted) {
requestAnimationFrame(updateStatus);
loopStarted = true;
}
}
function removeGamepad(gamepad) {
document.querySelector(`#controller${gamepad.index}`).remove();
}
function updateStatus() {
for (const gamepad of navigator.getGamepads()) {
if (!gamepad) continue;
const d = document.getElementById(`controller${gamepad.index}`);
const buttonElements = d.getElementsByClassName("button");
for (const [i, button] of gamepad.buttons.entries()) {
const el = buttonElements[i];
const pct = `${Math.round(button.value * 100)}%`;
el.style.backgroundSize = `${pct} ${pct}`;
if (button.pressed) {
el.textContent = `Button ${i} [PRESSED]`;
el.style.color = "#42f593";
el.className = "button pressed";
} else {
el.textContent = `Button ${i}`;
el.style.color = "#2e2d33";
el.className = "button";
}
}
const axisElements = d.getElementsByClassName("axis");
for (const [i, axis] of gamepad.axes.entries()) {
const el = axisElements[i];
el.textContent = `${i}: ${axis.toFixed(4)}`;
el.setAttribute("value", axis + 1);
}
}
requestAnimationFrame(updateStatus);
}
Спецификации
Совместимость с браузерами
| Рабочие столы | Мобильные устройства | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox for Android | Opera Android | Safari на iOS | Samsung Internet | WebView Android | |
Using_the_Gamepad_API |
21 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
axes |
21 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
buttons |
21 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
connected |
25 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
displayId |
Нет | 15–79 | 9864–98Поддержка macOS была включена в Firefox 64.55–98Поддержка Windows была включена в Firefox 55. |
Нет | Нет | 55–80В настоящее время поддерживается только Google Daydream. |
55–98 | 42–57В настоящее время поддерживается только Google Daydream. |
Нет | 6.0–13.0В настоящее время поддерживается только Google Daydream. |
Нет |
hand |
Нет | 15–79 | 55 | Нет | Нет | Нет | 55 | Нет | Нет | Нет | Нет |
hapticActuators |
Нет | 15–79 | 55 | Нет | Нет | Нет | 55 | Нет | Нет | Нет | Нет |
id |
21 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
index |
21 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
mapping |
21 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
pose |
Нет | 15–79 | 55 | Нет | Нет | Нет | 55 | Нет | Нет | Нет | Нет |
secure_context_required |
86 | 86 | 91 | 72 | Нет | 86 | 91 | Нет | Нет | Нет | Нет |
timestamp |
21 | 12 | 29 | 15 | 10.1 | 25 | 32 | 14 | 10.3 | 1.5 | 4.4 |
vibrationActuator |
68 | 79 | Нет | 55 | 16.4 | 68 | Нет | 48 | Нет | 10.0 | Нет |
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API