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

Derek Detweiler
Derek Detweiler

מבוא

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

צילום מסך של thwack במסך מלא
צילום מסך של 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 אחרים, החל מבלוק ה-div של האב gameArea:

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

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

מכיוון שגודל אזור המשחק משתנה באופן אוטומטי בהתאם למאפייני החלון, לא רוצים שהמאפיינים של רכיבי הצאצא של הבלוק gameArea div יהיו בפיסקלים, אלא באחוזים. ערכי פיקסלים לא מאפשרים לאלמנטים הפנימיים להתאים את עצמם ל-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 כבר ממוקם בפינה הימנית העליונה של ה-div של gameArea במרכז החלון, כך שכך ממקמים את אזור המשחק בחלון:

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

כדאי גם לשנות את גודל הגופן באופן אוטומטי. אם כל רכיבי הצאצאים משתמשים ב-em, אפשר פשוט להגדיר את מאפיין ה-CSS fontSize של הבלוק div של 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;
}

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

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

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

סיכום

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