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

John McCutchan
John McCutchan

מבוא

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

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

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

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

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

תאימות דפדפן

תמיכה בדפדפן

  • Chrome: 37.
  • קצה: 13.
  • Firefox: 50.
  • Safari: 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);

בתוך הקריאה החוזרת של 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);

האם נדרש מסך מלא?

במקור, נעילת המצביע הייתה קשורה ל-FullScreen 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);

רצועה שמאלה וימינה דורשת כיוון ישר. ניתן לחשב את כיוון המכפלה באמצעות המכפלה הווקטורית (cross-product):

// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);

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

בשלב הבא, סיבוב התצוגה.

Yaw

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

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

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

קובצי עזר