Nút điều khiển trò chơi bắn súng ở góc nhìn người thứ nhất và khoá con trỏ

John McCutchan
John McCutchan

Giới thiệu

Pointer Lock API giúp triển khai đúng cách các chế độ điều khiển trò chơi bắn súng góc nhìn thứ nhất trong trò chơi trên trình duyệt. Ví dụ: nếu không có chuyển động tương đối của chuột, con trỏ của người chơi có thể chạm vào cạnh phải của màn hình và mọi chuyển động tiếp theo sang phải sẽ bị giảm – khung nhìn sẽ không tiếp tục kéo sang phải và người chơi sẽ không thể truy đuổi kẻ xấu và bắn họ bằng súng máy. Người chơi sẽ bị bắn hạ và trở nên thất vọng. Với tính năng khoá con trỏ, hành vi không tối ưu này sẽ không xảy ra.

Pointer Lock API (API khoá con trỏ) cho phép ứng dụng của bạn làm những việc sau:

  • Truy cập vào dữ liệu chuột thô, bao gồm cả các chuyển động tương đối của chuột
  • Chuyển hướng tất cả sự kiện chuột đến một phần tử cụ thể

Khi bật tính năng khoá con trỏ, con trỏ chuột sẽ bị ẩn để bạn có thể chọn vẽ một con trỏ dành riêng cho ứng dụng nếu muốn hoặc để con trỏ chuột bị ẩn để người dùng có thể di chuyển khung bằng chuột. Di chuyển chuột tương đối là delta của vị trí con trỏ chuột so với khung hình trước đó, bất kể vị trí tuyệt đối. Ví dụ: nếu con trỏ chuột di chuyển từ (640, 480) đến (520, 490), thì chuyển động tương đối là (-120, 10). Hãy xem ví dụ tương tác bên dưới để biết delta vị trí chuột thô.

Hướng dẫn này bao gồm hai chủ đề: thông tin cơ bản về cách kích hoạt và xử lý các sự kiện khoá con trỏ, cũng như cách triển khai sơ đồ điều khiển trò chơi bắn súng góc nhìn thứ nhất. Đúng vậy, khi đọc xong bài viết này, bạn sẽ biết cách sử dụng tính năng khoá con trỏ và triển khai các chế độ điều khiển kiểu Quake cho trò chơi trên trình duyệt của riêng mình!

Khả năng tương thích với trình duyệt

Hỗ trợ trình duyệt

  • Chrome: 37.
  • Edge: 13.
  • Firefox: 50.
  • Safari: 10.1.

Nguồn

Cơ chế khoá con trỏ

Phát hiện tính năng

Để xác định xem trình duyệt của người dùng có hỗ trợ tính năng khoá con trỏ hay không, bạn cần kiểm tra pointerLockElement hoặc phiên bản có tiền tố của nhà cung cấp trong đối tượng tài liệu. Trong mã:

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

Hiện tại, tính năng khoá con trỏ chỉ có trong Firefox và Chrome. Opera và IE chưa hỗ trợ tính năng này.

Đang kích hoạt

Việc kích hoạt tính năng khoá con trỏ là một quy trình gồm hai bước. Trước tiên, ứng dụng của bạn yêu cầu bật tính năng khoá con trỏ cho một phần tử cụ thể và ngay sau khi người dùng cấp quyền, sự kiện pointerlockchange sẽ kích hoạt. Người dùng có thể huỷ tính năng khoá con trỏ bất cứ lúc nào bằng cách nhấn phím thoát. Ứng dụng của bạn cũng có thể thoát khỏi chế độ khoá con trỏ theo phương thức có lập trình. Khi bạn huỷ tính năng khoá con trỏ, một sự kiện pointerlockchange sẽ kích hoạt.

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

Bạn chỉ cần mã ở trên. Khi trình duyệt khoá con trỏ, một bong bóng trò chuyện sẽ bật lên để người dùng biết rằng ứng dụng của bạn đã khoá con trỏ và hướng dẫn họ có thể huỷ thao tác đó bằng cách nhấn phím "Esc".

Thanh thông tin của tính năng Khoá con trỏ trong Chrome.
Thanh thông tin của tính năng Khoá con trỏ trong Chrome.

Xử lý sự kiện

Ứng dụng của bạn phải thêm trình nghe cho hai sự kiện. Lệnh gọi đầu tiên là pointerlockchange, lệnh này sẽ kích hoạt bất cứ khi nào có thay đổi về trạng thái khoá con trỏ. Lệnh thứ hai là mousemove, lệnh này sẽ kích hoạt bất cứ khi nào chuột di chuyển.

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

Bên trong lệnh gọi lại pointerlockchange, bạn phải kiểm tra xem con trỏ vừa được khoá hay mở khoá. Việc xác định xem tính năng khoá con trỏ có được bật hay không rất đơn giản: hãy kiểm tra xem document.pointerLockElement có bằng phần tử mà bạn đã yêu cầu khoá con trỏ hay không. Nếu có, ứng dụng của bạn đã khoá thành công con trỏ và nếu không, người dùng hoặc mã của chính bạn đã mở khoá con trỏ.

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

