מקרה לדוגמה – בניית 'מציירים Google' של Stanisław Lem

Marcin Wichary
Marcin Wichary

שלום, עולם (מוזר)

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

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

כל דודל אינטראקטיבי שכתבתי (Pac-Man,‏ Jules Verne,‏ World’s Fair) – ורבים שסייעתי ליצור – היה בחלקים שווים עתידני ואנאכרוניסטי: הזדמנויות נהדרות לשימוש בפיצ'רים מתקדמים באינטרנט, וגם פרגמטיזם קשוח של תאימות לדפדפנים שונים.

אנחנו לומדים הרבה מכל 'גוגל דוד' אינטראקטיבי, והמשחקון המיני של סטניסלב לם מהזמן האחרון לא היה יוצא דופן. 17,000 שורות הקוד ב-JavaScript של המשחקון הזה אפשרו לנו לנסות הרבה דברים בפעם הראשונה בהיסטוריה של 'גוגל דוד'. היום אני רוצה לשתף את הקוד הזה איתכם – אולי תמצאו בו משהו מעניין או תצביעו על הטעויות שלי – ואדבר קצת עליו.

הצגת הקוד של ה-doodle של סטניסלב Lem »

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

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

גרפיקה באמצעות DOM ו-Canvas

Canvas הוא כלי חזק שנועד בדיוק למטרות האלה. עם זאת, חלק מהדפדפנים הישנים שחשובים לנו לא תומכים ב-WebGL – ואפילו שאני משתף משרד עם האדם שהרכיב את excanvas, שהיא ספרייה מצוינת בכל שאר ההיבטים, החלטתי לבחור דרך אחרת.

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

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

לצערנו, המעבר ל-Canvas לא פשוט כמו שיקוף רקעים של CSS באמצעות drawImage(): אתם מפסידים כמה דברים שמגיעים בחינם כשמרכיבים דברים באמצעות DOM – והחשוב ביותר הוא שכבות עם אינדקסים מסוג z ואירועי עכבר.

כבר הוצאתי את z-index מהתמונה באמצעות מושג שנקרא 'מישורים'. ב-doodle הוגדרו כמה מישורים – מהשמים הרחוקים מאחור ועד לסמן העכבר שמול הכול – וכל רכיב ב-doodle היה צריך להחליט לאיזה מישור הוא שייך (אפשר היה לבצע תיקונים קטנים של פלוס/מינוס בתוך מישור באמצעות planeCorrection).

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

גם באירועי העכבר, העברתי את הקוד ל-abstract… פחות או יותר. גם ב-DOM וגם ב-canvas, השתמשתי ברכיבי DOM נוספים שצפים ושקופים לחלוטין עם אינדקס z גבוה, והפונקציה שלהם היא רק להגיב לתנועות עכבר מעל/מתחת, לקליקים וללחיצות.

אחד הדברים שרצינו לנסות ב-doodle הזה היה לפרוץ את הקיר הרביעי. המנוע שלמעלה אפשר לנו לשלב רכיבים מבוססי-Canvas עם רכיבים מבוססי-DOM. לדוגמה, הפיצוצים בסוף הסרט נמצאים גם ב-canvas לאובייקטים ביקום וגם ב-DOM לשאר דפי הבית של Google. הציפור, שבדרך כלל טסה מסביב ומוחקת על ידי המסכה המשוננת שלנו כמו כל שחקן אחר, מחליטה להימנע מבעיות במהלך שלב הצילום, ומתיישבת על הלחצן 'אני רוצה להרגיש מזל'. הדרך לעשות זאת היא שהציפור תצא מהקנבס ותהפוך לרכיב DOM (ולהפך בהמשך), וחשבתי שהיא תהיה שקופת לגמרי למבקרים שלנו.

קצב הפריימים

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

