Tkinter:优化画布对象移动

3
我写了一个非常简单的代码,可以将一个椭圆形移动到屏幕上:
from tkinter import *

""" GLOBAL VARS """
H, W = 400, 600  # default height / width
DELAY = 30  # frame rate in milliseconds
R = 20  # radius of the ball


class App:
    def __init__(self, parent):
        """ app init """
        self.root = parent
        self.canvas = Canvas(self.root, bg="white", height=H, width=W)
        self.canvas.pack()

        self.pos = [W / 2, H / 2]  # starting position
        self.speed = [2, 2]  # x / y speed

        self.canvas.create_oval(self.pos[0] - 20, self.pos[1] - 20, self.pos[0] + 20, self.pos[1] + 20, fill="blue")

        self.loop()


    def loop(self):
        """ main loop """
        coords = self.canvas.coords(1)[:2]  # actual oval coordinates

        if not 0 < coords[0] < W - 20:  # the oval bounce off the window
            self.speed[0] *= -1
        if not 0 < coords[1] < H - 20:
            self.speed[1] *= -1

        self.canvas.move(1, *self.speed)

        self.root.after(DELAY, self.loop)


""" GUI SETUP """
root = Tk()
App(root)
root.mainloop()

问题在于当我尝试增加帧率时,椭圆的一半消失了(我尝试拍了几张截图,但在这样做时,应用程序会冻结一个帧,并且在截图中椭圆显示正确,顺便说一句,您可以通过运行上面的代码来尝试自己)。
我的问题是:是否有一种优化动画的方法,或者我必须接受Tkinter无法处理高帧率这一事实?

以上代码对我来说完美运行。你在哪个平台上运行? - Bryan Oakley
@BryanOakley 我正在使用联想 ideapad 110 笔记本电脑上的 Windows 10。 - L'ultimo
1个回答

5

我认为这是一个文档不够详细的Tkinter性能问题。你看到椭圆形消失一半的原因是,当检测到需要更新画布上的内容时,它会获取需要更新的bbox,在你的情况下为椭圆形,然后只刷新画布上该部分(出于优化考虑)。

但是,当你快速移动椭圆形穿过画布时,它离开了需要更新的部分,这意味着椭圆形的某些部分将被截断,就像你所看到和描述的那样。

这就是损坏/修复模型

在我看来,解决并修复这个问题的最简单方法是创建一个更大的、对用户不可见的对象,围绕着您想要更新的内容。这意味着Tkinter检测到了更大的对象,并更新了该部件(画布)周围的部分。诀窍是让新的更新部分包含您希望出现的椭圆形。

您可以通过标签系统轻松移动较大的不可见对象和椭圆形。

要实现这一点,您可以将"circle"标签添加到您的椭圆形中,然后创建一个具有相同标签但坐标覆盖整个椭圆形的新椭圆形。还重要的是要指定fill=""outline="",以便您看不到新对象。

它看起来像:

    self.canvas.create_oval(self.pos[0] - 20, self.pos[1] - 20,
                            self.pos[0] + 20, self.pos[1] + 20, fill="blue", tag="circle")
    self.canvas.create_oval(self.pos[0] - 40, self.pos[1] - 40, self.pos[0] + 40,
                            self.pos[1] + 40, fill="", outline="", tag="circle")

第一个创建的椭圆是当前带有标签的椭圆,第二个椭圆是不可见的、更大的椭圆。

当您移动椭圆时,不要移动1(因为它是您最先创建的椭圆的ID),而应该移动"circle"

    self.canvas.move("circle", *self.speed)

在您实施此操作后,您的椭圆不应该显示任何截断。如果您仍然看到截断,则可以增加隐形椭圆的大小,例如偏移量为60而不是40


非常感谢您提供如此详细的答案!在1000fps下,40半径椭圆仍然存在一些问题,但是现在增加了大小后,它完美地运行了! - L'ultimo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接