讀古今文學網 > 父與子的編程之旅:與小卡特一起學Python > 17.3 統計時間 >

17.3 統計時間

到目前為止,我們一直在使用 time.delay 來控制動畫運行的快慢。不過這不是最好的辦法,這是因為,使用 time.delay 時,你並不真正知道每個循環需要多長時間。循環中的代碼要花一些時間來運行(這是一個未知時間),然後延遲也要花費一些時間(這是一個已知時間)。所以這個時間中有一部分是已知的,但有一部分是未知的。

如果我們想知道循環多長時間運行一次,就需要知道每個循環的總時間,這應當是代碼運行時間 + 延遲時間。要計算動畫的時間,使用毫秒或千分之一秒會很方便。它的縮寫是 ms,所以 25 毫秒就是 25 ms。

在我們的例子中,假設代碼時間是 15 ms。這說明,while 循環中的代碼運行需要 15 ms,這不包括 time.delay。我們已經知道延遲時間,因為這裡使用 time.delay(20) 把延遲設置為 20 ms。所以循環的總時間是 20 ms + 15 ms = 35 ms。由於 1 秒就是 1000 ms,如果每個循環需要 35 ms,可以得到 1000 ms / 35 ms = 28.57。這說明每秒大約有 29 個循環。在計算機圖形學中,每個動畫步叫做一幀,遊戲程序員討論圖形更新的快慢時都會提到幀速率(每秒幀數,fps)。在我們的例子中,幀速率大約是 29 fps。

問題在於,我們並不能真正控制這個公式中的「代碼時間」部分。如果增加或刪除代碼,這個時間就會改變。即使是相同的代碼,如果動畫精靈個數不同(例如,隨著遊戲對象的出現和消失,動畫精靈個數會變化),繪製這些精靈所花費的時間也會變化。此外,在不同的機器上,相同的代碼運行的速度也不同。可能不是 15 ms,代碼時間可能變成 10 ms 或 20 ms。如果有一種更便於預測的方法來控制幀速率就好了。好在,Pygame 的 time 模塊為我們提供了這樣的工具:一個名為 Clock 的類。

pygame.time.Clock 控制幀速率

並不是向每個循環增加一個延遲,pygame.time.Clock 會控制每個循環多長時間運行一次。這就像一個定時器在控制時間進程,指出「現在開始下一個循環!現在開始下一個循環!……」

使用 Pygame 時鐘之前,必須先創建 Clock 對象的一個實例。這與創建其他類的實例完全相同:

clock = pygame.time.Clock  

然後在主循環體中,只需要告訴時鐘多久「滴答」一次——也就是說,循環應該多長時間運行一次:

clock.tick(60)  

傳入 clock.tick 的數不是一個毫秒數。這是每秒內循環要運行的次數。所以這個循環應當每秒運行 60 次。在這裡我只是說「應當運行」,因為循環只能按計算機能夠保證的速度運行。每秒 60 個循環(或幀)時,每個循環需要 1000 / 60 = 16.66 ms(大約 17 ms)。如果循環中的代碼運行時間超過 17 ms,在 clock 指出開始下一次循環時當前循環將無法完成。

實際上,這說明對於圖形運行的幀速率有一個限制。這個限制取決於圖形的複雜程度、窗口大小以及運行這個程序的計算機的速度。對於一個特定的程序,計算機的運行速度可能是 90 fps,而較早的一個較慢的計算機也許只能以 10 fps 的速度緩慢運行。

對於非常複雜的圖形,大多數現代計算機都完全可以按 20 ~ 30 fps 的速率運行 Pygame 程序。所以如果希望你的遊戲在大多數計算機上都能以相同的速度運行,可以選擇一個 20 ~ 30 fps(或者更低)的幀速率。這已經很快了,足以生成看上去流暢的運動。從現在開始,這本書中的例子都將使用 clock.tick(30)

檢查幀速率

如果想知道你的程序能以多快的速度運行,可以用一個名為 clock.get_fps 的函數檢查幀速率。當然,如果將幀速率設置為 30,它就總會以 30 fps 的幀速率運行(假設你的計算機能夠運行那麼快)。要看一個特定程序在特定機器上運行的最快速度,可以先將 clock.tick 設置得非常快(例如 200 fps),然後運行這個程序,用 clock.get_fps 檢查實際的幀速率。(接下來就會給出一個這樣的例子。)

調整幀速率

如果想要確保你的動畫在每個機器上都以相同的速度運行,可以利用 clock.tickclock.get_fps 實現一個小技巧。因為你知道要以多快的速度運行,而且也知道實際運行的速度,因此可以根據機器的速度調整(scale)動畫的速度。

例如,假設已經設置了 clock.tick(30),這說明你想按 30 fps 的幀速率運行。 如果使用 clock.get_fps 並發現只得到速率為 20 fps,可以知道:屏幕上對像移動的速度比你希望的要慢。因為每秒的幀數更少,所以每一幀必須把對像移動得更遠,這樣看上去才跟得上預想的速度。你的移動對象可能有一個名為 speed 的變量(或屬性),這會告訴它們每一幀移動多遠。只需要增加 speed 對運行速度較慢的機器做出補償。

要增加多少呢?可以按期望幀頻率與實際幀速率的比值來增加。如果對象的當前速度是 10,期望的幀速率是 30 fps,程序實際運行速率為 20 fps,可以得到:

object_speed = current_speed * (desired fps / actual fps)object_speed = 10 * (30 / 20)object_speed = 15  

所以並不是每幀要將對像移動 10 個像素,而是需要移動 15 個像素,才能彌補較慢的幀速率。我們將在本書後面的一些程序中使用這個技巧。

下面的沙灘球程序使用了前面幾節討論的內容:Clockget_fps

代碼清單 17-4 沙灘球程序中使用 Clockget_fps

Pygame 和動畫精靈的基本知識就介紹完了。在下一章中,我們將使用 Pygame 建立一個真正的遊戲,我們還會介紹另外一些你能完成的工作,比如增加文本(顯示遊戲得分)、聲音和鼠標及鍵盤輸入。

你學到了什麼

在這一章,你學到了以下內容。

  • Pygame 中的動畫精靈,以及如何使用動畫精靈處理多個移動的圖像。

  • 動畫精靈組。

  • 碰撞檢測。

  • pygame.clock 和幀速率。

測試題

1. 什麼是碰撞檢測?

2. 什麼是像素完美碰撞檢測?它與矩形碰撞檢測有什麼區別?

3. 可以利用哪兩種方法跟蹤多個在一起的動畫精靈對像?

4. 在代碼中控制動畫的速度有哪兩種方法?

5. 為什麼使用 pygame.clock 比使用 pygame.time.delay 更準確?

6. 怎麼得出你的程序運行的幀速率?

動手試一試

鍵入這一章中的所有代碼示例就能讓你試個夠。如果還不夠,可以回過頭去再做一遍。相信你能從中得到很多收穫!