התחלתי להשתמש ב-requestAnimationFrame, וחזרתי ל-setTimeout הישן אם הראשונה לא הייתה זמינה. requestAnimationFrame חוסך ביעילות את ה-CPU במצבים מסוימים – למרות שאנחנו עושים חלק מהעבודה הזו בעצמנו, כפי שמוסבר בהמשך – אבל הוא גם מאפשר לנו לקבל קצב פריימים גבוה יותר מ-setTimeout.

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

איזה סוג של החלטות?

  • אם קצב הפריימים גבוה מ-60fps, אנחנו מגבילים אותו. נכון לעכשיו, בחלק מהגרסאות של Firefox אין למאפיין requestAnimationFrame מגבלה על קצב הפריימים, ואין טעם לבזבז את מעבד ה-CPU. חשוב לדעת: אנחנו מגבילים את קצב הפריימים ל-65fps בגלל שגיאות בעיגול שגורמות לקצב פריימים גבוה מעט מ-60fps בדפדפנים אחרים. אנחנו לא רוצים להתחיל לבצע הגבלת קצב פריימים בטעות.

  • אם קצב הפריימים נמוך מ-10fps, אנחנו פשוט מאטים את המנוע במקום להשמיט פריימים. זוהי הצעה של הפסד-הפסד, אבל הרגשתי שדילוג מוגזם על פריימים יהיה מביך יותר מאשר פשוט משחק איטי יותר (אבל עדיין עקבי). יש לכך גם תופעת לוואי נעימה – אם המערכת תהיה איטית באופן זמני, המשתמש לא יחווה קפיצה מוזרה קדימה בזמן שהמנוע ינסה להשלים את הפער. (במשחק Pac-Man עשיתי זאת בצורה שונה במקצת, אבל קצב הפריימים המינימלי הוא גישה טובה יותר).

  • לבסוף, אפשר לחשוב על פשטות הגרפיקה כשקצב הפריימים יורד לרמה מסוכנת. אנחנו לא עושים את זה ב-doodle של Lem, מלבד סמן העכבר (מידע נוסף מופיע בהמשך), אבל באופן היפותטי, יכול להיות שנוותר על חלק מהאנימציות הלא רלוונטיות כדי שה-doodle ייראה חלק גם במחשבים איטיים יותר.

יש לנו גם את המושגים 'סימון פיזי' ו'סימון לוגי'. הראשון מגיע מ-requestAnimationFrame/setTimeout. היחס במהלך משחק רגיל הוא 1:1, אבל כדי לדלג מהר, אנחנו פשוט מוסיפים עוד טיקים לוגיים לכל טיקים פיזי (עד 1:5). כך אנחנו יכולים לבצע את כל החישובים הנדרשים לכל אירוע לוגי, אבל להגדיר רק את האירוע האחרון כזה שצריך לעדכן את הדברים במסך.

השוואה לשוק

אפשר להניח (ובתחילת הדרך הניחו) ש-Canvas יהיה מהיר יותר מ-DOM בכל פעם שהוא זמין. זה לא תמיד נכון. במהלך הבדיקה, גילינו ש-Opera בגרסאות 10.0 עד 10.1 ב-Mac ו-Firefox ב-Linux מהירים יותר בהעברת רכיבי DOM.

בעולם מושלם, הדודג' יבצע בדיקות ביצועים בשקט של טכניקות גרפיות שונות – רכיבי DOM שמועברים באמצעות style.left ו-style.top, ציור על קנבס ואולי אפילו רכיבי DOM שמועברים באמצעות טרנספורמציות של CSS3.

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

בסופו של דבר, פיתוח אינטרנט מגיע לפעמים למצב שבו צריך לעשות מה שצריך. הסתכלתי מאחורי כדי לוודא שאף אחד לא מסתכל, ואז פשוט הוצאתי את Opera 10 ו-Firefox מתוך הבד. בחיים הבאים אשוב בתור תג <marquee>.

חיסכון בשימוש במעבד

