Python:Tkinter滞后

时间:2018-05-30 20:28:29

标签: python tkinter

我有一个问题。 我在tkinter做一个平台游戏,我有一个问题: 我现在有:玩家,积木和硬币。 我正在更新玩家的移动,它是动画和硬币的动画,出于某种原因,当我投入太多硬币时,玩家的动作开始滞后。 注意:我正在使用tkinter的after功能来为玩家的动作+动画制作动画,同样也适用于硬币。 对于重力等其他东西,我只使用线程。

硬币更新代码:

def coinsCheckCollision(self):
    cRemove = None
    indexRemove = -1
    count = 0
    for c in self.frame.coins:
        x, y , width , height = c.getRectangle()
        xP = self.player.getX; yP = self.player.getY; wP = self.player.getWidth; hP = self.player.getHeight
        if collisionDetect(xP , x, yP  , y, wP , width, hP , height) or collisionDetect(x , xP , y , yP , width , wP , height , hP):
            if count not in coinsRemoved:
                indexRemove = count
        if indexRemove != -1:
            if indexRemove not in coinsRemoved:
                coinsRemoved.append(indexRemove)
        count +=1

def coinsUpdateAnimations(self):
    count = 0
    for c in self.frame.coins:
        if count not in coinsRemoved:
            self.img = c.getAnimation()
            self.img = ImageTk.PhotoImage(self.img)
            self.frame.coinsImages[count] = self.img
        else:
            if self.frame.coinsImages[count] is not '' :
                self.frame.coinsImages[count] = ''
                self.frame.canvas.delete('coinB'+str(count))
        what = self.frame.canvas.itemconfig('coin' + str(count), image=self.frame.coinsImages[count])
        count += 1
    self.coinsCheckCollision()
    self.frame.frame.after(40 , self.coinsUpdateAnimations)

无论如何,简单的问题是:为什么当我更新多个彼此并不“真正”相关的东西时,gui会开始滞后?

1 个答案:

答案 0 :(得分:2)

您的设计似乎希望您的功能每40毫秒运行一次。也许+/-几毫秒,但平均每秒25次。

但事实并非如此。

首先,你有多少硬币,collisionDetect功能有多复杂?如果只需要1ms的一小部分来完成该循环,那就没什么大不了的了,但想想如果需要15毫秒会发生什么:你等待40毫秒,然后做15毫秒的工作,然后等待另一个40ms,然后做15ms的工作等等。所以你的工作每秒只运行15次,而不是25次。

现在假设每枚硬币花费0.2毫秒。 3枚硬币的延迟为0.6毫秒,几乎没有明显的变化。但是在100枚硬币的情况下,它的延迟时间为20毫秒。这会使硬币减速50%,这显然是显而易见的。

第二,正如the docs所说:

  

Tkinter只保证不会在此之前调用回调;如果系统繁忙,实际延迟可能会更长。

在任一方向上随机关闭几毫秒可能没​​问题;它最终会平均化。但是after总是迟到几毫秒,从来没有提前几毫秒,所以不是平均,而是建立起来,你会越走越远。

而且,更糟糕的是,如果你的某个功能落后,它会使每个after的延迟时间更长 - 所以它不会让你的硬币动画减速50%,但整场比赛在0-50%之间随意放慢了一些不可预测的数量,但可能足以引人注意。

要解决这两个问题,你需要随身携带一些类似你预期运行的时间,然后,不要做after(40),你可以这样做:

expected_time += 40
delay = expected_time - current_time
after(max(0, delay), func)

使用the datetime module

将其置于具体(虽然未经测试)的术语中
def __init__(self):
    self.next_frame_time = datetime.datetime.now()
    self.schedule()

def schedule(self):
    self.next_frame_time += datetime.timedelta(seconds=0.040)
    now = datetime.datetime.now()
    delta = max(datetime.timedelta(), now - self.next_frame_time)
    self.frame.frame.after(delta.total_seconds * 1000, self.coinsUpdateAnimations)

def coinsUpdateAnimations(self):
    # all the existing code before the last two lines
    self.coinsCheckCollision()
    self.schedule()

当你完成的工作总时间超过40毫秒时,这仍然无法解决问题。想象一下,你花费50毫秒,然后做一个after(0, func),它至少触发10毫秒,然后再花50毫秒,然后下一个after(0, func)触发至少20毫秒,依此类推。如果你不能在通常远低于40毫秒的时间内完成所有工作,那么你将无法跟上。你必须要么:

  • 找到一种优化代码的方法(例如,您可以使用更好的算法,或使用numpy代替for循环),
  • 重新设计游戏以减少工作量,或
  • 将帧速率降低到实际可以跟上的速度。

一个可能更好的解决方案是停止尝试将Tkinter弯曲到游戏框架中。它不是为此而设计的,并不能帮助你正确地获得所有细节,即使你做对了也不能很好地工作。

相比之下,顾名思义,像Pygame Zero这样的东西是为创造游戏而设计的。并且旨在让那些拥有比你似乎拥有的Python经验少得多的人能够轻松使用它。

例如,不是一个以你的操作系统想要运行它的任何速度运行的事件循环,而是让你有责任让所有时间都正确,Pygame Zero运行一个框架循环,调用你的update函数N次每秒,尽可能接近均匀。它还具有内置函数,用于碰撞检测,绘制动画精灵等等。