Khi bạn bật tính năng khoá con trỏ, clientX, clientY, screenXscreenY sẽ không thay đổi. movementXmovementY được cập nhật bằng số pixel mà con trỏ đã di chuyển kể từ khi sự kiện gần đây nhất được phân phối. Trong mã giả:

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

Bên trong lệnh gọi lại mousemove, bạn có thể trích xuất dữ liệu chuyển động tương đối của chuột từ các trường movementXmovementY của sự kiện.

function moveCallback(e) {
  var movementX = e.movementX ||
      e.mozMovementX          ||
      e.webkitMovementX       ||
      0,
  movementY = e.movementY ||
      e.mozMovementY      ||
      e.webkitMovementY   ||
      0;
}

Phát hiện lỗi

Nếu lỗi xảy ra khi nhập hoặc thoát khỏi chế độ khoá con trỏ, sự kiện pointerlockerror sẽ kích hoạt. Không có dữ liệu nào được đính kèm vào sự kiện này.

document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);

Có bắt buộc phải dùng chế độ toàn màn hình không?

Ban đầu, tính năng khoá con trỏ được liên kết với API FullScreen. Tức là một phần tử phải ở chế độ toàn màn hình thì mới có thể khoá con trỏ vào phần tử đó. Điều đó không còn đúng nữa và bạn có thể sử dụng tính năng khoá con trỏ cho bất kỳ phần tử nào trong ứng dụng của mình, dù ở chế độ toàn màn hình hay không.

Ví dụ về các nút điều khiển trong trò chơi bắn súng góc nhìn thứ nhất

Giờ đây, chúng ta đã bật tính năng khoá con trỏ và nhận sự kiện, đã đến lúc xem một ví dụ thực tế. Bạn có bao giờ muốn biết cách hoạt động của các nút điều khiển trong Quake không? Hãy chuẩn bị sẵn sàng vì tôi sắp giải thích các hàm này bằng mã!

Các chế độ điều khiển trong trò chơi bắn súng góc nhìn thứ nhất được xây dựng dựa trên 4 cơ chế cốt lõi:

  • Di chuyển về phía trước và phía sau dọc theo vectơ chế độ xem hiện tại
  • Di chuyển sang trái và phải dọc theo vectơ di chuyển sang trái và phải hiện tại
  • Xoay góc nhìn (sang trái và sang phải)
  • Xoay độ dốc của chế độ xem (lên và xuống)

Một trò chơi triển khai giao thức điều khiển này chỉ cần 3 phần dữ liệu: vị trí máy ảnh, vectơ hướng máy ảnh và vectơ lên không đổi. Vectơ lên trên luôn là (0, 1, 0). Cả 4 cơ chế trên chỉ điều khiển vị trí máy ảnh và vectơ hướng máy ảnh theo nhiều cách.

Phong trào

Đầu tiên là chuyển động. Trong bản minh hoạ bên dưới, thao tác di chuyển được liên kết với các phím W, A, S và D tiêu chuẩn. Các phím W và S di chuyển máy ảnh về phía trước và phía sau. Trong khi phím A và D di chuyển máy ảnh sang trái và phải. Bạn có thể di chuyển máy ảnh về phía trước và phía sau một cách đơn giản:

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

Bạn cần có hướng di chuyển sang trái và phải để di chuyển sang trái và phải. Bạn có thể tính toán hướng di chuyển ngang bằng cách sử dụng tích vô hướng:

// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);

Sau khi bạn có hướng di chuyển ngang, việc triển khai chuyển động ngang cũng giống như di chuyển về phía trước hoặc lùi.

Tiếp theo là xoay thành phần hiển thị.

Góc trệch

Độ nghiêng hoặc độ xoay ngang của chế độ xem máy ảnh chỉ là một vòng quay xung quanh vectơ lên không đổi. Dưới đây là mã chung để xoay vectơ hướng máy ảnh xung quanh một trục tuỳ ý. Phương thức này hoạt động bằng cách tạo một quaternion biểu thị góc xoay deltaAngle radian xung quanh axis, sau đó sử dụng quaternion để xoay vectơ hướng nhìn của máy ảnh:

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

Cao độ

Việc triển khai độ dốc hoặc xoay theo chiều dọc của chế độ xem máy ảnh cũng tương tự, nhưng thay vì xoay xung quanh vectơ lên, bạn sẽ áp dụng chế độ xoay xung quanh vectơ di chuyển ngang. Bước đầu tiên là tính toán vectơ di chuyển ngang, sau đó xoay vectơ hướng nhìn của máy ảnh xung quanh trục đó.

Tóm tắt

Pointer Lock API cho phép bạn kiểm soát con trỏ chuột. Nếu bạn đang tạo trò chơi trên web, người chơi sẽ rất thích khi họ không bị bắn hạ nữa vì họ đã hào hứng di chuyển chuột ra khỏi cửa sổ và trò chơi của bạn không nhận được thông tin cập nhật về chuột nữa. Cách sử dụng rất đơn giản:

  • Thêm trình nghe sự kiện pointerlockchange để theo dõi trạng thái của tính năng khoá con trỏ
  • Yêu cầu khoá con trỏ cho một phần tử cụ thể
  • Thêm trình nghe sự kiện mousemove để nhận thông tin cập nhật

Bản minh hoạ bên ngoài

Tài liệu tham khảo