포인터 잠금 및 1인칭 슈팅 게임 컨트롤

존 맥커찬
존 맥커찬

소개

Pointer Lock API는 브라우저 게임에서 1인칭 슈팅 컨트롤을 올바르게 구현하는 데 도움이 됩니다. 상대적인 마우스 움직임이 없으면 플레이어의 커서가 화면 오른쪽 가장자리를 누르게 되고 추가로 오른쪽으로 움직이면 시야가 오른쪽으로 움직이지 않고 플레이어는 악당을 쫓아 기관총으로 그 사람을 쫓아 잡을 수 없게 됩니다. 플레이어는 좌절하고 좌절합니다. 포인터 잠금을 사용하면 최적이 아닌 동작이 발생할 수 없습니다.

Pointer Lock API를 사용하면 애플리케이션에서 다음 작업을 할 수 있습니다.

  • 상대적인 마우스 움직임을 포함한 원시 마우스 데이터에 액세스
  • 모든 마우스 이벤트를 특정 요소로 라우팅

포인터 잠금을 사용 설정하면 마우스 커서가 숨겨져 원하는 경우 애플리케이션별 포인터를 그리거나 사용자가 마우스로 프레임을 이동할 수 있도록 마우스 포인터를 숨기도록 선택할 수 있습니다. 마우스의 상대적 움직임은 절대 위치에 관계없이 이전 프레임으로부터의 마우스 포인터 위치 델타입니다. 예를 들어 마우스 포인터가 (640, 480)에서 (520, 490)으로 이동하면 상대적 움직임은 (-120, 10)입니다. 원시 마우스 위치 델타를 보여주는 대화형 예는 아래를 참고하세요.

이 튜토리얼에서는 포인터 잠금 이벤트 활성화 및 처리의 기본적인 사항과 1인칭 슈팅 게임 제어 체계 구현이라는 두 가지 주제를 다룹니다. 맞습니다. 이 도움말을 다 읽고 나면 나만의 브라우저 게임에 포인터 잠금을 사용하고 Quake 스타일 컨트롤을 구현하는 방법을 알 수 있을 것입니다.

브라우저 호환성

브라우저 지원

  • 37
  • 13
  • 50
  • 10.1

소스

포인터 잠금 메커니즘

기능 감지

사용자의 브라우저가 포인터 잠금을 지원하는지 확인하려면 문서 객체에서 pointerLockElement 또는 공급업체 접두사 버전을 확인해야 합니다. 코드:

var havePointerLock = 'pointerLockElement' in document ||
    'mozPointerLockElement' in document ||
    'webkitPointerLockElement' in document;

현재 포인터 잠금은 Firefox와 Chrome에서만 사용할 수 있습니다. Opera와 IE는 아직 이 기능을 지원하지 않습니다.

활성화 중

포인터 잠금 활성화는 두 단계로 진행됩니다. 먼저 애플리케이션은 특정 요소에 포인터 잠금을 사용 설정하도록 요청하고, 사용자가 권한을 부여한 직후에 pointerlockchange 이벤트가 실행됩니다. 사용자는 Esc 키를 눌러 언제든지 포인터 잠금을 취소할 수 있습니다. 또한 애플리케이션에서 프로그래매틱 방식으로 포인터 잠금을 종료할 수도 있습니다. 포인터 잠금이 취소되면 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' 키를 눌러 취소할 수 있음을 알리는 도움말 풍선이 표시됩니다.

Chrome의 포인터 잠금 정보 표시줄
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가 일정하게 유지됩니다. movementXmovementY는 마지막 이벤트가 전달된 이후 포인터가 이동했을 픽셀 수로 업데이트됩니다. 의사 코드는 다음과 같습니다.

event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;

mousemove 콜백 내 상대적인 마우스 모션 데이터는 이벤트의 movementXmovementY 필드에서 추출할 수 있습니다.

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에 연결되었습니다. 즉, 포인터를 요소에 고정하려면 요소가 전체 화면 모드여야 합니다. 이는 더 이상 참이 아니며 포인터 잠금은 애플리케이션의 전체 화면 여부와 관계없이 모든 요소에 사용될 수 있습니다.

1인칭 슈팅 컨트롤의 예

포인터 잠금을 사용 설정하고 이벤트를 수신했으므로 이제 실제 예를 들어 보겠습니다. Quake에서 컨트롤이 어떻게 작동하는지 알고 싶었던 적이 있으세요? 코드로 설명해 드리려고 하니 묶어주세요.

1인칭 슈팅 컨트롤은 다음과 같은 4가지 핵심 메커니즘을 중심으로 구현됩니다.

  • 현재 Look 벡터를 따라 앞뒤로 이동
  • 현재 스트랩 벡터를 따라 좌우로 이동
  • 뷰 요 회전 (왼쪽 및 오른쪽)
  • 뷰 피치 회전 (상하로)

이 컨트롤 스킴을 구현하는 게임에는 카메라 위치, 카메라 스타일 벡터, 상수 업 벡터라는 세 가지 데이터만 있으면 됩니다. 위쪽 벡터는 항상 (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);

스트랩 방향을 정한 후에는 스트랩 이동을 구현하는 것은 앞이나 뒤로 이동하는 것과 같습니다.

다음은 뷰를 회전하는 것입니다.

카메라 뷰의 요(Yaw) 또는 수평 회전은 상수 업 벡터를 중심으로 한 회전일 뿐입니다. 다음은 임의의 축을 중심으로 카메라 Look 벡터를 회전하는 일반적인 코드입니다. axis를 중심으로 한 deltaAngle라디안 회전을 나타내는 사분위를 구성한 다음 사원분자를 사용하여 카메라 모습 벡터를 회전합니다.

// 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);

추천

카메라 뷰의 피치 또는 수직 회전을 구현하는 것은 비슷하지만 위로 벡터를 중심으로 한 회전 대신 스트랩 벡터를 중심으로 회전을 적용합니다. 첫 번째 단계는 스트랩 벡터를 계산한 다음 이 축을 중심으로 카메라 Look 벡터를 회전하는 것입니다.

요약

Pointer Lock API를 사용하면 마우스 커서를 제어할 수 있습니다. 웹 게임을 만드는 경우 플레이어가 흥분해서 창 밖으로 마우스를 움직였고 게임이 더 이상 마우스 업데이트를 받지 못하기 때문에 골치 아픈 일이 멈추면 즐거워할 것입니다. 사용법은 간단합니다.

  • 포인터 잠금 상태를 추적하는 pointerlockchange 이벤트 리스너 추가
  • 특정 요소에 대한 포인터 잠금 요청
  • mousemove 이벤트 리스너를 추가하여 업데이트 받기

외부 데모

참조