ล็อกตัวชี้และการควบคุมการยิงมุมมองบุคคลที่หนึ่ง

บทนำ

Pointer Lock API ช่วยให้ใช้การควบคุมเกมยิงมุมมองบุคคลที่หนึ่งในเกมบนเบราว์เซอร์ได้อย่างถูกต้อง หากไม่มีการเคลื่อนไหวของเมาส์แบบสัมพัทธ์ เคอร์เซอร์ของผู้เล่นอาจไปชนขอบขวาของหน้าจอและการเคลื่อนไหวเพิ่มเติมไปทางขวาจะถูกละเว้น มุมมองจะไม่เลื่อนไปทางขวาต่อ และผู้เล่นจะไม่สามารถไล่ล่าคนร้ายและยิงด้วยปืนกล ผู้เล่นจะตายและรู้สึกหงุดหงิด เมื่อใช้การล็อกเคอร์เซอร์ ลักษณะการทำงานที่ไม่เหมาะสมนี้จะไม่เกิดขึ้น

Pointer Lock API ช่วยให้แอปพลิเคชันของคุณทําสิ่งต่อไปนี้ได้

  • รับสิทธิ์เข้าถึงข้อมูลเมาส์ดิบ รวมถึงการเคลื่อนไหวของเมาส์แบบสัมพัทธ์
  • กําหนดเส้นทางเหตุการณ์เมาส์ทั้งหมดไปยังองค์ประกอบที่เฉพาะเจาะจง

ผลข้างเคียงของการเปิดใช้การล็อกเคอร์เซอร์คือเคอร์เซอร์เมาส์จะซ่อนอยู่ ซึ่งจะช่วยให้คุณเลือกวาดเคอร์เซอร์เฉพาะแอปพลิเคชันได้หากต้องการ หรือจะซ่อนเคอร์เซอร์เมาส์ไว้เพื่อให้ผู้ใช้เลื่อนเฟรมด้วยเมาส์ก็ได้ การเคลื่อนไหวของเมาส์แบบสัมพัทธ์คือค่าเดลต้าของตำแหน่งเคอร์เซอร์เมาส์จากเฟรมก่อนหน้า โดยไม่คำนึงถึงตำแหน่งสัมบูรณ์ เช่น หากเคอร์เซอร์ของเมาส์ย้ายจาก (640, 480) ไปยัง (520, 490) การเคลื่อนไหวแบบสัมพัทธ์คือ (-120, 10) ดูตัวอย่างแบบอินเทอร์แอกทีฟที่แสดงค่า Delta ตำแหน่งเมาส์ดิบได้ที่ด้านล่าง

บทแนะนำนี้ครอบคลุม 2 หัวข้อ ได้แก่ ข้อมูลเบื้องต้นเกี่ยวกับการเปิดใช้งานและการประมวลผลเหตุการณ์การล็อกเคอร์เซอร์ และการใช้รูปแบบการควบคุมการยิงปืนในมุมมองบุคคลที่หนึ่ง ถูกต้อง เมื่ออ่านบทความนี้จบแล้ว คุณจะทราบวิธีใช้การล็อกเคอร์เซอร์และการใช้การควบคุมสไตล์ Quake สำหรับเกมในเบราว์เซอร์ของคุณเอง

ความเข้ากันได้กับเบราว์เซอร์

การรองรับเบราว์เซอร์

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

แหล่งที่มา

กลไกการล็อกเคอร์เซอร์

การตรวจจับองค์ประกอบ

หากต้องการตรวจสอบว่าเบราว์เซอร์ของผู้ใช้รองรับการล็อกเคอร์เซอร์หรือไม่ คุณต้องตรวจสอบ pointerLockElement หรือเวอร์ชันที่มีคำนำหน้าของผู้ให้บริการในออบเจ็กต์เอกสาร ในโค้ด

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

ปัจจุบันการล็อกเคอร์เซอร์ใช้ได้ใน Firefox และ Chrome เท่านั้น แต่ Opera และ IE ยังไม่รองรับ

กำลังเปิดใช้งาน

การเปิดใช้งานการล็อกเคอร์เซอร์เป็นกระบวนการ 2 ขั้นตอน ก่อนอื่น แอปพลิเคชันจะขอให้เปิดใช้การล็อกเคอร์เซอร์สําหรับองค์ประกอบที่เฉพาะเจาะจง และเหตุการณ์ 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"

แถบข้อมูลการล็อกเคอร์เซอร์ใน Chrome
แถบข้อมูลการล็อกเคอร์เซอร์ใน Chrome

การจัดการเหตุการณ์

แอปพลิเคชันของคุณต้องเพิ่ม Listener ให้กับเหตุการณ์ 2 รายการ รายการแรกคือ pointerlockchange ซึ่งจะทํางานทุกครั้งที่มีการเปลี่ยนแปลงสถานะการล็อกเคอร์เซอร์ ส่วนเหตุการณ์ที่ 2 คือ 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 callback คุณต้องตรวจสอบว่าเพิ่งมีการล็อกหรือปลดล็อกเคอร์เซอร์ การตรวจสอบว่าเปิดใช้การล็อกเคอร์เซอร์ไว้หรือไม่นั้นง่ายมาก เพียงตรวจสอบว่า 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);

