Блокировка указателя и управление шутером от первого лица

Введение

API Pointer Lock помогает правильно реализовать элементы управления шутером от первого лица в браузерной игре. Без относительного движения мыши курсор игрока может, например, попасть в правый край экрана, и любые дальнейшие движения вправо будут не учитываться - вид не будет продолжать панорамироваться вправо, и игрок не сможет преследовать плохое ребят и обстреливать их из своего пулемета. Игрок будет разбит и расстроен. При блокировке указателя такое неоптимальное поведение невозможно.

API Pointer Lock позволяет вашему приложению выполнять следующие действия:

  • Получите доступ к необработанным данным мыши, включая относительные движения мыши.
  • Направьте все события мыши на определенный элемент

В качестве побочного эффекта включения блокировки указателя курсор мыши скрывается, что позволяет вам при желании нарисовать указатель для конкретного приложения или оставить указатель мыши скрытым, чтобы пользователь мог перемещать рамку с помощью мыши. Относительное движение мыши — это отклонение положения указателя мыши от предыдущего кадра, независимо от абсолютного положения. Например, если указатель мыши переместился из (640, 480) в (520, 490), относительное перемещение будет (-120, 10). Ниже приведен интерактивный пример, показывающий необработанные изменения положения мыши.

В этом руководстве рассматриваются две темы: основные моменты активации и обработки событий блокировки указателя, а также реализация схемы управления шутером от первого лица. Да, когда вы закончите читать эту статью, вы узнаете, как использовать блокировку указателя и реализовать элементы управления в стиле Quake для вашей собственной браузерной игры!

Совместимость с браузером

Поддержка браузера

  • Хром: 37.
  • Край: 13.
  • Фаерфокс: 50.
  • Сафари: 10.1.

Источник

Механика блокировки указателя

Обнаружение функций

Чтобы определить, поддерживает ли браузер пользователя блокировку указателя, вам необходимо проверить наличие 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».

Информационная панель Pointer Lock в Chrome.
Информационная панель Pointer Lock в Chrome.

Обработка событий

Есть два события, для которых ваше приложение должно добавить прослушиватели. Первый — 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 чтобы получать обновления

Внешние демонстрации

Ссылки