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