ב.8 — שעון, קצב פריימים ורענון המסך
בפרק הקודם, כשהזזנו את המעגל בעזרת מקשי החצים, ראינו שהלולאה הראשית שלנו רצה מהר ככל שהמחשב מאפשר. במשחק אמיתי זו בעיה: אם בכל סיבוב של הלולאה כדור מתקדם כמה פיקסלים, במחשב מהיר הכדור יחצה את המסך בשבריר שנייה ובמחשב איטי יזחל, ואותו משחק ירגיש שונה לגמרי בכל מחשב. נוסף על כך, לולאה שרצה ללא הפסקה מעסיקה את המעבד במלוא הכוח גם כשאין מה לעשות.
תנועה על המסך נוצרת מרצף תמונות. מספר התמונות המוצגות בשנייה נקרא FPS — Frames Per Second. אם הלולאה פועלת מהר ככל האפשר, קצב העבודה עשוי להשתנות בין מחשבים וגם לאורך אותה הרצה. בעזרת שעון אפשר להגביל את הקצב הרצוי: השעון מודד כמה זמן חלף מאז הפריים הקודם, ואם הסיבוב הסתיים מוקדם מדי הוא ממתין מעט לפני שהסיבוב הבא מתחיל. כך הלולאה רצה בקצב קבוע ואחיד.
בפרק זה נוסיף את השעון ללולאה, ונבהיר גם את ההבדל בין ציור על המשטח לבין רענון החלון — שני שלבים שקל לבלבל ביניהם.
המצגת: מבוא ל־Pygame (עותק מקומי) · תיעוד התצוגה · תיעוד השעון
יצירת השעון והגבלת הקצב
השעון מאפשר להגביל את מספר הסיבובים של הלולאה בשנייה. כמו יצירת החלון, גם יצירת השעון היא הכנה חד־פעמית, ולכן יוצרים אותו פעם אחת, לפני הלולאה. המשתנה FPS שומר את הקצב הרצוי כדי שנוכל לשנותו במקום אחד:
clock = pygame.time.Clock()
FPS = 60
השימוש בשעון, לעומת זאת, חוזר בכל פריים. בסוף כל סיבוב, אחרי הצגת התמונה, מוסיפים:
pygame.display.update()
clock.tick(FPS)
הפעולה tick(60) ממתינה לפי הצורך כדי להגביל את הלולאה לכ־60 סיבובים בשנייה. היא אינה מבטיחה שהמחשב יצליח להגיע לקצב הזה: אם הכנת פריים נמשכת זמן רב, הקצב בפועל יהיה נמוך יותר. כמו כן, השעון אינו מצייר ואינו מרענן בעצמו את החלון.
ציור ורענון הן פעולות נפרדות
כזכור מפרק ב.2, יש שתי פעולות שמרעננות את החלון: update() ו־flip(). כשמציירים בתוך פריים אחד רקע, כמה צורות ותמונה, המשתמש אינו רואה כל ציור בנפרד. פעולות הציור משנות את תוכן המשטח בזיכרון בלבד; כדי להציג את התוצאה בחלון, קוראים לפעולת רענון לאחר שסיימנו לצייר את הפריים. כך המשתמש רואה את התמונה המורכבת כולה בבת אחת, ולא ציור חלקי באמצע הבנייה.
| פעולה | שימוש |
|---|---|
pygame.display.update() |
עדכון כל משטח התצוגה בחלון הרגיל שבו נשתמש |
pygame.display.update(area) |
עדכון אזור מסוים; את ייצוג האזור באמצעות Rect נלמד בהמשך |
pygame.display.flip() |
עדכון התצוגה המלאה |
ההבדל היחיד בין השתיים הוא ש־update() יכולה לקבל אזור ולרענן רק אותו, ואילו flip() תמיד מרעננת את החלון כולו. בדוגמאות שלנו אפשר להשתמש ב־flip() במקום update() ללא ארגומנטים. אין צורך להפעיל את שתיהן עבור אותו פריים. עדכון חלקי שימושי רק כאשר מנהלים גם את האזורים שהתוכן הקודם שלהם צריך להימחק; בתחילת הלימוד נרענן את כל המסך.
התוכנית עם שעון
נשלב את השעון בתוכנית החלון, כך שכל פריים יכלול גם רענון של התצוגה וגם הגבלת קצב.
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 400))
clock = pygame.time.Clock()
FPS = 60
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if not running:
break
pygame.display.update()
clock.tick(FPS)
pygame.quit()
קצב הפריימים אינו מהירות התנועה. עצם שמתקדם שלושה פיקסלים בכל פריים ינוע מרחק שונה בשנייה אם קצב הפריימים ישתנה. בפרק הבא, פרק האנימציה, נתחיל בתנועה לפי פריים ונראה גם כיצד משתמשים בזמן שחלף כדי לחשב את מרחק התנועה.
קוד להורדה: הדוגמה המלאה.