אתם מכירים את החבר או החברה שמגיעים אליכם הביתה, צופים בפרק הסיום של העונה של "שובר שורות", מפריעים לכם ליהנות ממנו ומוחקים אותו מה-DVR? אתם לא רוצים להיות הבחור הזה, נכון?

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

מתי?

  • אחרי 18 שניות בדף הבית (במשחקי ארקייד זה נקרא מצב משיכה)
  • אחרי 180 שניות אם הכרטיסייה נמצאת במוקד
  • אחרי 30 שניות אם הכרטיסייה לא נמצאת במוקד (למשל, המשתמש עבר לחלון אחר, אבל אולי הוא עדיין צופה בציור הדמות המצוירת בכרטיסייה לא פעילה)
  • מיד אם הכרטיסייה הופכת לא נראית (למשל, המשתמש עבר לכרטיסייה אחרת באותו חלון – אין טעם לבזבז מחזורים אם אנחנו לא גלויים)

איך אנחנו יודעים שהמיקוד נמצא כרגע בכרטיסייה? אנחנו מצורפים ל-window.focus ול-window.blur. איך אנחנו יודעים שהכרטיסייה גלויה? אנחנו משתמשים ב-Page Visibility API החדש ומגיבים לאירוע המתאים.

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

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

מעברים, טרנספורמציות ואירועים

אחד מהיתרונות של HTML תמיד היה העובדה שאפשר לשפר אותו בעצמכם: אם משהו לא טוב מספיק בפורטפוליו הרגיל של HTML ו-CSS, אפשר להשתמש ב-JavaScript כדי להרחיב אותו. לצערנו, לרוב צריך להתחיל מחדש. מעברים ב-CSS3 הם נהדרים, אבל אי אפשר להוסיף סוג מעבר חדש או להשתמש במעברים כדי לעשות משהו אחר מלבד עיצוב רכיבים. דוגמה נוספת: טרנספורמציות של CSS3 נהדרות ל-DOM, אבל כשעוברים ל-Canvas, פתאום אתם לבד.

הבעיות האלה ועוד כמה סיבות נוספות הן הסיבה לכך של-Lem doodle יש מנוע טרנספורמציה ומעבר משלו. כן, אני יודע, שנות ה-2000 התקשרו וכו' – היכולות שכתבתי לא חזקות כמו CSS3, אבל כל מה שהמנוע עושה הוא עושה באופן עקבי ונותן לנו הרבה יותר שליטה.

התחלתי עם מערכת פעולה (אירוע) פשוטה – ציר זמן שמפעיל אירועים בעתיד בלי להשתמש ב-setTimeout, כי בכל שלב נתון, הזמן ב-Doodle יכול להיות מנותק מהזמן הפיזי כשהוא מאיץ (העברה מהירה קדימה), מאט (קצב פריימים נמוך או מצב שינה כדי לחסוך ב-CPU) או נעצר לגמרי (המתנה לסיום טעינת התמונות).

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

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

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

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

עבודה עם תמונות ו-sprites

מנוע לא רק מפעיל את הדודג' – הוא גם מאפשר לעבוד עליו. למעלה שיתפתי כמה פרמטרים לניפוי באגים: שאר הפרמטרים מופיעים בקובץ engine.readDebugParams.

יצירת גרפיקה היא שיטה ידועה שגם אנחנו משתמשים בה ליצירת הדודלים. כך אנחנו יכולים לחסוך בייטים ולקצר את זמני הטעינה, וגם לבצע טעינה מראש בקלות רבה יותר. עם זאת, היא גם מקשה על הפיתוח – כל שינוי בתמונות ידרוש יצירת גרפיקה מחדש (התהליך אוטומטי במידה רבה, אבל עדיין מסורבל). לכן, המנוע תומך בהרצה על קובצי אימג' גולמיים לפיתוח, וגם ב-sprites בסביבת הייצור באמצעות engine.useSprites – שניהם כלולים בקוד המקור.

הדודל של פק-מן
סמלי ה-sprite ששימשו בדודל של פק-מן.

