Tkinter 程序在 canvas.delete('all') 和 canvas.destroy() 之后变慢



我正在tkinter中创建一个简单的乒乓球游戏克隆,我想创建一个游戏循环,这样当游戏结束时,它就会重新启动。

import tkinter as tk
import random
import time
from time import sleep
# setting base Tk widget
root = tk.Tk()
root.resizable(False, False)
root.title("Pong")
root.configure(bg='black')
# finding the center of the screen
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
canvas_w = 1280
canvas_h = 720
center_x = int(screen_w/2 - canvas_w / 2)
center_y = int(screen_h/2 - canvas_h / 2)
root.geometry(f'{canvas_w}x{canvas_h}+{center_x}+{center_y}')
def main():
# canvas
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
cv.delete('all')
cv.configure(bg="#000000")
global x, y, num_r, num_l, ball
# initializing variables
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
x = 0
y = 0
num_l = 0
num_r = 0
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
#print(dirx, diry)

# paddles
paddle_l = cv.create_rectangle(20, 320, 30, 390, outline='#000000', fill='#ffffff')
paddle_r = cv.create_rectangle(1250, 320, 1260, 390, outline='#000000', fill='#ffffff')
# middle line
midline1 = cv.create_rectangle(canvas_w/2-5, 25, canvas_w/2+5, 85, outline='#000000', fill='#ffffff')
midline2 = cv.create_rectangle(canvas_w/2-5, 125, canvas_w/2+5, 185, outline='#000000', fill='#ffffff')
midline3 = cv.create_rectangle(canvas_w/2-5, 225, canvas_w/2+5, 285, outline='#000000', fill='#ffffff')
midline4 = cv.create_rectangle(canvas_w/2-5, 325, canvas_w/2+5, 385, outline='#000000', fill='#ffffff')
midline5 = cv.create_rectangle(canvas_w/2-5, 425, canvas_w/2+5, 485, outline='#000000', fill='#ffffff')
midline6 = cv.create_rectangle(canvas_w/2-5, 525, canvas_w/2+5, 585, outline='#000000', fill='#ffffff')
midline7 = cv.create_rectangle(canvas_w/2-5, 625, canvas_w/2+5, 685, outline='#000000', fill='#ffffff')
# score trackers
score_left = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_right = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_left.place(relx=0.43, rely=0.1)
score_right.place(relx=0.55, rely=0.1)
# ball
ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, outline='#000000', fill='#696969')
# movement of the paddles
def detectMoveKeys():
# left paddle
root.bind('w', leftUp)
root.bind('s', leftDown)

# right paddle
root.bind('i', rightUp)
root.bind('k', rightDown)
root.after(5, detectMoveKeys)
def leftUp(self):
cv.move(paddle_l, 0, -10)
#print('registered')
def leftDown(self):
cv.move(paddle_l, 0, 10)
#print('registered')
def rightUp(self):
cv.move(paddle_r, 0, -10)
#print('registered')
def rightDown(self):
cv.move(paddle_r, 0, 10)
#print('registered')

def ballMovement():
global x, y 
if cv.coords(ball)[1]+10 == canvas_h:
y = y * (0-1)
print('ball touched bottom', x, y)

if cv.coords(ball)[1] == 0:
y = y * (0-1)
print('ball touched top', x, y)

pos = cv.coords(ball)
if paddle_l in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched left paddle', x, y)
if paddle_r in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched right paddle', x, y)
cv.move(ball, x, y)
#print(cv.coords(ball)[0])
cv.pack()
root.after(35, ballMovement) 
def endGame():
if score_right['text'] == '10':
score_right['text'] = '0'
score_left['text'] = '0'
r_won = tk.Label(text='Right won,nthe game willnrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
r_won.place(relx=0.3, rely=0.5)
cv.delete('all')
score_right.config(text='')
score_left.config(text='')
cv.destroy()
root.after(3000, main)
root.after(10, endGame)
def outOfBounds():
global num_l, num_r, ball, dirx, diry, x, y

# detects if the ball is out of bounds on the left side of the screen
if cv.coords(ball)[0] == -30.0:
print('right scored')
num_r += 1
score_right['text'] = str(num_r)
cv.pack()
cv.coords(ball, 630, 350, 650, 370)
#ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, fill='#696969')
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
# detects if the ball is out of bounds on the right side of the screen
if cv.coords(ball)[0] == 1310.0:
print('left scored')
num_l += 1
score_left['text'] = str(num_l)
cv.pack()

cv.delete(ball)
ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, fill='#696969')
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
root.after(10, outOfBounds)
root.after(5, detectMoveKeys)
root.after(35, ballMovement)
root.after(10, outOfBounds)
root.after(10, endGame)
main()
root.mainloop()

