2012-07-10 75 views
3

我做了一個簡單的程序與pygame的那基本上是一個滾動的背景,發現週期性的滯後峯值。經過很長時間的代碼搞亂之後,我發現調用pygame.display.update()有時需要很長時間才能執行。pygame.display.update()導致週期性的滯後尖峯

要真正剝離下來並複製問題,我寫了下面的代碼:

import pygame 
import sys 
import time 

FRAME_RATE = 30 

# don't mind the screen and time_passed variables; they aren't used in this script 

def run_game(): 
    pygame.init() 
    clock = pygame.time.Clock() 
    screen = pygame.display.set_mode((500, 500)) 
    prev_spike = 0 
    time_passed = 0 
    while 1: 
     start = time.clock() 
     pygame.display.update() 
     timenow = time.clock() 
     time_spent = timenow - start 
     if time_spent > 0.01: 
      print time_spent 
      if prev_spike: 
       print "Last spike was: {} seconds ago".format(timenow - prev_spike) 
      prev_spike = timenow 
     time_passed = clock.tick(FRAME_RATE) 

if __name__ == "__main__": 
    run_game() 

在那個幀率輸出的一個片段:

0.0258948412828 
Last spike was: 1.01579813191 seconds ago 
0.0186809297657 
Last spike was: 0.982841934526 seconds ago 
0.0225958783907 
Last spike was: 2.01697784257 seconds ago 
0.0145269648427 
Last spike was: 1.01603407404 seconds ago 
0.0186094554386 
Last spike was: 2.01713885195 seconds ago 
0.0283046020628 
Last spike was: 1.03270104172 seconds ago 
0.0223322687757 
Last spike was: 1.01709735072 seconds ago 
0.0152536205013 
Last spike was: 1.01601639759 seconds ago 

我真的不知道是什麼繼續前進,並會真正喜歡一些見解。

更多的細節:

在每一個循環迭代打印time_spent(而不是僅當它是> 0.01)當輸出的一個片段:

0.000204431946257 
0.000242090462673 
0.000207890381438 
0.000272447838151 
0.000230178074828 
0.0357667523718   <-- update taking two orders of magnitude longer than normal 
0.000293582719813 
0.000343153624075 
0.000287818661178 
0.000249391603611 

當以60fps運行的每個峯值之間的間隔幾乎總是1秒,很少2秒(峯值會持續兩倍左右)。在較低的幀率下,峯值之間的間隔開始變化更多,但總是接近整數值。

我試圖在另一臺計算機上運行腳本,但沒有複製的問題; pygame.display.update()上的執行時間相當快速且一致。然而,當我跑這臺機器上我原來的計劃,在1秒間隔滯後峯值保持(我可能會尋找其他的機器上測試...)

我測試在兩臺機器上運行Windows 7 。

編輯:

我抓住舉辦Pygame的網站上的一些隨機的遊戲,我得到類似的行爲 - 調用pygame.display.update(或flip)10之間定期進行 - 40毫秒,而他們通常需要較少比2毫秒。

沒有其他人似乎有這個問題(或抱怨,至少這可能是因爲大多數遊戲運行速度低於30 FPS這個問題不太明顯),所以有可能是我的東西環境。我(有點)重現該問題的第二臺機器上,但(如上所述),所以我寧願不忽視的問題,並希望最終用戶不會遇到它......

+0

我沒有直接回答,但這些都是時間步長的問題很好的資源:http://gafferongames.com/game-physics/fix-your-timestep/,HTTP://gamedev.stackexchange .com/questions/1589/fixed-time-step-vs-variable-time-step – ninMonkey 2012-07-10 20:48:38

+0

@monkey感謝您的聯繫。使用dt可以防止這個問題影響遊戲/模擬,但問題本身仍然沒有解決。我想我可以只是希望用戶不會注意到口吃或什麼。 – mizzinx 2012-07-11 03:33:35

+0

由於你使用pygame,你可以從'pygame.time.get_ticks()'http://www.pygame.org/docs/ref/time.html#pygame.time.get_ticks – ninMonkey 2012-07-11 07:29:27

