איך לשחק במשחק Chrome Dino באמצעות הגיימפאד

כדאי ללמוד איך להשתמש ב-Gamepad API כדי לקדם את משחקי האינטרנט שלכם לרמה הבאה.

ביצת ההפתעה של הדף אופליין ב-Chrome היא אחד מהסודות השמורים ביותר בהיסטוריה ([citation needed], אבל הטענה הזו מבוססת על האפקט הדרמטי). אם מקישים על מקש הרווח או מקישים על הדינוזאור במכשירים ניידים, הדף אופליין הופך למשחק ארקייד שאפשר לשחק בו. יכול להיות שתשימו לב שאתם לא צריכים לעבור למצב אופליין אם מתחשק לכם לשחק: ב-Chrome תוכלו פשוט לנווט אל about://dino, או, לחנונים שבפנים, לגלוש אל about://network-error/-106. אבל האם ידעתם ש-270 מיליון משחקי Chrome Dino משחקים בכל חודש?

דף אופליין של Chrome עם משחק Chrome Dino.
מקישים על מקש הרווח כדי לשחק!

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

איך משתמשים ב-Gamepad API

זיהוי תכונות ותמיכה בדפדפן

ל-Gamepad API יש תמיכה מעולה בדפדפנים במחשבים ובניידים. אפשר לבדוק אם יש תמיכה ב-Gamepad API באמצעות קטע הקוד הבא:

if ('getGamepads' in navigator) {
  // The API is supported!
}

איך הדפדפן מייצג גיימפאד

הדפדפן מייצג גיימפאד כאובייקטים Gamepad. ל-Gamepad יש את המאפיינים (properties) הבאים:

  • id: מחרוזת זיהוי לגיימפאד. המחרוזת הזו מזהה את המותג או הסגנון של מכשיר הגיימפאד המחובר.
  • displayId: VRDisplay.displayId של VRDisplay המשויך (אם רלוונטי).
  • index: האינדקס של הגיימפאד בניווט.
  • connected: מציין אם הגיימפאד עדיין מחובר למערכת.
  • hand: טיפוסים בני מנייה (enum) שקובעים באיזו יד שלט רחוק מוחזק, או שסביר להניח שהוא יחזיק בה.
  • timestamp: הפעם האחרונה שהנתונים של הגיימפאד הזה עודכנו.
  • mapping: מיפוי הלחצן והצירים שבשימוש במכשיר הזה – "standard" או "xr-standard".
  • pose: אובייקט GamepadPose שמייצג את פרטי התנוחה שמשויכת לבקרת WebVR.
  • axes: מערך של ערכים לכל הצירים של הגיימפאד, מנורמלים באופן לינארי לטווח של -1.0 עד 1.0.
  • buttons: מערך של מצבי לחצנים לכל הלחצנים בגיימפאד.

