pygame.display.update()导致周期性滞后峰值

时间:2012-07-10 13:47:36

标签: python pygame

我用Pygame创建了一个简单的程序,它基本上是一个滚动的背景,并注意到周期性的延迟峰值。在搞乱代码很长一段时间之后,我发现调用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

当以60 FPS运行时,每个尖峰之间的间隔几乎总是1秒,非常罕见的2秒(并且尖峰将持续大约两倍的长度)。在较低的帧速率下,尖峰之间的间隔开始变化更大,但总是接近整数值。

我尝试在另一台计算机上运行该脚本,但问题没有被复制; pygame.display.update()上的执行时间相当快且一致。但是,当我在那台机器上运行我的原始程序时,仍然存在一秒间隔滞后峰值(我可能会寻找其他机器进行测试......)

我测试的两台机器都运行Windows 7。

编辑:

我抓住了在Pygame网站上托管的一些随机游戏,我得到了类似的行为 - 对pygame.display.update(或flip)的调用定期在10到40毫秒之间,而他们通常需要更少超过2毫秒。

似乎没有其他人遇到过这个问题(或者至少是抱怨。这可能是因为大多数游戏的运行速度低于30 FPS,这个问题不太明显),所以我的环境可能会出现问题。我确实(有点)在第二台机器上重现了这个问题(如上所述),所以我宁愿不理睬这个问题,希望最终用户不会遇到它......

3 个答案:

答案 0 :(得分: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()

这样做我似乎没有任何麻烦,但如果您仍然遇到问题,请告诉我。

答案 1 :(得分: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。它取决于睡眠机制的OS实现和所涉及的调度。

解决方案是不使用任何与睡眠相关的功能。

还有第二部分。即使您不使用任何睡眠(),也存在各个帧之间的增量时间不一致的问题,如果不加以考虑,可能会导致视觉抖动/口吃。我相信在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()

答案 2 :(得分:-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 #