איך התכונה 'כבישים איטיים' מסקרנת גיימרים ומפתחים וגם מדגישה את היכולות המפתיעות של תלת-ממד בדפדפן

משחק נהיגה קל שבו תוכלו לגלות את הפוטנציאל של WebGL באמצעות הנוף האינסופי שנוצר באופן פרוגרמטי.

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

פיתוח תלת-ממד בדפדפן

אחרי השקת התכונה 'דרכים איטיות', ראיתי תגובה חוזרת ונשנית במשוב: "לא ידעתי שאפשר לעשות את זה בדפדפן". אם אתם חושבים כך, אתם בהחלט לא מיעוט. לפי הסקר 2022 State of JS, כ-80% מהמפתחים עדיין לא ניסו את WebGL. לדעתי, זה חבל שאנחנו עלולים להחמיץ כל כך הרבה פוטנציאל, במיוחד כשמדובר בגיימינג מבוסס-דפדפן. אני מקווה ש-Slow Roads יעזור להביא את WebGL לקדמת הבמה, ואולי גם לצמצם את מספר המפתחים שמפחדים מהביטוי 'מנוע משחקים בעל ביצועים גבוהים של JavaScript'.

WebGL אולי נראה לרבים מסתורי ומורכב, אבל בשנים האחרונות סביבות הפיתוח שלו התפתחו מאוד והפכו לספריות וכלים נוחים ומקצועיים. עכשיו קל יותר מתמיד למפתחי חזית להוסיף חוויית משתמש תלת-ממדית לעבודתם, גם בלי ניסיון קודם בגרפיקה ממוחשבת. Three.js, ספריית WebGL המובילה, משמשת כבסיס להרחבות רבות, כולל react-three-fiber שמאפשרת להוסיף רכיבים תלת-ממדיים למסגרת React. יש עכשיו גם כלי עריכה מקיפים למשחקים מבוססי-אינטרנט, כמו Babylon.js או PlayCanvas, שמציעים ממשק מוכר וכלי פיתוח משולבים.

עם זאת, למרות התועלת הרבה של הספריות האלה, בסופו של דבר פרויקטים שאפתניים מוגבלים במגבלות טכניות. ספקנים לגבי הרעיון של משחקים מבוססי-דפדפן עשויים להדגיש ש-JavaScript הוא מסוג single-threaded ומוגבל במשאבים. אבל כשעוברים את המגבלות האלה, מגלים את הערך הנסתר: אין פלטפורמה אחרת שמציעה את אותה נגישות מיידית ותאימות רחבה שמאפשרות הדפדפנים. משתמשים בכל מערכת עם דפדפן יכולים להתחיל לשחק בלחיצה אחת, בלי צורך להתקין אפליקציות או להיכנס לשירותים. בלי לדבר על הנוחות האלגנטית של מסגרות חזית חזקות שזמינות למפתחים ליצירת ממשק משתמש, או לטיפול ברשתות במצבים של משחקים מרובי משתתפים. לדעתי, הערכים האלה הם שהופכים את הדפדפן לפלטפורמה מצוינת גם לשחקנים וגם למפתחים. כמו שראינו ב-Slow Roads, לעתים קרובות אפשר לצמצם את המגבלות הטכניות לבעיה בעיצוב.

שיפור הביצועים בכבישים איטיים

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

בהמשך מפורט פירוט של הרכיבים העיקריים ששומרים על יעילות של 'דרכים איטיות'.

עיצוב מנוע הסביבה בהתאם למשחק

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

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

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

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

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

להיות בררנים לגבי חוקי הפיזיקה

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

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

ניהול הזיכרון שבשימוש

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

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

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

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

קיצור זמן הטעינה באמצעות נכסים שנוצרו באופן פרוגרמטי

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

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

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

רוב הגיאומטריה ב'דרכים איטיות' נוצרת באופן פרוגרמטי ופשוטה, עם שילובים מותאמים אישית של מספר טקסטורות כדי להוסיף פרטים. החיסרון הוא שיכול להיות שהטקסטורות האלה יהיו נכסים כבדים, אבל יש כאן הזדמנויות נוספות לחיסכון, עם שיטות כמו טקסטורה סטוכסטית שמאפשרות להשיג פרטים נוספים מטקסטורות מקור קטנות. ברמה קיצונית יותר, אפשר גם ליצור טקסטורות לגמרי בצד הלקוח באמצעות כלים כמו texgen.js. אותו הדבר נכון גם לגבי אודיו, כאשר Web Audio API מאפשר יצירת אודיו באמצעות צמתים של אודיו.

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

תרשים היסטוגרמה של זמני הטעינה, שבו מוצג שיא משמעותי בשלושת השניות הראשונות, שמהווה יותר מ-60% מהמשתמשים, ולאחר מכן ירידה מהירה. בתרשים ההיסטוגרמה מוצג שזמן הטעינה של יותר מ-97% מהמשתמשים הוא פחות מ-10 שניות.

גישה גמישה לאופטימיזציה מאוחרת

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

מעקב אחר חוויית המשתמש

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

עם זאת, יצירת פרופילים במחשב שלכם יכולה לכסות רק חלק מהנתונים, ולכן כדאי לסגור את מעגל המשוב עם המשתמשים בדרך כלשהי. ב-Slow Roads אני מפעיל ניתוח נתונים פשוט שמדווח על הביצועים יחד עם גורמים הקשורים להקשר, כמו רזולוציית המסך. נתוני הניתוח האלה נשלחים לקצה עורפי בסיסי של Node באמצעות socket.io, יחד עם כל משוב בכתב שהמשתמש שולח באמצעות הטופס במשחק. בשלבים המוקדמים, ניתוח הנתונים הזה חשף הרבה בעיות חשובות שאפשר היה לצמצם באמצעות שינויים פשוטים בממשק המשתמש, כמו הדגשת תפריט ההגדרות כשזוהתה קצב פריימים נמוך באופן עקבי, או אזהרה על כך שייתכן שהמשתמש יצטרך להפעיל האצת חומרה אם הביצועים גרועים במיוחד.

הדרכים האיטיות שבדרך

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

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

למרבה המזל, עדיין יש הרבה הזדמנויות לחסוך בעלויות הביצועים. בנוסף להוספת עוד טריקים לעיבוד כדי לצמצם את הביקוש ל-GPU, אני מקווה להתנסות ב-web workers כדי לבצע במקביל את יצירת הסביבה בעתיד הקרוב, ובסופו של דבר אוכל לראות צורך בשילוב של WASM או WebGPU בקוד הבסיסי. כל מקום פנוי שאצליח לפנות יאפשר לי ליצור סביבות עשירות ומגוונות יותר, וזה יהיה היעד העיקרי שלי במהלך הפרויקט.

Slow Roads הוא פרויקט חובבים ששימש אותי כדרך נהדרת להראות עד כמה משחקי דפדפן יכולים להיות מורכבים, פופולריים ומניבים ביצועים טובים. אם הצלחתי לעורר את העניין שלכם ב-WebGL, חשוב לדעת ש-Slow Roads היא דוגמה טכנולוגית שטחית למדי ליכולות המלאות שלו. מומלץ מאוד לקוראים לעיין בתצוגה של Three.js, ומי שמתעניין בפיתוח משחקים לאינטרנט במיוחד מוזמן להצטרף לקהילה בכתובת webgamedev.com.