אנחנו גם תומכים בטעינת תמונות מראש תוך כדי עבודה, ועוצרים את הדודל אם התמונות לא נטענו בזמן – עם סרגל התקדמות מזויף! (הטעות היא כי לצערנו, גם HTML5 לא יכול לומר לנו כמה מקובץ התמונה כבר נטען).

צילום מסך של גרפיקה של טעינה עם סרגל התקדמות שעבר מניפולציה.
צילום מסך של גרפיקה של טעינה עם סרגל התקדמות מזויף.

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

איפה HTML5 נכנס לתמונה? לא הרבה מזה מופיע למעלה, אבל הכלי שכתבתי ליצירת פריימים או חיתוך היה טכנולוגיית אינטרנט חדשה לגמרי: קנבס, blobs,‏ a[download]. אחד מהדברים המעניינים ב-HTML הוא שהוא כולל בהדרגה דברים שבעבר היה צריך לבצע מחוץ לדפדפן. החלק היחיד שאנחנו צריכים לעשות הוא לבצע אופטימיזציה של קובצי PNG.

שמירת המצב בין משחקים

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

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

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

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

מה אנחנו עושים עם המידע הזה?

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

יש כמה פרמטרים של ניפוי באגים ששולטים בכך:

  • ?doodle-debug&doodle-first-run – כאילו זו הפעלה ראשונה
  • ?doodle-debug&doodle-second-run – כאילו מדובר בהרצה שנייה
  • ?doodle-debug&doodle-old-run – כאילו מדובר בריצה ישנה

מכשירי מגע

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

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

רגיל לא פנוי/ה קישור נלחץ
עבודה בתהליך
סמן רגיל של עבודה בתהליך
סמן עסוק של עבודה בתהליך
סמן שניתן ללחוץ עליו כדי לציין שהעבודה בטיפול
סמן של לחיצה על פריט בעבודה
גמר
סוף של מצב נורמליv
מצב עסוק סופי
סמן סופי שניתן ללחוץ עליו
הסמן האחרון של הקליק
סמני עכבר במהלך הפיתוח וסמנים מקבילים בגרסה הסופית.

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

העובדה שהיו לי אלמנטים שקופים נפרדים של DOM שניתן ללחוץ עליהם עזרה לי מאוד, כי יכולתי לשנות את הגודל שלהם בנפרד מהרכיבים החזותיים. הוספתי עוד 15 פיקסלים של ריפוד למכשירי מגע, והשתמשתי בו בכל פעם שיצרתי רכיבים שניתן ללחוץ עליהם. (הוספת גם מרווח של 5 פיקסלים לסביבות של עכבר, רק כדי לשמח את מר Fitts).

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

אנחנו גם משתמשים בנכסי סגנון מודרניים יותר כדי להסיר כמה תכונות מגע שמתבצעות כברירת מחדל בדפדפני WebKit (הקשה על הדגשה, הקשה על הסבר קצר).

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

התאמה אישית של סמן העכבר

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

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

אם לא זה, אז מה? אז למה לא להפוך את סמן העכבר לעוד שחקן ב-doodle? אפשר לעשות זאת, אבל יש כמה דברים שצריך לזכור:

  • צריך להיות אפשרות להסיר את סמן העכבר המקורי
  • צריך להיות לכם כושר מצוין לשמור על סנכרון בין סמן העכבר לבין הסמן 'האמיתי'.

האפשרות הראשונה היא מורכבת. ב-CSS3 יש תמיכה ב-cursor: none, אבל גם הוא לא נתמך בדפדפנים מסוימים. נאלצנו להשתמש בקצת גמישות: שימוש בקובץ .cur ריק כחלופה, ציון התנהגות ספציפית לדפדפנים מסוימים ואפילו קידוד קבוע של דפדפנים אחרים כדי להוציא אותם מהחוויה.