חשוב לזכור שהלחצנים יכולים להיות דיגיטליים (לחצים או לא לחוצים) או אנלוגיים (לדוגמה, לחיצה של 78%). לכן לחצנים מדווחים כאובייקטים GamepadButton עם המאפיינים הבאים:

  • pressed: המצב שנלחץ (true אם לוחצים על הלחצן, ו-false אם לא לוחצים עליו.
  • touched: המצב שבו הלחצן נגע. אם הלחצן יכול לזהות מגע, המאפיין הזה הוא true אם נוגעים בלחצן. אחרת, המאפיין false.
  • value: בלחצנים עם חיישן אנלוגי, המאפיין הזה מייצג את כמות הלחיצה על הלחצן, מנורמל לינארי בטווח שבין 0.0 ל-1.0.
  • hapticActuators: מערך שמכיל אובייקטים GamepadHapticActuator, שכל אחד מהם מייצג חומרת משוב פיזי שזמינה בבקר.

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

  • Dual-Rumble: אפקט המשוב הפיזי שנוצר על ידי שני מפעילי מסה אקסצנטריים מסתובבים, אחד בכל אחת מנקודות האחיזה בגיימפאד.
  • טריגר התווספות משוב (Trigger-Rumble): אפקט המשוב הפיזי שנוצר על ידי שני מנועים עצמאיים, כשמנוע אחד ממוקם בכל אחד מהטריגרים של הגיימפאד.

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

סקירה סכמטית של מיפויי הלחצנים והצירים של לוח משחקים נפוץ.
ייצוג חזותי של פריסה רגילה של גיימפאד (מקור).

קבלת התראה כשמתבצע חיבור של גיימפאד

כדי לדעת מתי גיימפאד מחובר, מקישים על האירוע gamepadconnected שמופעל באובייקט window. כשהמשתמש מחבר גיימפאד, פעולה יכולה להתרחש באמצעות USB או באמצעות Bluetooth, מופעל GamepadEvent שכולל את פרטי הגיימפאד בנכס gamepad בשם הנכון. בקטע הבא אפשר לראות דוגמה בשלט רחוק של Xbox 360 ששכבה בו (כן, אני אוהבת גיימינג בסגנון רטרו).

window.addEventListener('gamepadconnected', (event) => {
  console.log('✅ 🎮 A gamepad was connected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: true
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: GamepadHapticActuator {type: "dual-rumble"}
  */
});

התראה כשמנתקים את הגיימפאד

קבלת ההתראות על ניתוקים מגיימפאד קורית באופן דומה לאופן הזיהוי של החיבורים. הפעם האפליקציה מאזינה לאירוע gamepaddisconnected. שים לב שבדוגמה הבאה, connected נקראת עכשיו false כשמנתקים את השלט רחוק של Xbox 360.

window.addEventListener('gamepaddisconnected', (event) => {
  console.log('❌ 🎮 A gamepad was disconnected:', event.gamepad);
  /*
    gamepad: Gamepad
    axes: (4) [0, 0, 0, 0]
    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
    connected: false
    id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
    index: 0
    mapping: "standard"
    timestamp: 6563054.284999998
    vibrationActuator: null
  */
});

הגיימפאד ב-gameplay

שמירה של גיימפאד מתחילה בקריאה ל-navigator.getGamepads(), שמחזירה מערך עם Gamepad פריטים. למערך ב-Chrome תמיד יש אורך קבוע של ארבעה פריטים. אם מחוברים אל אפס או פחות מארבעה לוחות גיימפאד, הפריט יכול להיות רק null. חשוב תמיד לבדוק את כל הפריטים במערך, ושימו לב ש-Gamepads "זוכרים" את מיקום המשבצת שלהם ולא תמיד נמצאים במשבצת הראשונה שזמינה.

// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]

אם אחד או יותר מהגיימפאדים מחוברים אבל navigator.getGamepads() עדיין מדווח על null פריטים, יכול להיות שתצטרכו 'להעיר' כל גיימפאד על ידי לחיצה על אחד מהלחצנים שלו. לאחר מכן תוכלו לדגום את המצבים של הגיימפאד בלולאת המשחקים, כפי שמוצג בקוד הבא.

const pollGamepads = () => {
  // Always call `navigator.getGamepads()` inside of
  // the game loop, not outside.
  const gamepads = navigator.getGamepads();
  for (const gamepad of gamepads) {
    // Disregard empty slots.
    if (!gamepad) {
      continue;
    }
    // Process the gamepad state.
    console.log(gamepad);
  }
  // Call yourself upon the next animation frame.
  // (Typically this happens every 60 times per second.)
  window.requestAnimationFrame(pollGamepads);
};
// Kick off the initial game loop iteration.
pollGamepads();

מפעיל הרטט

המאפיין vibrationActuator מחזיר אובייקט GamepadHapticActuator, שתואם להגדרה של מנועים או מפעילים אחרים שיכולים להפעיל כוח למטרות משוב פיזי. אפשר להפעיל אפקטים פיזיים בהתקשרות אל Gamepad.vibrationActuator.playEffect(). סוג האפקט החוקי היחיד הוא 'dual-rumble'. רעשן כפול מתאר הגדרה אישית של משוב פיזי עם מנוע רטט מסה אקסצנטרי שמסתובב ומסתובב בכל יד של משחק גיימפאד סטנדרטי. בתצורה הזו, כל אחד מהמנועים יכול לרטוט את כל הגיימפאד. שתי המסות שונות, כך שניתן לשלב את ההשפעות של כל אחת מהן וליצור אפקטים פיזיים מורכבים יותר. אפקטים של רעש כפול מוגדרים באמצעות ארבעה פרמטרים:

  • duration: להגדרת משך אפקט הרטט באלפיות השנייה.
  • startDelay: מגדירה את משך ההשהיה עד שהרטט יתחיל.
  • strongMagnitude ו-weakMagnitude: קובעים את רמות עוצמת הרטט למנועי המסה האקסצנטריים הכבדים והקלים יותר, מנורמלים לטווח 0.0-1.0.

אפקטים נתמכים של ראמבל

if (gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
  // Trigger rumble supported.
} else if (gamepad.vibrationActuator.effects.includes('dual-rumble')) {
  // Dual rumble supported.
} else {
  // Rumble effects aren't supported.
}

רעשן כפול

// This assumes a `Gamepad` as the value of the `gamepad` variable.
const dualRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  gamepad.vibrationActuator.playEffect('dual-rumble', {
    // Start delay in ms.
    startDelay: delay,
    // Duration in ms.
    duration: duration,
    // The magnitude of the weak actuator (between 0 and 1).
    weakMagnitude: weak,
    // The magnitude of the strong actuator (between 0 and 1).
    strongMagnitude: strong,
  });
};

הפעלת רעשן

// This assumes a `Gamepad` as the value of the `gamepad` variable.
const triggerRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
  if (!('vibrationActuator' in gamepad)) {
    return;
  }
  // Feature detection.
  if (!('effects' in gamepad.vibrationActuator) || !gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
    return;
  }
  gamepad.vibrationActuator.playEffect('trigger-rumble', {
    // Duration in ms.
    duration: duration,
    // The left trigger (between 0 and 1).
    leftTrigger: leftTrigger,
    // The right trigger (between 0 and 1).
    rightTrigger: rightTrigger,
  });
};