回答

1

經過一番測試,下面是一些結果。首先,回答這個問題: 滯後峯值的原因不是pygame.display.update()。滯後尖峯的原因是clock.tick(FRAME_RATE)。請注意,沒有FRAME_RATE參數的clock.tick()不會導致尖峯。當我試圖用python的time.sleep()方法替代pygame的clock.tick()手動跟蹤幀速率時,問題並沒有消失。我認爲這是因爲在內部,python的time.sleep()和pygame的clock.tick()使用相同的函數,這已知是不準確的。看起來,如果您將該函數提供1ms進入睡眠狀態(以便在遊戲足夠簡單的情況下不佔用所有CPU),該函數有時會比這個睡眠時間長得多,大約需要10-15ms。這取決於睡眠機制的操作系統實施和涉及的調度。

的解決方法是不使用任何與睡眠有關的功能。

還有一個第二部分。即使您沒有使用任何睡眠(),也存在個別幀之間的增量時間不一致的問題,這些問題在不考慮時可能會導致視覺抖動/口吃。我相信這個問題已經在這tutorial詳細探討。

因此,我繼續前進,並在python和pygame中實現了本教程中介紹的解決方案,並且完美地工作。即使我僅以30fps更新「物理」,它看起來也非常流暢。它吃了很多CPU,但看起來不錯。下面是代碼:

from __future__ import division 
import pygame 
from random import randint 
from math import fabs 

PHYS_FPS = 30 
DT = 1/PHYS_FPS 
MAX_FRAMETIME = 0.25 



def interpolate(star1, star2, alpha): 
    x1 = star1[0] 
    x2 = star2[0] 
    # since I "teleport" stars at the end of the screen, I need to ignore 
    # interpolation in such cases. try 1000 instead of 100 and see what happens 
    if fabs(x2 - x1) < 100: 
     return (x2 * alpha + x1 * (1 - alpha), star1[1], star1[2]) 
    return star2 


def run_game(): 
    pygame.init() 
    clock = pygame.time.Clock() 
    screen = pygame.display.set_mode((500, 500)) 

    # generate stars 
    stars = [(randint(0, 500), randint(0, 500), randint(2, 6)) for i in range(50)] 
    stars_prev = stars 

    accumulator = 0 
    frametime = clock.tick() 

    play = True 
    while play: 
     frametime = clock.tick()/1000 
     if frametime > MAX_FRAMETIME: 
      frametime = MAX_FRAMETIME 

     accumulator += frametime 

     # handle events to quit on 'X' and escape key 
     for e in pygame.event.get(): 
      if e.type == pygame.QUIT: 
       play = False 
      elif e.type == pygame.KEYDOWN: 
       if e.key == pygame.K_ESCAPE: 
        play = False 

     while accumulator >= DT: 
      stars_prev = stars[:] 
      # move stars 
      for i, (x, y, r) in enumerate(stars): 
       stars[i] = (x - r * 50 * DT, y, r) if x > -20 else (520, randint(0, 500), r) 
      accumulator -= DT 

     alpha = accumulator/DT 
     stars_inter = [interpolate(s1, s2, alpha) for s1, s2 in zip(stars_prev, stars)] 

     # clear screen 
     screen.fill(pygame.Color('black')) 

     # draw stars 
     for x, y, r in stars_inter: 
      pygame.draw.circle(screen, pygame.Color('white'), (int(x), y), r) 

     pygame.display.update() 

if __name__ == "__main__": 
    run_game() 
+0

是的,'pygame.Clock .tick()'不準確,因爲它只是使用'SDL_Delay'。 'pygame.Clock.tick_busy_loop()'更準確,但浪費更多的CPU能力。 – sloth 2012-07-25 06:17:53

+0

更新與工作pygame代碼,插值和其他好東西。 – Mikka 2012-07-25 16:05:57

+0

太棒了,但不幸的是我不認爲這個答案適用於我的問題。我在我的機器上嘗試了您的代碼(和其他答案的代碼),尖峯仍然存在。我提供的代碼測量update()執行所花費的時間,所以沒有空間讓tick()混淆任何事情。 – mizzinx 2012-07-26 11:35:52

