נעילת המצביע ולחצני יריות בגוף ראשון

ג'ון מק'צ'ן
ג'ון מקצ'אן

מבוא

Pointer Lock API עוזר להטמיע בצורה נכונה אמצעי בקרה של יריות בגוף ראשון במשחק דפדפן. ללא תנועת עכבר יחסית, סמן השחקן היה יכול, למשל, לפגוע בקצה הימני של המסך, וכל תנועות נוספות ימינה יהיו מבוטלות – התצוגה לא תמשיך לנוע ימינה, והשחקן לא יוכל לעקוב אחר האנשים הרעים ולהרוס אותם באמצעות הרעה שלו. השחקן יתקלקל ויתאכזב. התנהגות לא אופטימלית כזו לא יכולה להתרחש כשמשתמשים בנעילת הסמן.

Pointer Lock API מאפשר לאפליקציה לבצע את הפעולות הבאות:

  • גישה לנתוני עכבר גולמיים, כולל תנועות עכבר יחסיות
  • ניתוב כל אירועי העכבר לרכיב ספציפי

כתוצאה מהפעלת נעילת המצביע, סמן העכבר מוסתר ומאפשר לך לבחור לצייר מצביע ספציפי ליישום אם תרצה, או להשאיר את מצביע העכבר מוסתר כדי שהמשתמש יוכל להזיז את המסגרת באמצעות העכבר. תנועת עכבר יחסית היא הדלתא של מיקום מצביע העכבר מהמסגרת הקודמת, ללא קשר למיקום המוחלט. לדוגמה, אם מצביע העכבר עבר מ-(640, 480) ל-(520, 490), התנועה היחסית הייתה (-120, 10). למטה מוצגת דוגמה אינטראקטיבית שמציגה הפרשות של מיקום עכבר גולמיים.

במדריך הזה עוסקים בשני נושאים: את הנקודות העיקריות בהפעלה ובעיבוד של אירועי נעילת מצביע והטמעה של סכמת השליטה ביריות בגוף ראשון. נכון, אחרי קריאת המאמר הזה תדעו איך להשתמש בנעילת מצביע ולהטמיע פקדים בסגנון Quake במשחק דפדפן משלכם!

תאימות דפדפן

תמיכה בדפדפן

  • 37
  • 13
  • 50
  • 10.1

מקור

מכניקה של נעילת מצביע

זיהוי תכונה

כדי לקבוע אם הדפדפן של המשתמש תומך בנעילת מצביע, עליך לחפש באובייקט המסמך pointerLockElement או גרסה עם קידומת ספק. בקוד:

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

בשלב זה נעילת הסמן זמינה רק ב-Firefox וב-Chrome. Opera ו-IE עדיין לא תומכים בו.

בהפעלה

הפעלה של נעילת מצביע היא תהליך דו-שלבי. בשלב הראשון האפליקציה מבקשת להפעיל נעילת מצביע לרכיב מסוים, ומיד אחרי שהמשתמש נותן הרשאה, מופעל אירוע 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.

טיפול באירועים

קיימים שני אירועים שהאפליקציה שלכם חייבת להוסיף להם מאזינים. הראשונה היא pointerlockchange, שמופעלת בכל פעם שמתרחש שינוי במצב נעילת המצביע. הערך השני הוא 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);

בתוך הקריאה החוזרת (callback) של 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 של הקריאה החוזרת (callback) ניתן לחלץ נתונים של תנועת עכבר יחסית מהשדות 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? כנסו לכאן כי אני עומד להסביר אותם באמצעות קוד!

משחקי יריות בגוף ראשון בנויים סביב ארבעה מנגנונים:

  • תזוזה קדימה ואחורה לאורך וקטור המראה הנוכחי
  • תנועה שמאלה וימינה לאורך וקטור הסטארטה הנוכחי
  • סיבוב של תנועת התצוגה (שמאלה וימינה)
  • סיבוב גובה הצליל (למעלה ולמטה)

במשחק שמטמיע את סכמת הבקרה הזו נדרשים רק שלושה קטעי נתונים: מיקום המצלמה, וקטור מראה המצלמה ווקטור קבוע. הווקטור למעלה הוא תמיד (0, 1, 0). כל ארבע הפעולות שצוינו למעלה פשוט משנות את מיקום המצלמה והמראה של המצלמה בווקטורים בדרכים שונות.

תנועה

הדבר הראשון בסיפון הוא תנועה. בהדגמה שלמטה התנועה ממופה למפתחות הרגילים 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);

גובה צליל

יישום הסיבוב או הסיבוב האנכי של תצוגת המצלמה דומה, אבל במקום סיבוב סביב הווקטור למעלה, אתה מפעיל סיבוב סביב וקטור הסטרה. השלב הראשון הוא לחשב את וקטור ה-Strafe, ולאחר מכן לסובב את הווקטור של מראה המצלמה מסביב לציר הזה.

סיכום

ה-Pointer Lock API מאפשר לך לשלוט בסמן העכבר. אם אתם מכינים משחקי אינטרנט, השחקנים יאהבו את זה כשהם יפסיקו לקבל פרגון כי הם הזיזו את העכבר בהתלהבות מהחלון והמשחק שלכם הפסיק לקבל עדכונים. השימוש פשוט:

  • הוספה של event listener של pointerlockchange כדי לעקוב אחרי מצב הנעילה של הסמן
  • שליחת בקשה לנעילת מצביע לרכיב ספציפי
  • יש להוסיף event listener של mousemove כדי לקבל עדכונים

הדגמות חיצוניות

קובצי עזר