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

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

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

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

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

שימוש ב-Gamepad API

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

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

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

איך הדפדפן מייצג בקר משחקים

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

  • id: מחרוזת מזהה של משחקן הווידאו. המחרוזת הזו מזהה את המותג או הסגנון של מכשיר ה-gamepad המחובר.
  • displayId: הערך VRDisplay.displayId של VRDisplay משויך (אם רלוונטי).
  • index: האינדקס של הגיימפאד בכלי הניווט.
  • connected: מציין אם השלט עדיין מחובר למערכת.
  • hand: enum שמגדיר באיזו יד השלט מוחזק, או באיזו יד סביר להניח שהוא מוחזק.
  • timestamp: הפעם האחרונה שבה עודכנו הנתונים של שלט ה-Gamepad הזה.
  • 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: אפקט המשוב החזותי שנוצר על ידי שני מפעילי מסה מסתובבים אקסצנטריים, אחד בכל אחיזה של השליטה.
  • רעידות בלחיצה: אפקט המשוב החזותי שנוצר על ידי שני מנועים עצמאיים, כאשר מנוע אחד ממוקם בכל אחד מהטריגרים של שלט המשחק.

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

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

התראה כשגיימפאד מחובר

כדי לדעת מתי מחובר בקר משחקים, מקשיבים לאירוע 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
  */
});

הגיימפאד בגיימפליי

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

// 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' ו-'trigger-rumble'.

אפקטים נתמכים של רעידות

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.
}

רעידות כפולות

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

  • duration: הגדרת משך אפקט הרטט באלפיות שנייה.
  • startDelay: הגדרת משך ההשהיה עד שהרטט יופעל.
  • strongMagnitude ו-weakMagnitude: הגדרת רמות עוצמת הרטט של המנועים עם המסה המסתובבת החריגה (eccentric) הכבדה והקלה יותר, תוך נורמליזציה לטווח 0.0 עד 1.0.
// 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 עם שלט תפעל, הוצאתי את משחק הדינוזאור של Chrome מפרויקט הליבה של Chromium (עדכון של מאמץ קודם של Arnelle Ballane), העברתי אותו לאתר עצמאי, הרחיבתי את ההטמעה הקיימת של API למשחקי מחשב על ידי הוספת אפקטים של צלילים ורעידות, יצרתי מצב מסך מלא וMehul Satardekar תרם להטמעה של מצב כהה. גיימינג מהנה!

אישורים

המסמך הזה נבדק על ידי פרנסואה בופורט ועל ידי ג'ו מדלי. המפרט של Gamepad API נערך על ידי Steve Agoston, James Hollyer ו-Matt Reynolds. עורכי המפרט הקודמים הם ברנדון ג'ונס, סקוט גרהם וטד מיילצ'ארק. ברנדון ג'ונס הוא העורך של המפרט של תוספים לגיימפאד. התמונה הראשית (Hero) היא של Laura Torrent Puig.