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 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 di chuyển chuột tương đối, con trỏ của người chơi có thể nhấn vào cạnh phải của màn hình và bất kỳ di chuyển nào khác sang bên phải sẽ bị giảm - tầm nhìn sẽ không tiếp tục dịch chuyển sang phải và người chơi sẽ không thể truy đuổi kẻ xấu và dùng súng máy của mình để truy đuổi kẻ xấu. Người chơi sẽ cảm thấy tức giận và trở nên thất vọng. Với khoá con trỏ, hành vi dưới mức tối ưu này không thể xảy ra.

API Khoá Pointer 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ả di chuyển chuột tương đối
  • Định tuyến tất cả các sự kiện chuột đến một phần tử cụ thể

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

Phần hướng dẫn này đề cập đến 2 chủ đề: cơ bản để kích hoạt và xử lý các sự kiện khoá con trỏ, cũng như cách triển khai lược đồ điều khiển 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 phương thức khóa con trỏ và triển khai các biện pháp điều khiển kiểu Quake cho trò chơi trên trình duyệt của riêng bạn!

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

Hỗ trợ trình duyệt

  • 37
  • 13
  • 50
  • 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ợ khoá con trỏ hay không, bạn cần kiểm tra pointerLockElement hoặc phiên bản có tiền tố 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 khóa 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

Kích hoạt khoá con trỏ là quy trình gồm 2 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ể. Sự kiện pointerlockchange sẽ được kích hoạt ngay sau khi người dùng cấp quyền. Người dùng có thể huỷ khoá con trỏ bất cứ lúc nào bằng cách nhấn phím Escape. Ứng dụng của bạn cũng có thể thường xuyên thoát khỏi khoá con trỏ. Khi khoá con trỏ bị huỷ, 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();

Chỉ cần mã ở trên là được. Khi trình duyệt khoá con trỏ, một bong bóng sẽ bật lên thông báo cho 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ỷ con trỏ bằng cách nhấn phím 'Esc'.

Thanh thông tin về Khoá con trỏ trong Chrome.
Thanh thông tin về Pointer Lock trong Chrome.

Xử lý sự kiện

Có 2 sự kiện mà ứng dụng của bạn phải thêm trình nghe. Kiểu đầu tiên là pointerlockchange. Mã này sẽ kích hoạt bất cứ khi nào có sự thay đổi về trạng thái khoá con trỏ. Hàm thứ hai là mousemove, 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);

Trong lệnh gọi lại pointerlockchange, bạn phải kiểm tra xem con trỏ vừa được khoá hoặc mở khoá hay không. Việc xác định xem tính năng khoá con trỏ có đang bật hay không rất đơn giản: kiểm tra xem document.pointerLockElement có bằng với phần tử mà tính năng khoá con trỏ được yêu cầu hay không. Nếu có thì ứng dụng của bạn đã khoá con trỏ thành công. Nếu chưa, người dùng hoặc mã của 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 khoá con trỏ, clientX, clientY, screenXscreenY không thay đổi. movementXmovementY được cập nhật bằng số lượng pixel mà con trỏ đáng lẽ sẽ di chuyển kể từ sự kiện cuối cùng đượ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, dữ liệu chuyển động tương đối của chuột có thể được trích xuấ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;
}

Lỗi phát hiện

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

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

Bắt buộc phải bật chế độ toàn màn hình?

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

Ví dụ về chế độ điều khiển bắn súng góc nhìn thứ nhất

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

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 tới và lùi dọc theo vectơ tìm kiếm hiện tại
  • Di chuyển sang trái và phải dọc theo vectơ strafe hiện tại
  • Xoay yếm chế độ xem (trái và phải)
  • Xoay cao độ xem (lên và xuống)

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

Phong trào

Đầu tiên là chuyển động. Trong bản minh hoạ bên dưới, chuyển động được ánh xạ với các phím W, A, S và D tiêu chuẩn. Phím W và S điều khiển camera tiến và lùi. Trong khi phím A và D điều khiển camera sang trái và phải. Việc di chuyển máy ảnh về phía trước và phía sau rất đơ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);

Ngang trái và phải theo hướng thẳng. Hướng strafe có thể được tính bằng cách sử dụng tích chéo:

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

Sau khi bạn xác định được hướng di chuyển, việc triển khai chuyển động đó cũng giống như việc tiến hoặc lùi.

Tiếp theo là xoay chế độ xem.

Yaw

Yaw hay xoay ngang của chế độ xem camera chỉ là một xoay xung quanh vectơ hướng lên không đổi. Dưới đây là mã chung để xoay vectơ giao diện máy ảnh xung quanh một trục tuỳ ý. Lớp này hoạt động bằng cách tạo một quaternion đại diện cho độ xoay deltaAngle radian xung quanh axis, sau đó sử dụng quaternion để xoay vectơ giao diện 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);

Đề cử

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

Tóm tắt

Pointer Lock API (API Khoá con trỏ) cho phép bạn điều khiển con trỏ chuột. Nếu bạn đang tạo các trò chơi trên web, người chơi sẽ yêu thích nó khi họ không còn thấy chán vì họ thích thú di chuột ra khỏi cửa sổ và trò chơi của bạn không còn nhận được 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 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