שילוב עם מדיניות ההרשאות

במפרט של Gamepad API מוגדר תכונה מבוקרת שהמחרוזת "gamepad" מזהה. ערך ברירת המחדל של allowlist הוא "self". מדיניות ההרשאות של המסמך קובעת אם לתוכן כלשהו במסמך הזה מותר לגשת אל navigator.getGamepads(). אם המדיניות מושבתת באף מסמך, לא תהיה אפשרות להשתמש בתוכן במסמך navigator.getGamepads(), וגם האירועים gamepadconnected ו-gamepaddisconnected לא יופעלו.

<iframe src="index.html" allow="gamepad"></iframe>

הדגמה (דמו)

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

בונוס: כדאי לשחק ב-Chrome Dino באתר web.dev

תוכלו לשחק ב-Chrome Dino עם הגיימפאד באתר הזה. קוד המקור זמין ב-GitHub. כדאי לראות את הטמעת הסקרים של הגיימפאד ב-trex-runner.js ולראות איך מתבצע אמולציה של לחיצות על מקשים.

כדי שההדגמה של Chrome dino Gamepad תפעל, מחקתי את משחק Chrome dino מהפרויקט המרכזי של Chromium (עדכון בוצע קודם על ידי ארנל בלאן), הצבתי אותו באתר עצמאי, הרחבתי את הטמעת ה-API של הגיימפאד הקיים על ידי הוספת אפקטים של הנמכה ורטט, יצרתי מצב מסך מלא ומצב הטמעה של Mehul Satardekar. גיימינג שמח!

אישורים

המסמך הזה נבדק על ידי פרנסואה בופורט וג'ו מדלי. המפרט של Gamepad API נערך על ידי סטיב אגוסטון, ג'יימס הולייר ומאט ריינולדס. עורכי המפרט הקודמים הם ברנדון ג'ונס, סקוט גרהאם וטד מילצ'ארק. המפרט של תוספי Gamepad נערך על ידי Brandon Jones. תמונה ראשית (Hero) מאת לורה טורנט פואיג.