บทนำ
Pointer Lock API ช่วยให้ใช้การควบคุมเกมยิงมุมมองบุคคลที่หนึ่งในเกมบนเบราว์เซอร์ได้อย่างถูกต้อง หากไม่มีการเคลื่อนไหวของเมาส์แบบสัมพัทธ์ เคอร์เซอร์ของผู้เล่นอาจไปชนขอบขวาของหน้าจอและการเคลื่อนไหวเพิ่มเติมไปทางขวาจะถูกละเว้น มุมมองจะไม่เลื่อนไปทางขวาต่อ และผู้เล่นจะไม่สามารถไล่ล่าคนร้ายและยิงด้วยปืนกล ผู้เล่นจะตายและรู้สึกหงุดหงิด เมื่อใช้การล็อกเคอร์เซอร์ ลักษณะการทำงานที่ไม่เหมาะสมนี้จะไม่เกิดขึ้น
Pointer Lock API ช่วยให้แอปพลิเคชันของคุณทําสิ่งต่อไปนี้ได้
- รับสิทธิ์เข้าถึงข้อมูลเมาส์ดิบ รวมถึงการเคลื่อนไหวของเมาส์แบบสัมพัทธ์
- กําหนดเส้นทางเหตุการณ์เมาส์ทั้งหมดไปยังองค์ประกอบที่เฉพาะเจาะจง
ผลข้างเคียงของการเปิดใช้การล็อกเคอร์เซอร์คือเคอร์เซอร์เมาส์จะซ่อนอยู่ ซึ่งจะช่วยให้คุณเลือกวาดเคอร์เซอร์เฉพาะแอปพลิเคชันได้หากต้องการ หรือจะซ่อนเคอร์เซอร์เมาส์ไว้เพื่อให้ผู้ใช้เลื่อนเฟรมด้วยเมาส์ก็ได้ การเคลื่อนไหวของเมาส์แบบสัมพัทธ์คือค่าเดลต้าของตำแหน่งเคอร์เซอร์เมาส์จากเฟรมก่อนหน้า โดยไม่คำนึงถึงตำแหน่งสัมบูรณ์ เช่น หากเคอร์เซอร์ของเมาส์ย้ายจาก (640, 480) ไปยัง (520, 490) การเคลื่อนไหวแบบสัมพัทธ์คือ (-120, 10) ดูตัวอย่างแบบอินเทอร์แอกทีฟที่แสดงค่า Delta ตำแหน่งเมาส์ดิบได้ที่ด้านล่าง
บทแนะนำนี้ครอบคลุม 2 หัวข้อ ได้แก่ ข้อมูลเบื้องต้นเกี่ยวกับการเปิดใช้งานและการประมวลผลเหตุการณ์การล็อกเคอร์เซอร์ และการใช้รูปแบบการควบคุมการยิงปืนในมุมมองบุคคลที่หนึ่ง ถูกต้อง เมื่ออ่านบทความนี้จบแล้ว คุณจะทราบวิธีใช้การล็อกเคอร์เซอร์และการใช้การควบคุมสไตล์ Quake สำหรับเกมในเบราว์เซอร์ของคุณเอง
ความเข้ากันได้กับเบราว์เซอร์
กลไกการล็อกเคอร์เซอร์
การตรวจจับองค์ประกอบ
หากต้องการตรวจสอบว่าเบราว์เซอร์ของผู้ใช้รองรับการล็อกเคอร์เซอร์หรือไม่ คุณต้องตรวจสอบ 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"
การจัดการเหตุการณ์
แอปพลิเคชันของคุณต้องเพิ่ม 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
เพื่อรับข้อมูลอัปเดต