当我第一次启动程序时,一切都很顺利,直到第一个游戏循环开始。然后它开始滞后并最终冻结。

以下是错误消息:

Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib64/python3.10/tkinter/__init__.py", line 1921, in __call__
return self.func(*args)
File "/usr/lib64/python3.10/tkinter/__init__.py", line 839, in callit
func(*args)
File "/home/anton/Documents/pong/pong.py", line 140, in outOfBounds
if cv.coords(ball)[0] == -30.0:
File "/usr/lib64/python3.10/tkinter/__init__.py", line 2795, in coords
self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib64/python3.10/tkinter/__init__.py", line 1921, in __call__
return self.func(*args)
File "/usr/lib64/python3.10/tkinter/__init__.py", line 839, in callit
func(*args)
File "/home/anton/Documents/pong/pong.py", line 103, in ballMovement
if cv.coords(ball)[1]+10 == canvas_h:
File "/usr/lib64/python3.10/tkinter/__init__.py", line 2795, in coords
self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"

我尝试过的:

  1. 确保所有变量在游戏开始后都设置为默认值
  2. 将所有内容放入tk.Frame小部件并销毁它

如果它对我运行Fedora 36工作站有帮助。

有人知道出了什么问题吗?提前谢谢。

编辑:

我已经实现了一个"结束"标志,这样循环就停止了,但程序在重新启动后又开始滞后。我也不知道怎么解决这个问题。

...
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
def main():
#print('main called')
global x, y, num_r, num_l, ball, end
# canvas
cv.configure(bg="black") 
...
end = False
...
def detectMoveKeys():
...
if end == False:
root.after(5, detectMoveKeys)
...
def ballMovement():
...
if end == False:
root.after(35, ballMovement) 
def outOfBounds():
...
if end == False:
root.after(10, outOfBounds)
def endGame():
global end, ball
def r_won_destroy():
r_won.destroy()
cv.coords(ball, canvas_w/2-5, canvas_h/2-5, canvas_w/2+5, canvas_h/2+5)
cv.delete('all')