הבעיה השנייה נראית טריוויאלית יחסית, אבל מאחר שסמן העכבר הוא רק חלק נוסף ביקום של הדודג', הוא יירשה גם את כל הבעיות שלו. הגדולה ביותר? אם קצב הפריימים של ה-doodle נמוך, גם קצב הפריימים של סמן העכבר יהיה נמוך – ויש לכך השלכות חמורות, כי סמן העכבר הוא המשך טבעי של היד, והוא צריך להגיב במהירות בכל מצב. (אנשים שהשתמשו ב-Commodore Amiga בעבר מתחילים להנהן בהסכמה).

פתרון מורכב למדי לבעיה הזו הוא לבטל את הקישור בין סמן העכבר לבין לולאת העדכון הרגילה. זה בדיוק מה שעשינו – ביקום חלופי שבו אין צורך לישון. פתרון פשוט יותר לבעיה הזו? פשוט חוזרים לסמן העכבר המקורי אם קצב הפריימים המצטבר יורד מתחת ל-20FPS. (כאן נכנסת לתמונה התכונה 'קצב פריימים מתגלגל'. אם היינו מגיבים לשיעור הפריימים הנוכחי, ואם הוא היה משתנה סביב 20fps, המשתמש היה רואה את סמן העכבר המותאם אישית מסתתר ומוצג כל הזמן). זה מביא אותנו ל:

טווח קצב הפריימים התנהגות
יותר מ-10fps להאט את המשחק כדי שלא ייפסקו עוד פריימים.
10-20fps שימוש במצביע עכבר מקורי במקום במצביע מותאם אישית.
20-60fps פעילות רגילה.
‎>60fps צריך להגביל את קצב הפריימים כך שלא יעלה על הערך הזה.
סיכום של ההתנהגות שתלויה בקצב הפריימים.

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

סיכום

זה לא מנוע מושלם, אבל הוא לא מנסה להיות כזה. הוא פותח במקביל ל-doodle של Lem, והוא ספציפי מאוד אליו. זה בסדר. "אופטימיזציה מוקדמת היא שורש כל הרוע", כפי שאמר דון נוטה (Don Knuth) במשפט המפורסם שלו, ואני לא חושב שזה הגיוני לכתוב מנוע בנפרד קודם, ולהחיל אותו רק מאוחר יותר – התרגול מעדכן את התיאוריה באותה מידה שהתיאוריה מעדכנת את התרגול. במקרה שלי, הקוד נזרק, חלקים רבים נכתבו מחדש שוב ושוב, וחלקים נפוצים רבים נמצאו רק לאחר מעשה, ולא לפניו. אבל בסופו של דבר, מה שיש לנו כאן איפשר לנו לעשות את מה שרצינו – לחגוג את הקריירה של סטניסלב לם ואת האיורים של דניאל מרוז בדרך הטובה ביותר שיכולנו לחשוב עליה.

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

עשיתי את זה בעצמי – התמונה הבאה הייתה פעילה בימים האחרונים, והיא סיפרה את הימים שחלפו עד לשעות המוקדמות של 23 בנובמבר 2011 ברוסיה, שעון ראשון שבו הוצג הדודל של Lem. אולי זה נשמע קצת מגוחך, אבל כמו שדודלים, לפעמים לדברים שנראים לא משמעותיים יש משמעות עמוקה יותר – המונה הזה היה באמת 'בדיקת לחץ' טובה למנוע.

צילום מסך של שעון הספירה לאחור ביקום של Lem doodle.
צילום מסך של שעון הספירה לאחור ביקום של Lem doodle.

זוהי אחת מהדרכים להסתכל על החיים של 'גוגל דודל' – חודשים של עבודה, שבועות של בדיקות, 48 שעות של פיתוח, והכול בשביל משהו שאנשים משחקים בו במשך חמש דקות. כל אחת מאלפי שורות ה-JavaScript האלה מקווה ש-5 הדקות האלה יהיו שווה את הזמן. שיהיה לך כיף.