ต้องเปิดใช้โหมดเต็มหน้าจอไหม

เดิมการล็อกเคอร์เซอร์จะเชื่อมโยงกับ FullScreen API ซึ่งหมายความว่าองค์ประกอบต้องอยู่ในโหมดเต็มหน้าจอก่อนจึงจะล็อกเคอร์เซอร์ไว้กับองค์ประกอบนั้นได้ แต่ตอนนี้คุณใช้การล็อกเคอร์เซอร์กับองค์ประกอบใดก็ได้ในแอปพลิเคชันไม่ว่าจะแบบเต็มหน้าจอหรือไม่ก็ตาม

ตัวอย่างการควบคุมเกมยิงมุมมองบุคคลที่หนึ่ง

เมื่อเปิดใช้การล็อกเคอร์เซอร์และรับเหตุการณ์แล้ว เรามาลองดูตัวอย่างการใช้งานจริงกัน คุณเคยอยากรู้วิธีการทำงานของการควบคุมใน Quake ไหม เตรียมตัวให้พร้อมเพราะเรากำลังจะอธิบายด้วยโค้ด

การควบคุมเกมยิงมุมมองบุคคลที่หนึ่งสร้างขึ้นจากกลไกหลัก 4 อย่างต่อไปนี้

  • เลื่อนไปข้างหน้าและข้างหลังตามเวกเตอร์การมองปัจจุบัน
  • เลื่อนไปทางซ้ายและขวาตามเวกเตอร์การเดินไปด้านข้างปัจจุบัน
  • หมุนมุมมองตามแนวราบ (ซ้ายและขวา)
  • หมุนระดับมุมมอง (ขึ้นและลง)

เกมที่ใช้รูปแบบการควบคุมนี้ต้องใช้ข้อมูลเพียง 3 รายการ ได้แก่ ตำแหน่งกล้อง เวกเตอร์การมองของกล้อง และเวกเตอร์ขึ้นแบบคงที่ เวกเตอร์ขึ้นคือ (0, 1, 0) เสมอ กลไกทั้ง 4 รายการข้างต้นเป็นเพียงการปรับตำแหน่งกล้องและเวกเตอร์การมองของกล้องในลักษณะต่างๆ

การเคลื่อนไหว

สิ่งแรกที่ต้องคำนึงถึงคือการเคลื่อนไหว ในเดโมด้านล่าง การเคลื่อนไหวจะแมปกับแป้น 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);

เมื่อคุณกำหนดทิศทางการเดินไปด้านข้างแล้ว การใช้การเคลื่อนไหวไปด้านข้างจะเหมือนกับการเดินไปข้างหน้าหรือข้างหลัง

ต่อไปคือการหมุนมุมมอง

การเอียง

การเอียงหรือการหมุนแนวนอนของมุมมองกล้องเป็นเพียงการหมุนรอบเวกเตอร์ขึ้นแบบคงที่ ด้านล่างนี้คือโค้ดทั่วไปสำหรับการหมุนเวกเตอร์การมองของกล้องรอบแกนที่กำหนดเอง โดยวิธีทํางานคือสร้าง Quaternion ที่แสดงการหมุน deltaAngle เรเดียนรอบ axis จากนั้นใช้ Quaternion เพื่อหมุนเวกเตอร์การมองของกล้อง

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

ระดับเสียง

การใช้ระดับหรือการหมุนแนวตั้งของมุมมองกล้องจะคล้ายกัน แต่คุณจะใช้การหมุนรอบเวกเตอร์การเคลื่อนที่ไปด้านข้างแทนการหมุนรอบเวกเตอร์ขึ้น ขั้นตอนแรกคือการคำนวณเวกเตอร์การเดินไปด้านข้าง จากนั้นหมุนเวกเตอร์การมองของกล้องรอบๆ แกนนั้น

สรุป

Pointer Lock API ช่วยให้คุณควบคุมเคอร์เซอร์เมาส์ได้ หากคุณกำลังสร้างเกมบนเว็บ ผู้เล่นจะดีใจเมื่อไม่โดนฆ่าเพราะพวกเขาตื่นเต้นจนเลื่อนเมาส์ออกจากหน้าต่างและเกมของคุณหยุดรับการอัปเดตเมาส์ การใช้งานนั้นง่ายดาย

  • เพิ่ม pointerlockchange event listener เพื่อติดตามสถานะของการล็อกเคอร์เซอร์
  • ขอการล็อกเคอร์เซอร์สำหรับองค์ประกอบที่เฉพาะเจาะจง
  • เพิ่ม Listener เหตุการณ์ mousemove เพื่อรับข้อมูลอัปเดต

การสาธิตภายนอก

ข้อมูลอ้างอิง