מקרה לדוגמה – שינוי אוטומטי של גודל משחקי HTML5

Derek Detweiler
Derek Detweiler

מבוא

בקיץ 2010 יצרנו את Saand Trap, משחק שהצטרף לתחרות על משחקי HTML5 לטלפונים ניידים. עם זאת, רוב הטלפונים הניידים הוצגו רק חלק מהמשחק, או הפכו את המשחק לקטן מדי - ולכן אי אפשר להפעיל אותו כלל. לכן החלטנו להתאים את המשחק באופן שוטף, לכל רזולוציה. אחרי קצת תכנות מחדש ושימוש ברעיונות שמתוארים במאמר הזה, פיתחנו משחק שקנה המידה של המשחק בכל דפדפן מודרני, בין אם הוא פועל במחשב שולחני או במכשיר נייד.

צילום מסך של מסך מלא של Thwack
צילום מסך של הפיכת תמונה להקטנה יותר בחלון הדפדפן

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

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

הכנת הדף

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

אם אזור המשחק מכיל שני חלקים, אזור משחקים ואזור לשמירת התוצאות, הוא עשוי להיראות כך:

<div id="gameArea">
  <canvas id="gameCanvas"></canvas>
  <div id="statsPanel"></div>
</div>

אחרי שיוצרים מבנה מסמך בסיסי, אפשר לתת לרכיבים האלה כמה מאפייני CSS כדי להכין אותם לשינוי הגודל. הרבה ממאפייני ה-CSS של "gameArea" עוברים שינויים ישירות ב-JavaScript, אבל כדי שהם יפעלו, צריך להגדיר כמה מאפייני CSS אחרים, שמתחילים בבלוק ההורה של gameArea div:

#gameArea {
  position: absolute;
  left:     50%;
  top:      50%;
}

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

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

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

מידות של רכיבי צאצא של gameArea בפיקסלים
איור 1: מידות רכיבי הצאצא של gameArea בפיקסלים

אם מתרגמים את הערכים האלה לאחוזים, הרוחב של הקנבס הוא 100% ברוחב ו-100% גובה (של gameArea, ולא של החלון). חלוקה של 24 ב-300 מחזירה את הגובה של חלונית הנתונים הסטטיסטיים כ-8%. מאחר שהגובה של חלונית הנתונים הסטטיסטיים יכסה את החלק התחתון של אזור המשחק, הרוחב שלה יהיה גם 100%, כפי שמתואר באיור 2.

מאפיינים של רכיבי צאצא של gameArea באחוזים
איור 2: מידות של רכיבי צאצא של GameArea באחוזים

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

#gameCanvas {
  width: 100%;
  height: 100%;
}
#statsPanel {
  position: absolute;
  width: 100%;
  height: 8%;
  bottom: 0;
  opacity: 0.8;
}

שינוי גודל המשחק

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

var gameArea = document.getElementById('gameArea');

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

var widthToHeight = 4 / 3;

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

var newWidth = window.innerWidth;
var newHeight = window.innerHeight;

בדיוק כפי שקבעת את יחס הרוחב-גובה הרצוי, עכשיו תוכל לקבוע את יחס הגובה-רוחב הנוכחי של החלון:

var newWidthToHeight = newWidth / newHeight;

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

התאמת הרכיב gameArea לחלון תוך שמירה על יחס הגובה-רוחב
איור 3: התאמת הרכיב gameArea לחלון תוך שמירה על יחס הגובה-רוחב

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

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

if (newWidthToHeight > widthToHeight) {
  // window width is too wide relative to desired game width
  newWidth = newHeight * widthToHeight;
  gameArea.style.height = newHeight + 'px';
  gameArea.style.width = newWidth + 'px';
} else { // window height is too high relative to desired game height
  newHeight = newWidth / widthToHeight;
  gameArea.style.width = newWidth + 'px';
  gameArea.style.height = newHeight + 'px';
}

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

gameArea.style.marginTop = (-newHeight / 2) + 'px';
gameArea.style.marginLeft = (-newWidth / 2) + 'px';

מומלץ גם להתאים אוטומטית את גודל הגופן. אם יש לכם את כל רכיבי הצאצא באמצעות em, אתם יכולים פשוט להגדיר את מאפיין fontSize CSS של בלוק GameArea לערך שנקבע לפי הגודל שלו.

gameArea.style.fontSize = (newWidth / 400) + 'em';

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

var gameCanvas = document.getElementById('gameCanvas');
gameCanvas.width = newWidth;
gameCanvas.height = newHeight;

לכן פונקציית שינוי הגודל שהושלמה עשויה להיראות כך:

function resizeGame() {
    var gameArea = document.getElementById('gameArea');
    var widthToHeight = 4 / 3;
    var newWidth = window.innerWidth;
    var newHeight = window.innerHeight;
    var newWidthToHeight = newWidth / newHeight;
    
    if (newWidthToHeight > widthToHeight) {
        newWidth = newHeight * widthToHeight;
        gameArea.style.height = newHeight + 'px';
        gameArea.style.width = newWidth + 'px';
    } else {
        newHeight = newWidth / widthToHeight;
        gameArea.style.width = newWidth + 'px';
        gameArea.style.height = newHeight + 'px';
    }
    
    gameArea.style.marginTop = (-newHeight / 2) + 'px';
    gameArea.style.marginLeft = (-newWidth / 2) + 'px';
    
    var gameCanvas = document.getElementById('gameCanvas');
    gameCanvas.width = newWidth;
    gameCanvas.height = newHeight;
}

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

window.addEventListener('resize', resizeGame, false);
window.addEventListener('orientationchange', resizeGame, false);

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

סיכום

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