2

Try asking this in Game Development,你可能會得到更好的答案。

編輯:下面的代碼似乎並沒有解決提出的問題,但確實提供了動畫測試,並使用定時回調是遊戲的主要循環

嘗試用定時回調努力渲染功能。

import pygame 
import time 
import math 
from pygame.locals import * 

desiredfps = 60 
updaterate = int(1000/desiredfps) 
print "Aiming for {0}fps, update every {1} millisecond".format(desiredfps, updaterate) 
lasttime = 0 
rectx = 0 
recty = 0 

def run_game(): 
    pygame.init() 
    screen = pygame.display.set_mode((500, 500)) 
    pygame.time.set_timer(USEREVENT+1, updaterate) 

    def mainloop(): 
     global lasttime 
     global rectx 
     global recty 
     screen.fill(pygame.Color("white")) 
     screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20)) 
     screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20)) 
     screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20)) 
     screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20)) 
     rectx += 5 
     if rectx > 500: 
      rectx = 0 
      recty += 20 
     beforerender = time.clock() 
     pygame.display.update() 
     afterrender = time.clock() 
     renderdelta = afterrender - beforerender 
     framedelta = beforerender - lasttime 
     lasttime = beforerender 
     if renderdelta > 0.01: 
      print "render time: {0}".format(renderdelta) 
      print "frame delta: {0}".format(framedelta) 
      print "-------------------------------------"    
    while(1): 
     for event in pygame.event.get(): 
      if event.type == USEREVENT+1: 
       mainloop() 
      if event.type == QUIT: 
       pygame.quit() 
       return 
# Run test   
run_game() 

我似乎並沒有這樣做的時候有任何麻煩,但請讓我知道,如果你仍然遇到的問題。

+0

沒有爲我工作。報告的幀時間沒有滯後尖峯,但視覺上(我添加了移動物體)時滯非常明顯。 – Mikka 2012-07-24 11:27:19

+0

嗯..嘗試編輯。它只有非常基本的動畫。如果你仍然看到滯後,那麼我不知道,否則它可能是你的動畫。 – Aesthete 2012-07-24 11:56:21

+0

它有相同的結果。 – Mikka 2012-07-24 15:22:10

-2
import pygame 
import time 
import math 
from pygame.locals import * 
desiredfps = 60 
updaterate = int(1000/desiredfps) 
lasttime = 0 
rectx = 0 
recty = 0 

def run_game(): 
    pygame.init() 
    screen = pygame.display.set_mode((500, 500)) 
    pygame.time.set_timer(USEREVENT+1, updaterate) 
    def mainloop(): 
     global lasttime 
     global rectx 
     global recty 
     screen.fill(pygame.Color("white")) 
     screen.fill(pygame.Color("red"), pygame.Rect(rectx,recty,20,20)) 
     screen.fill(pygame.Color("blue"), pygame.Rect(480-rectx,480-recty,20,20)) 
     screen.fill(pygame.Color("green"), pygame.Rect(rectx,480-recty,20,20)) 
     screen.fill(pygame.Color("yellow"), pygame.Rect(480-rectx,recty,20,20)) 
     rectx += 5 
     if rectx > 500: 
      rectx = 0 
      recty += 20 
     beforerender = time.clock() 
     pygame.display.update() 
     afterrender = time.clock() 
     renderdelta = afterrender - beforerender 
     framedelta = beforerender - lasttime 
     lasttime = beforerender 
     if renderdelta > 0.01: 
      print ("render time: {0}").format(renderdelta) 
      print ("frame delta: {0}").format(framedelta) 
      print ("-------------------------------------")  

    while(1): 
     for event in pygame.event.get(): 
      if event.type == USEREVENT+1: 
       mainloop() 
      if event.type == QUIT: 
       pygame.quit() 
       return # 
+0

沒有解釋,沒有文字,只有代碼。這是否應該回答這個問題? – 2016-02-25 10:41:17