3

我用 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,而这个问题不太明显),所以我的环境可能有问题。我确实(有点)在第二台机器上重现了这个问题(如上所述),所以我宁愿不要忽视这个问题,希望最终用户不会遇到它......

4

3 回答 3

2

尝试在游戏开发中提出这个问题,您可能会得到更好的答案。

编辑:以下代码似乎没有解决提出的问题,但确实提供了动画测试并为主游戏循环使用定时回调

尝试对渲染函数使用定时回调。

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()

执行此操作时我似乎没有任何问题,但是如果您仍然遇到问题,请告诉我。

于 2012-07-24T10:26:41.337 回答
1

经过一些测试,这里有一些结果。首先,回答问题:延迟尖峰的原因不是pygame.display.update()。延迟尖峰的原因是clock.tick(FRAME_RATE)。请注意,没有 FRAME_RATE 参数的 clock.tick() 不会导致尖峰。当我尝试使用 python 的 time.sleep() 方法手动跟踪帧速率来替换 pygame 的 clock.tick() 时,问题并没有消失。我认为这是因为在内部,python 的 time.sleep() 和 pygame 的 clock.tick() 都使用相同的函数,这被认为是不精确的。似乎如果你让这个函数休眠 1 毫秒(如果游戏足够简单,这样就不会占用所有的 CPU),这个函数有时会比这长得多,大约长 10-15 毫秒。这取决于睡眠机制的操作系统实现和所涉及的调度。

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

还有第二部分。即使您不使用任何 sleep(),也存在各个帧之间的增量时间不一致的问题,如果不考虑这一点,可能会导致视觉抖动/卡顿。我相信这个问题已经在本教程中进行了非常详细的探讨。

因此,我继续在 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()
于 2012-07-24T09:26:36.050 回答
-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 # 
于 2015-09-02T12:09:48.163 回答