Введение
API Pointer Lock помогает правильно реализовать элементы управления шутером от первого лица в браузерной игре. Без относительного движения мыши курсор игрока может, например, попасть в правый край экрана, и любые дальнейшие движения вправо будут не учитываться - вид не будет продолжать панорамироваться вправо, и игрок не сможет преследовать плохое ребят и обстреливать их из своего пулемета. Игрок будет разбит и расстроен. При блокировке указателя такое неоптимальное поведение невозможно.
API Pointer Lock позволяет вашему приложению выполнять следующие действия:
- Получите доступ к необработанным данным мыши, включая относительные движения мыши.
- Направьте все события мыши на определенный элемент
В качестве побочного эффекта включения блокировки указателя курсор мыши скрывается, что позволяет вам при желании нарисовать указатель для конкретного приложения или оставить указатель мыши скрытым, чтобы пользователь мог перемещать рамку с помощью мыши. Относительное движение мыши — это отклонение положения указателя мыши от предыдущего кадра, независимо от абсолютного положения. Например, если указатель мыши переместился из (640, 480) в (520, 490), относительное перемещение будет (-120, 10). Ниже приведен интерактивный пример, показывающий необработанные изменения положения мыши.
В этом руководстве рассматриваются две темы: основные моменты активации и обработки событий блокировки указателя, а также реализация схемы управления шутером от первого лица. Да, когда вы закончите читать эту статью, вы узнаете, как использовать блокировку указателя и реализовать элементы управления в стиле Quake для вашей собственной браузерной игры!
Совместимость с браузером
Механика блокировки указателя
Обнаружение функций
Чтобы определить, поддерживает ли браузер пользователя блокировку указателя, вам необходимо проверить наличие pointerLockElement
или версии с префиксом поставщика в объекте документа. В коде:
var havePointerLock = 'pointerLockElement' in document ||
'mozPointerLockElement' in document ||
'webkitPointerLockElement' in document;
В настоящее время блокировка указателя доступна только в Firefox и Chrome. Opera и IE пока не поддерживают его.
Активация
Активация блокировки указателя представляет собой двухэтапный процесс. Сначала ваше приложение запрашивает блокировку указателя для определенного элемента, и сразу после того, как пользователь дает разрешение, срабатывает событие pointerlockchange
. Пользователь может отменить блокировку указателя в любой момент, нажав клавишу Escape. Ваше приложение также может программно выйти из блокировки указателя. Когда блокировка указателя отменяется, срабатывает событие pointerlockchange
.
element.requestPointerLock = element.requestPointerLock ||
element.mozRequestPointerLock ||
element.webkitRequestPointerLock;
// Ask the browser to lock the pointer
element.requestPointerLock();
// Ask the browser to release the pointer
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
Приведенный выше код — это все, что нужно. Когда браузер заблокирует указатель, появится всплывающее окно, сообщающее пользователю, что ваше приложение заблокировало указатель, и сообщающее им, что они могут отменить его, нажав клавишу «Esc».
Обработка событий
Есть два события, для которых ваше приложение должно добавить прослушиватели. Первый — pointerlockchange
, который срабатывает всякий раз, когда происходит изменение состояния блокировки указателя. Второй — mousemove
, который срабатывает всякий раз, когда мышь перемещается.
// Hook pointer lock state change events
document.addEventListener('pointerlockchange', changeCallback, false);
document.addEventListener('mozpointerlockchange', changeCallback, false);
document.addEventListener('webkitpointerlockchange', changeCallback, false);
// Hook mouse move events
document.addEventListener("mousemove", this.moveCallback, false);
Внутри обратного вызова pointerlockchange
вы должны проверить, был ли указатель только что заблокирован или разблокирован. Определить, включена ли блокировка указателя, просто: проверьте, равен ли document.pointerLockElement элементу, для которого была запрошена блокировка указателя. Если это так, ваше приложение успешно заблокировало указатель, а если нет, то указатель был разблокирован пользователем или вашим собственным кодом.
if (document.pointerLockElement === requestedElement ||
document.mozPointerLockElement === requestedElement ||
document.webkitPointerLockElement === requestedElement) {
// Pointer was just locked
// Enable the mousemove listener
document.addEventListener("mousemove", this.moveCallback, false);
} else {
// Pointer was just unlocked
// Disable the mousemove listener
document.removeEventListener("mousemove", this.moveCallback, false);
this.unlockHook(this.element);
}
Когда блокировка указателя включена, clientX
, clientY
, screenX
и screenY
остаются постоянными. movementX
и movementY
обновляются с учетом количества пикселей, на которое указатель переместился бы с момента доставки последнего события. В псевдокоде:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
Внутри обратного вызова mousemove
данные относительного движения мыши могут быть извлечены из полей movementX
и movementY
события.
function moveCallback(e) {
var movementX = e.movementX ||
e.mozMovementX ||
e.webkitMovementX ||
0,
movementY = e.movementY ||
e.mozMovementY ||
e.webkitMovementY ||
0;
}
Обнаружение ошибок
Если при входе или выходе из блокировки указателя возникает ошибка, срабатывает событие pointerlockerror
. К этому событию не прикреплены никакие данные.
document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);
Требуется полноэкранный режим?
Первоначально блокировка указателя была привязана к API FullScreen. Это означает, что элемент должен находиться в полноэкранном режиме, прежде чем на него можно будет зафиксировать указатель. Это уже не так, и блокировку указателя можно использовать для любого элемента вашего приложения в полноэкранном или нет.
Пример управления шутером от первого лица
Теперь, когда у нас включена блокировка указателя и мы получаем события, пришло время для практического примера. Вы когда-нибудь хотели узнать, как работают элементы управления в Quake? Пристегнитесь, потому что я собираюсь объяснить это с помощью кода!
Управление в шутере от первого лица построено на четырех основных механиках:
- Перемещение вперед и назад по текущему вектору вида.
- Перемещение влево и вправо по текущему вектору стрейфа.
- Вращение обзора (влево и вправо)
- Вращение угла обзора (вверх и вниз)
Игра, реализующая эту схему управления, требует всего три части данных: положение камеры, вектор обзора камеры и постоянный вектор вверх. Вектор вверх всегда равен (0, 1, 0). Все четыре вышеперечисленных механизма просто по-разному манипулируют положением камеры и вектором обзора камеры.
Движение
Первое на палубе — это движение. В приведенной ниже демонстрации движение сопоставлено стандартным клавишам W, A, S и D. Клавиши W и S перемещают камеру вперед и назад. При этом клавиши A и D управляют камерой влево и вправо. Перемещать камеру вперед и назад просто:
// Forward direction
var forwardDirection = vec3.create(cameraLookVector);
// Speed
var forwardSpeed = dt * cameraSpeed;
// Forward or backward depending on keys held
var forwardScale = 0.0;
forwardScale += keyState.W ? 1.0 : 0.0;
forwardScale -= keyState.S ? 1.0 : 0.0;
// Scale movement
vec3.scale(forwardDirection, forwardScale * forwardSpeed);
// Add scaled movement to camera position
vec3.add(cameraPosition, forwardDirection);
Стрейф влево и вправо требует направления стрейфа. Направление стрейфа можно вычислить с помощью векторного произведения:
// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);
Как только вы определите направление стрейфа, реализация стрейфа будет аналогична движению вперед или назад.
Следующий шаг — поворот изображения.
рыскание
Отклонение от курса или горизонтальное вращение обзора камеры — это просто вращение вокруг постоянного вектора вверх. Ниже приведен общий код для вращения вектора обзора камеры вокруг произвольной оси. Он работает путем создания кватерниона, представляющего вращение радианов deltaAngle
вокруг axis
, а затем использует кватернион для вращения вектора обзора камеры:
// Extract camera look vector
var frontDirection = vec3.create();
vec3.subtract(this.lookAtPoint, this.eyePoint, frontDirection);
vec3.normalize(frontDirection);
var q = quat4.create();
// Construct quaternion
quat4.fromAngleAxis(deltaAngle, axis, q);
// Rotate camera look vector
quat4.multiplyVec3(q, frontDirection);
// Update camera look vector
this.lookAtPoint = vec3.create(this.eyePoint);
vec3.add(this.lookAtPoint, frontDirection);
Подача
Реализация наклона или вертикального вращения изображения камеры аналогична, но вместо вращения вокруг вектора вверх вы применяете вращение вокруг вектора стрейфа. Первый шаг — вычислить вектор стрейфа, а затем повернуть вектор обзора камеры вокруг этой оси.
Краткое содержание
API Pointer Lock позволяет вам контролировать курсор мыши. Если вы создаете веб-игры, вашим игрокам понравится, когда их перестанут фрагментировать, потому что они взволнованно вытащили мышь из окна, а ваша игра перестала получать обновления мыши. Использование простое:
- Добавьте прослушиватель событий
pointerlockchange
для отслеживания состояния блокировки указателя. - Запросить блокировку указателя для определенного элемента
- Добавьте прослушиватель событий
mousemove
чтобы получать обновления