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

จอห์น แมคคัตจัง
จอห์น แมคคัตจัง

เกริ่นนำ

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

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

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

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

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

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

การสนับสนุนเบราว์เซอร์

  • 37
  • 13
  • 50
  • 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"

แถบข้อมูล Pointer Lock ใน Chrome
แถบข้อมูลการล็อกตัวชี้ใน Chrome

การจัดการกิจกรรม

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

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

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

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

ถัดไปคือการหมุนมุมมอง

การเอียง

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

เสนอเพลง

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

สรุป

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

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

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

รายการอ้างอิง