Python tkinter 性能问题

ki*_*eks 2 python tkinter

我想创建一个基于块物理的游戏,但是 fps 下降得非常快。我尝试尽可能地优化它,但它的 fps 仍然相当低(200 块给我大约 20 fps)。有谁知道如何优化我的块游戏?我本来打算添加更多内容,但看到这个问题我可能会放弃。

这是我的代码:

from tkinter import *
import math
import random
import time


master = Tk()

w = Canvas(master,width=600,height=600)
w.pack()

tileSize = 20

def randomColor():
    r = math.floor(random.random() * 255)
    g = math.floor(random.random() * 255)
    b = math.floor(random.random() * 255)
    return '#%02x%02x%02x' % (r, g, b)

class newElement:
    def __init__(self,x,y):
        x = math.floor(x/tileSize) * tileSize
        y = math.floor(y/tileSize) * tileSize
        self.x = x
        self.y = y
        self.vx = 0
        self.vy = 0
        self.width = tileSize
        self.height = tileSize
        self.id = random.random()
        self.color = randomColor()

elements = []

def mouse(event):
    global mouseX,mouseY
    mouseX,mouseY = event.x,event.y

    canPlace = True

    for y in range(0,len(elements)):
        e = elements[y]
        dx = e.x + e.width/2 - mouseX
        dy = e.y + e.height/2 - mouseY
        if math.sqrt(dx*dx+dy*dy) < 20:
            canPlace = False
            break

    if canPlace:
        elements.append(newElement(mouseX,mouseY))

mouseX = 0
mouseY = 0

w.bind("<B1-Motion>",mouse)

elements.append(newElement(300,50))
elements.append(newElement(300,100))

def collision(rect1,rect2):
   return rect1.x < rect2.x + rect2.width and rect1.x + rect1.width > rect2.x and rect1.y < rect2.y + rect2.height and rect1.height + rect1.y > rect2.y

def distance(rect1,rect2):
    dx = rect1.x - rect2.x
    dy = rect2.y - rect2.y
    return math.sqrt(dx*dx+dy*dy)

lastCall = time.time()

def engine():
    global lastCall
    w.delete("all")

    w.create_line(0,590,600,590)

    for i in range(0,len(elements)):
        e = elements[i]

        gravity = 0.1

        e.vy += gravity

        e.x += e.vx
        e.y += e.vy

        if e.y + e.height >= 590:
            e.y = 590-e.height
            e.vy = 0

        for x in range(0,len(elements)):
            e1 = elements[x]
            if e.id == e1.id or distance(e,e1) > 14.14:
                continue
            col = collision(e,e1)
            if col:
                e.y = e1.y - e1.height
                e.vy = 0

        w.create_rectangle(e.x,e.y,e.x+e.width,e.y+e.height,fill=e.color)

    w.create_text(10,10,anchor=NW,text=len(elements))
    fps = 60
    if time.time() - lastCall != 0:
        fps = round(1/(time.time() - lastCall))
    w.create_text(600,10,anchor=NE,text=fps)

    lastCall = time.time()

    master.after(16,engine)
engine()

mainloop()
Run Code Online (Sandbox Code Playgroud)

Bry*_*ley 7

对于像这样的简单程序,Tkinter 能够达到 60fps,但性能会根据您选择的算法而降低。我概述了您可以采取的几项措施来提高代码的性能。

当我编写一个与您的程序类似但经过下面概述的更改的程序时,我能够以 60 fps 的速度处理超过 1000 个项目。

不要删除和重画

最大的问题是您每秒要多次删除和创建矩形。这是非常低效的。更糟糕的是,一旦您创建了数千个项目,即使您后来删除了它们,画布也会出现性能问题。我已经成功地为数千个项目制作了动画,但如果您创建了数万个项目,它可能会开始变得缓慢。

相反,您应该做的是创建项目一次,然后为每个项目提供一个move方法,以便移动现有项目而不是删除然后重新创建它。

不要移动不需要移动的物品

第二个问题是您在每一帧中移动所有对象,即使它们的速度为零。无需对不会移动的物体进行计算。

让 tkinter 为您找到碰撞

第三个问题是您查找碰撞的算法效率极低。如果您有 300 个对象,则需要进行 300x300 的比较。例如,您将检查项目 1 是否与项目 2 碰撞,然后是项目 3,然后是项目 4,等等。然后,您检查项目 2 是否与项目 1、项目 3、项目 4 等发生碰撞。因为您已经已经确定项目 1 和 2 是否发生碰撞,没有理由查看项目 2 和 1 是否发生碰撞。另外,由于项目总是直接向下移动,因此您实际上只需要检查与当前项目正下方的项目的碰撞。

您可以通过让 tkinter 为您完成工作来解决碰撞问题。给定一个对象,您可以使用画布的find_overlapping方法获取与它重叠的所有对象的列表。这可能比将每个对象与每个对象进行比较要快几个数量级,因为它是由 tkinter 在内部画布数据结构上内部完成的(即:它是使用 C 代码而不是 Python 代码)。