if score_right['text'] == '10':
score_right['text'] = ''
score_left['text'] = ''
r_won = tk.Label(text='Right won,nthe game willnrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
r_won.place(relx=0.3, rely=0.5)
end = True
#cv.itemconfig('all', fill='black')
root.after(3000, main)
root.after(2000, r_won_destroy)
if end == False:
root.after(10, endGame)
if end == False:
root.after(5, detectMoveKeys)
root.after(35, ballMovement)
root.after(10, outOfBounds)
root.after(10, endGame)
main()
root.mainloop()

我是国际米兰的新手,如果这是一个明显的错误,我很抱歉。

有人能解决这个问题吗?

部分问题在于这段代码:

def endGame():
if score_right['text'] == '10':
...
root.after(3000, main)
root.after(10, endGame)

如果if条件为true,则在3秒钟后调用main。您继续每秒调用endGame100次,无论是真是假。因此,即使游戏结束,您也会继续每秒调用endGame100次。

当您从if块内部调用main时,main也会调用endGame,导致endGame开始一个每秒运行100次的新循环。因此,在一次重新启动后,您每秒调用endGame100次两次。第三次重新启动时,您每秒调用endGame100次,共调用三次。等等。最终,你会每秒调用endGame数千次,远远超过它所能跟上的频率。

detectMoveKeysballMovementoutOfBounds也发生了同样的情况。当游戏结束时,你永远不会停止这些功能的无尽循环,每次游戏开始时,你都会开始一个新的无尽循环。

我想我做到了。我可能只是一秒钟调用outOfBoundsballMovementdetectMoveKeysendGame太多次了。(100次(我已经把它减少到每秒5次,滞后似乎已经消失了。我已经测试了100次重新启动,它仍然有响应。但我觉得在未定义数量的重新启动之后,它将再次滞后,因为函数调用可能仍然堆叠在一起。如果是这样的话,请告诉我。非常感谢你的帮助

以下是测试它的完整代码:

import tkinter as tk
import random
# setting base Tk widget
root = tk.Tk()
root.resizable(False, False)
root.title("Pong")
root.configure(bg='black')
# finding the center of the screen
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
canvas_w = 1280
canvas_h = 720
center_x = int(screen_w/2 - canvas_w / 2)
center_y = int(screen_h/2 - canvas_h / 2)
root.geometry(f'{canvas_w}x{canvas_h}+{center_x}+{center_y}')
# canvas
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
cv.configure(bg="black")
# paddles
paddle_l = cv.create_rectangle(20, 320, 30, 390, outline='#000000', fill='#ffffff')
paddle_r = cv.create_rectangle(1250, 320, 1260, 390, outline='#000000', fill='#ffffff')
# middle line
midline1 = cv.create_rectangle(canvas_w/2-5, 25, canvas_w/2+5, 85, outline='#000000', fill='#ffffff')
midline2 = cv.create_rectangle(canvas_w/2-5, 125, canvas_w/2+5, 185, outline='#000000', fill='#ffffff')
midline3 = cv.create_rectangle(canvas_w/2-5, 225, canvas_w/2+5, 285, outline='#000000', fill='#ffffff')
midline4 = cv.create_rectangle(canvas_w/2-5, 325, canvas_w/2+5, 385, outline='#000000', fill='#ffffff')
midline5 = cv.create_rectangle(canvas_w/2-5, 425, canvas_w/2+5, 485, outline='#000000', fill='#ffffff')
midline6 = cv.create_rectangle(canvas_w/2-5, 525, canvas_w/2+5, 585, outline='#000000', fill='#ffffff')
midline7 = cv.create_rectangle(canvas_w/2-5, 625, canvas_w/2+5, 685, outline='#000000', fill='#ffffff')
# score trackers
score_left = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_right = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_left.place(relx=0.43, rely=0.1)
score_right.place(relx=0.55, rely=0.1)
# ball
ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, outline='#000000', fill='#696969')
cv.pack()
# movement of the paddles
def detectMoveKeys():
# left paddle
root.bind('w', leftUp)
root.bind('s', leftDown)

# right paddle
root.bind('i', rightUp)
root.bind('k', rightDown)
if end == False:
root.after(200, detectMoveKeys)
def leftUp(self):
cv.move(paddle_l, 0, -10)
#print('registered')
def leftDown(self):
cv.move(paddle_l, 0, 10)
#print('registered')
def rightUp(self):
cv.move(paddle_r, 0, -10)
#print('registered')
def rightDown(self):
cv.move(paddle_r, 0, 10)
#print('registered')
def ballMovement():
global x, y 
# checking if the ball touched the bottom side of the window
if cv.coords(ball)[1]+10 == canvas_h:
y = y * (0-1)
print('ball touched bottom', x, y)
# checking if the ball touched the top side of the window
if cv.coords(ball)[1] == 0:
y = y * (0-1)
print('ball touched top', x, y)

pos = cv.coords(ball)
if paddle_l in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched left paddle', x, y)
if paddle_r in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched right paddle', x, y)
cv.move(ball, x, y)
#print(cv.coords(ball)[0])
cv.pack()
if end == False:
root.after(35, ballMovement) 
def outOfBounds():
global num_l, num_r, ball, dirx, diry, x, y
# checking if the ball is out of bounds on the left side
if cv.coords(ball)[0] <= -30.0:
print('right scored')
num_r += 1
score_right['text'] = str(num_r)

# setting the ball to the original position and setting the direction
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.pack()
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
# checking if the ball is out of bounds on the right side
if cv.coords(ball)[0] >= 1310.0:
print('left scored')
num_l += 1
score_left['text'] = str(num_l)

# setting the ball to the original position and setting the direction
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.pack()
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
if end == False:
root.after(200, outOfBounds)
# checking if the game should end
def endGame():
global end, ball, paddle_l, paddle_r
# deleting the label and settings objects back to original positions
def r_won_destroy():
r_won.destroy()
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.coords(paddle_l, 20, 320, 30, 390)
cv.coords(paddle_r, 1250, 320, 1260, 390)
score_right['text'] = '0'
score_left['text'] = '0'
# checking if the right score is 10, if yes, the game ends
if score_right['text'] == '10':
r_won = tk.Label(text='Right won,nthe game willnrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
r_won.place(relx=0.3, rely=0.5)
end = True
#cv.itemconfig('all', fill='black')
root.after(3000, main)
root.after(2000, r_won_destroy)
# deleting the label and settings objects back to original positions
def l_won_destroy():
l_won.destroy()
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.coords(paddle_l, 20, 320, 30, 390)
cv.coords(paddle_r, 1250, 320, 1260, 390)
score_right['text'] = '0'
score_left['text'] = '0'
# checking if the left score is 10, if yes, the game ends
if score_left['text'] == '10':
l_won = tk.Label(text='Left won,nthe game willnrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
l_won.place(relx=0.3, rely=0.5)
end = True
#cv.itemconfig('all', fill='black')
root.after(3000, main)
root.after(2000, l_won_destroy)
if end == False:
root.after(200, endGame)
def main():
# initializing variables
global x, y, end, num_l, num_r
end = False
num_l = 0
num_r = 0
x = 0
y = 0
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
# setting the starting diretion of the ball
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
# calling other functions
detectMoveKeys()
ballMovement()
outOfBounds()
endGame()
# calling the main function
main()
# starting mainloop for the main window object
root.mainloop()

相关内容

  • 没有找到相关文章

最新更新