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