מבוא
תנו למתחילים להשתמש במקלדות שלהם למשחקי הרפתקאות, בקצות האצבעות הרגישות שלהם לחיתוך פירות ובחיישני התנועה החדשים והמפוארים שלהם כדי להעמיד פנים שהם יכולים לרקוד כמו מייקל ג'קסון. (מבזק חדשות: הם לא יכולים). אבל אתם שונים. אתם טובים יותר. אתה מקצוען. מבחינתכם, המשחקים מתחילים ונגמרים עם שלט בידיים.
אבל רגע. האם אין לכם מזל אם אתם רוצים לתמוך בשלט משחק באפליקציית האינטרנט שלכם? כבר לא. Gamepad API החדש מאפשר לכם להשתמש ב-JavaScript כדי לקרוא את המצב של כל בקר שלט שמחובר למחשב. הוא כל כך חדש, שהוא הגיע ל-Chrome 21 רק בשבוע שעבר – והוא גם עומד להיתמך ב-Firefox (זמין כרגע בגרסה מיוחדת).
התזמון היה מצוין, כי לאחרונה הייתה לנו הזדמנות להשתמש בזה בדודל של Google בנושא ריצת משוכות 2012. במאמר הזה נסביר בקצרה איך הוספנו את Gamepad API לדודל, ומה למדנו במהלך התהליך.
בודק שלט המשחק
למרות שהם זמניים, מאחורי הקלעים של דודלים אינטראקטיביים יש בדרך כלל מערכות מורכבות. כדי שיהיה קל יותר להדגים על מה אנחנו מדברים, לקחנו את הקוד של שלט המשחק מהדודל, ויצרנו בודק פשוט של שלט המשחק. אתם יכולים להשתמש בו כדי לבדוק אם שלט המשחק בחיבור USB פועל כמו שצריך, וגם להציץ מאחורי הקלעים כדי לראות איך זה עובד.
אילו דפדפנים תומכים בזה כיום?
באילו שלטי משחק אפשר להשתמש?
באופן כללי, כל שלט משחק מודרני שנתמך במערכת שלכם באופן מובנה אמור לעבוד. בדקנו מגוון שלטי משחק, החל מבקרי USB של מותגים לא מוכרים במחשב PC, דרך שלטי משחק של PlayStation 2 שמחוברים באמצעות דונגל ל-Mac, ועד לבקרי Bluetooth שמחוברים למחשב נייד עם Chrome OS.
בתמונה הזו רואים כמה בקרי משחק שבהם השתמשנו כדי לבדוק את הדודל שלנו – "כן, אמא, זה באמת מה שאני עושה בעבודה". אם הבקר לא פועל או אם המקשים ממופים באופן שגוי, דווחו על באג ב-Chrome או ב-Firefox . (חשוב לבדוק את הבעיה בגרסה הכי עדכנית של כל דפדפן כדי לוודא שהיא לא תוקנה כבר).
זיהוי תכונות של Gamepad API<
ב-Chrome:
var gamepadSupportAvailable = !!navigator.webkitGetGamepads || !!navigator.webkitGamepads;
נראה שעדיין אי אפשר לזהות את זה ב-Firefox – הכול מבוסס על אירועים, וכל הגורמים המטפלים באירועים צריכים להיות מצורפים לחלון, מה שמונע את הפעולה של טכניקה אופיינית לזיהוי גורמים מטפלים באירועים.
אבל אנחנו בטוחים שזה זמני. הכלי המדהים Modernizr כבר מספק מידע על Gamepad API, ולכן אנחנו ממליצים להשתמש בו לכל צורכי הזיהוי הנוכחיים והעתידיים שלכם:
var gamepadSupportAvailable = Modernizr.gamepads;
איך בודקים אילו שלטי משחק מחוברים
גם אם מחברים את שלט המשחק, הוא לא יופיע בשום צורה אלא אם המשתמש ילחץ קודם על אחד מהכפתורים שלו. זה נועד למנוע טביעות אצבע, אבל זה קצת מאתגר את חוויית המשתמש: אי אפשר לבקש מהמשתמש ללחוץ על הכפתור או לספק הוראות ספציפיות לשלט המשחק כי אין לכם מושג אם הוא חיבר את השלט.
אבל אחרי שתעברו את המשוכה הזו (סליחה…), יחכו לכם עוד משוכות.
סקרים
ההטמעה של ה-API ב-Chrome חושפת פונקציה – navigator.webkitGetGamepads() – שמאפשרת לקבל רשימה של כל בקרי המשחק שמחוברים כרגע למערכת, לצד המצב הנוכחי שלהם (כפתורים + ג'ויסטיקים). שלט המשחק הראשון שמחובר יוחזר כרשומה הראשונה במערך, וכן הלאה.
(הבקשה הזו להפעלת פונקציה החליפה לאחרונה מערך שהייתה לכם גישה ישירה אליו – navigator.webkitGamepads[]. נכון לתחילת אוגוסט 2012, עדיין צריך לגשת למערך הזה ב-Chrome 21, אבל הקריאה להפעלת הפונקציה פועלת ב-Chrome 22 ובגרסאות חדשות יותר. מעכשיו, הקריאה להפעלת פונקציה היא הדרך המומלצת להשתמש ב-API, והיא תתחיל לפעול בהדרגה בכל דפדפני Chrome המותקנים.)
החלק במפרט שהוטמע עד כה מחייב אתכם לבדוק באופן רציף את המצב של בקרי המשחק המחוברים (ולהשוות אותו למצב הקודם, אם יש צורך), במקום להפעיל אירועים כשהמצב משתנה. הסתמכנו על requestAnimationFrame() כדי להגדיר את הסקר בדרך היעילה ביותר, שלא תרוקן את הסוללה. בדודל שלנו, למרות שכבר יש לנו לולאת requestAnimationFrame() שתומכת באנימציות, יצרנו לולאה שנייה נפרדת לחלוטין – היה פשוט יותר לתכנת אותה והיא לא אמורה להשפיע על הביצועים בשום צורה.
זה הקוד מהבודק:
/**
* Starts a polling loop to check for gamepad state.
*/
startPolling: function() {
// Don't accidentally start a second loop, man.
if (!gamepadSupport.ticking) {
gamepadSupport.ticking = true;
gamepadSupport.tick();
}
},
/**
* Stops a polling loop by setting a flag which will prevent the next
* requestAnimationFrame() from being scheduled.
*/
stopPolling: function() {
gamepadSupport.ticking = false;
},
/**
* A function called with each requestAnimationFrame(). Polls the gamepad
* status and schedules another poll.
*/
tick: function() {
gamepadSupport.pollStatus();
gamepadSupport.scheduleNextTick();
},
scheduleNextTick: function() {
// Only schedule the next frame if we haven't decided to stop via
// stopPolling() before.
if (gamepadSupport.ticking) {
if (window.requestAnimationFrame) {
window.requestAnimationFrame(gamepadSupport.tick);
} else if (window.mozRequestAnimationFrame) {
window.mozRequestAnimationFrame(gamepadSupport.tick);
} else if (window.webkitRequestAnimationFrame) {
window.webkitRequestAnimationFrame(gamepadSupport.tick);
}
// Note lack of setTimeout since all the browsers that support
// Gamepad API are already supporting requestAnimationFrame().
}
},
/**
* Checks for the gamepad status. Monitors the necessary data and notices
* the differences from previous state (buttons for Chrome/Firefox,
* new connects/disconnects for Chrome). If differences are noticed, asks
* to update the display accordingly. Should run as close to 60 frames per
* second as possible.
*/
pollStatus: function() {
// (Code goes here.)
},
אם אתם רוצים להשתמש רק בשלט משחק אחד, אתם יכולים לקבל את הנתונים שלו בצורה פשוטה:
var gamepad = navigator.webkitGetGamepads && navigator.webkitGetGamepads()[0];
אם אתם רוצים להיות קצת יותר מתוחכמים, או לתמוך ביותר משחקן אחד בו-זמנית, תצטרכו להוסיף עוד כמה שורות קוד כדי להגיב לתרחישים מורכבים יותר (שני שלטים או יותר מחוברים, חלקם מתנתקים באמצע וכו'). אפשר לעיין בקוד המקור של הבודק שלנו, פונקציה pollGamepads(), כדי לראות גישה אחת לפתרון הבעיה.
אירועים
ב-Firefox משתמשים בשיטה חלופית וטובה יותר שמתוארת במפרט של Gamepad API. במקום לבקש מכם לבצע סקר, הוא חושף שני אירועים – MozGamepadConnected ו-MozGamepadDisconnected – שמופעלים בכל פעם שמחברים שלט משחק (או, ליתר דיוק, מחברים אותו ו "מכריזים" עליו על ידי לחיצה על אחד מהלחצנים שלו) או מנתקים אותו. אובייקט שלט המשחק שימשיך לשקף את המצב העתידי שלו מועבר כפרמטר .gamepad של אובייקט האירוע.
מקוד המקור של הבודק:
/**
* React to the gamepad being connected. Today, this will only be executed
* on Firefox.
*/
onGamepadConnect: function(event) {
// Add the new gamepad on the list of gamepads to look after.
gamepadSupport.gamepads.push(event.gamepad);
// Start the polling loop to monitor button changes.
gamepadSupport.startPolling();
// Ask the tester to update the screen to show more gamepads.
tester.updateGamepads(gamepadSupport.gamepads);
},
סיכום
בסופו של דבר, פונקציית האתחול בבודק, שתומכת בשתי הגישות, נראית כך:
/**
* Initialize support for Gamepad API.
*/
init: function() {
// As of writing, it seems impossible to detect Gamepad API support
// in Firefox, hence we need to hardcode it in the third clause.
// (The preceding two clauses are for Chrome.)
var gamepadSupportAvailable = !!navigator.webkitGetGamepads ||
!!navigator.webkitGamepads ||
(navigator.userAgent.indexOf('Firefox/') != -1);
if (!gamepadSupportAvailable) {
// It doesn't seem Gamepad API is available – show a message telling
// the visitor about it.
tester.showNotSupported();
} else {
// Firefox supports the connect/disconnect event, so we attach event
// handlers to those.
window.addEventListener('MozGamepadConnected',
gamepadSupport.onGamepadConnect, false);
window.addEventListener('MozGamepadDisconnected',
gamepadSupport.onGamepadDisconnect, false);
// Since Chrome only supports polling, we initiate polling loop straight
// away. For Firefox, we will only do it if we get a connect event.
if (!!navigator.webkitGamepads || !!navigator.webkitGetGamepads) {
gamepadSupport.startPolling();
}
}
},
מידע על שלט המשחק
כל שלט משחק שמחובר למערכת ייוצג על ידי אובייקט שנראה בערך כך:
id: "PLAYSTATION(R)3 Controller (STANDARD GAMEPAD Vendor: 054c Product: 0268)"
index: 1
timestamp: 18395424738498
buttons: Array[8]
0: 0
1: 0
2: 1
3: 0
4: 0
5: 0
6: 0.03291
7: 0
axes: Array[4]
0: -0.01176
1: 0.01961
2: -0.00392
3: -0.01176
מידע בסיסי
השדות הראשונים הם מטא-נתונים פשוטים:
-
id: תיאור טקסטואלי של שלט המשחק -
index: מספר שלם שמאפשר להבחין בין שלטי משחק שונים שמחוברים לאותו מחשב -
timestamp: חותמת הזמן של העדכון האחרון של מצב הכפתור/הצירים (כרגע נתמך רק ב-Chrome)
כפתורים וסטיקים
שלטי המשחק של היום הם לא בדיוק מה שסבא שלכם השתמש בו כדי להציל את הנסיכה בטירה הלא נכונה – בדרך כלל יש להם לפחות שישה עשר כפתורים נפרדים (חלקם דיסקרטיים, חלקם אנלוגיים), בנוסף לשני סטיקים אנלוגיים. Gamepad API יספק לכם מידע על כל הכפתורים והסטיקים האנלוגיים שמערכת ההפעלה מדווחת עליהם.
אחרי שמקבלים את המצב הנוכחי באובייקט שלט המשחק, אפשר לגשת לכפתורים דרך מערכים של .buttons[] ולסטיקים דרך מערכים של .axes[]. הנה סיכום ויזואלי של מה שהם מייצגים:
המפרט מבקש מהדפדפן למפות את 16 הלחצנים הראשונים וארבעת הצירים אל:
gamepad.BUTTONS = {
FACE_1: 0, // Face (main) buttons
FACE_2: 1,
FACE_3: 2,
FACE_4: 3,
LEFT_SHOULDER: 4, // Top shoulder buttons
RIGHT_SHOULDER: 5,
LEFT_SHOULDER_BOTTOM: 6, // Bottom shoulder buttons
RIGHT_SHOULDER_BOTTOM: 7,
SELECT: 8,
START: 9,
LEFT_ANALOGUE_STICK: 10, // Analogue sticks (if depressible)
RIGHT_ANALOGUE_STICK: 11,
PAD_TOP: 12, // Directional (discrete) pad
PAD_BOTTOM: 13,
PAD_LEFT: 14,
PAD_RIGHT: 15
};
gamepad.AXES = {
LEFT_ANALOGUE_HOR: 0,
LEFT_ANALOGUE_VERT: 1,
RIGHT_ANALOGUE_HOR: 2,
RIGHT_ANALOGUE_VERT: 3
};
הכפתורים והצירים הנוספים יתווספו לאלה שלמעלה. חשוב לזכור שלא בטוח שיהיו 16 כפתורים או 4 צירים, ויכול להיות שחלק מהם פשוט לא יהיו מוגדרים.
הערכים של הכפתורים יכולים להיות בין 0.0 (לא נלחץ) ל-1.0 (נלחץ עד הסוף). הצירים נעים בין -1.0 (לגמרי שמאלה או למעלה) דרך 0.0 (מרכז) ועד 1.0 (לגמרי ימינה או למטה).
אנלוגי או דיסקרטי?
לכאורה, כל כפתור יכול להיות אנלוגי – זה די נפוץ בכפתורי כתף, למשל. לכן, עדיף להגדיר סף במקום להשוות אותו ישירות ל-1.00 (מה אם כפתור אנלוגי קצת מלוכלך? יכול להיות שהערך אף פעם לא יגיע ל-1.00). בדודל שלנו אנחנו עושים את זה ככה:
gamepad.ANALOGUE_BUTTON_THRESHOLD = .5;
gamepad.buttonPressed_ = function(pad, buttonId) {
return pad.buttons[buttonId] &&
(pad.buttons[buttonId] > gamepad.ANALOGUE_BUTTON_THRESHOLD);
};
אפשר לעשות את אותו הדבר כדי להפוך סטיקים אנלוגיים לג'ויסטיקים דיגיטליים. תמיד אפשר להשתמש בכפתורי החיצים (D-pad), אבל יכול להיות שבשלט שלכם אין כפתורים כאלה. הנה הקוד שלנו לטיפול בזה:
gamepad.AXIS_THRESHOLD = .75;
gamepad.stickMoved_ = function(pad, axisId, negativeDirection) {
if (typeof pad.axes[axisId] == 'undefined') {
return false;
} else if (negativeDirection) {
return pad.axes[axisId] < -gamepad.AXIS_THRESHOLD;
} else {
return pad.axes[axisId] > gamepad.AXIS_THRESHOLD;
}
};
לחיצות על כפתורים ותנועות של הסטיקים
אירועים
במקרים מסוימים, כמו משחק סימולטור טיסה, הגיוני יותר לבדוק כל הזמן את מיקומי הג'ויסטיק או הלחיצות על הכפתורים ולהגיב להם… אבל מה לגבי דברים כמו דודל ריצת משוכות 2012? אולי אתם תוהים: למה צריך לבדוק אם יש כפתורים בכל פריים? למה אני לא יכול לקבל אירועים כמו שאני מקבל עבור מקלדת או עכבר למעלה/למטה?
החדשות הטובות הן שזה אפשרי. החדשות הרעות הן – בעתיד. הוא מופיע במפרט, אבל עדיין לא הוטמע באף דפדפן.
סקרים
בינתיים, הפתרון הוא להשוות בין המצב הנוכחי למצב הקודם, ולהפעיל פונקציות אם יש הבדל. לדוגמה:
if (buttonPressed(pad, 0) != buttonPressed(oldPad, 0)) {
buttonEvent(0, buttonPressed(pad, 0) ? 'down' : 'up');
}
for (var i in gamepadSupport.gamepads) {
var gamepad = gamepadSupport.gamepads[i];
// Don't do anything if the current timestamp is the same as previous
// one, which means that the state of the gamepad hasn't changed.
// This is only supported by Chrome right now, so the first check
// makes sure we're not doing anything if the timestamps are empty
// or undefined.
if (gamepadSupport.prevTimestamps[i] &&
(gamepad.timestamp == gamepadSupport.prevTimestamps[i])) {
continue;
}
gamepadSupport.prevTimestamps[i] = gamepad.timestamp;
gamepadSupport.updateDisplay(i);
}
הגישה שמתמקדת במקלדת בדודל "ריצת משוכות 2012"
בלי שלט משחק, שיטת הקלט המועדפת בדודל של היום היא המקלדת, אז החלטנו ששלט המשחק יחקה אותה באופן די דומה. זה הוביל לשלוש החלטות:
הדודל צריך רק שלושה כפתורים – שניים לריצה ואחד לקפיצה – אבל לשלט המשחק כנראה יש הרבה יותר. לכן מיפינו את כל 16 הכפתורים המוכרים ואת שני הג'ויסטיקים המוכרים לשלוש הפונקציות הלוגיות האלה, באופן שלדעתנו היה הכי הגיוני, כדי שאנשים יוכלו לרוץ על ידי: החלפה בין כפתורי A/B, החלפה בין כפתורי הכתף, לחיצה על כפתורי החיצים (D-pad) שמאלה/ימינה, או הזזה מהירה של אחד מהג'ויסטיקים שמאלה וימינה (חלק מהאפשרויות האלה יהיו, כמובן, יעילות יותר מהאחרות). לדוגמה:
newState[gamepad.STATES.LEFT] = gamepad.buttonPressed_(pad, gamepad.BUTTONS.PAD_LEFT) || gamepad.stickMoved_(pad, gamepad.AXES.LEFT_ANALOGUE_HOR, true) || gamepad.stickMoved_(pad, gamepad.AXES.RIGHT_ANALOGUE_HOR, true), newState[gamepad.STATES.PRIMARY_BUTTON] = gamepad.buttonPressed_(pad, gamepad.BUTTONS.FACE_1) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_SHOULDER) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_SHOULDER_BOTTOM) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.SELECT) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.START) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_ANALOGUE_STICK),התייחסנו לכל קלט אנלוגי כקלט נפרד, באמצעות פונקציות הסף שתוארו קודם.
הגענו למצב שבו הוספנו את הקלט של שלט המשחק לדודל, במקום להטמיע אותו – לולאת הסקר שלנו למעשה מסנתזת את אירועי keydown ו-keyup הנחוצים (עם keyCode מתאים) ושולחת אותם בחזרה ל-DOM:
// Create and dispatch a corresponding key event.
var event = document.createEvent('Event');
var eventName = down ? 'keydown' : 'keyup';
event.initEvent(eventName, true, true);
event.keyCode = gamepad.stateToKeyCodeMap_[state];
gamepad.containerElement_.dispatchEvent(event);
זה הכול!
טיפים וטריקים
- חשוב לזכור ששלט המשחק לא יופיע בדפדפן בכלל לפני שלוחצים על כפתור.
- אם אתם בודקים את שלט המשחק בכמה דפדפנים בו-זמנית, שימו לב שרק אחד מהם יכול לזהות את השלט. אם אתם לא מקבלים אירועים, ודאו שסגרתם את הדפים האחרים שבהם יכול להיות שנעשה שימוש ב-API. בנוסף, מניסיוננו, לפעמים דפדפן יכול "להחזיק" את שלט המשחק גם אם סוגרים את הכרטיסייה או יוצאים מהדפדפן עצמו. לפעמים הפעלה מחדש של המערכת היא הדרך היחידה לפתור בעיות.
- כמו תמיד, כדאי להשתמש ב-Chrome Canary ובמקבילים שלו בדפדפנים אחרים כדי לוודא שאתם מקבלים את התמיכה הטובה ביותר, ואז לפעול בהתאם אם אתם רואים שגרסאות ישנות יותר מתנהגות אחרת.
העתיד
אנחנו מקווים שהמידע הזה עזר לכם להבין קצת יותר את ה-API החדש – הוא עדיין קצת לא יציב, אבל כבר אפשר ליהנות ממנו.
בנוסף לחלקים החסרים ב-API (למשל אירועים) ולתמיכה רחבה יותר בדפדפנים, אנחנו מקווים לראות בסופו של דבר דברים כמו שליטה ברטט, גישה לג'ירוסקופים מובנים וכו'. כמו כן, תמיכה רחבה יותר בסוגים שונים של שלטי משחק – אם אתם נתקלים בשלט משחק שלא פועל כראוי או לא פועל בכלל, דווחו על באג ב-Chrome ו/או דווחו על באג ב-Firefox.
אבל לפני כן, כדאי לנסות את הדודל "ריצת משוכות 2012" ולראות כמה כיף לשחק בו עם שלט המשחק. אמרת שאתה יכול לעשות את זה בפחות מ-10.7 שניות? אני מוכן.