Tkinter贪吃蛇游戏快速退出重进后蛇身重复绘制问题求助
问题根源
快速退出重进时蛇被多次绘制,核心原因是旧的游戏循环定时器未被取消。每次调用next_turn时,window.after(speed, next_turn, snake, food)会创建一个新的定时器,退出游戏时如果没取消这个定时器,它会继续执行,和新启动的游戏循环同时绘制蛇,导致多重蛇的问题。
解决步骤
- 新增全局变量存储定时器ID,用于后续取消操作
- 在退出游戏的函数(
go_to_start_screen、game_over)中,取消未执行的定时器 - 启动新游戏前,先清理可能残留的旧定时器
修改后的完整代码
from tkinter import * import random GAME_WIDTH = 700 GAME_HEIGHT = 700 SPACE_SIZE = 25 BODY_PARTS = 3 SNAKE_COLOUR = "#00A86B" FOOD_COLOUR = "#DC143C" BACKGROUND_COLOUR = "#2C3E50" TITLE_FONT = ("Helvetica", 48, "bold") BUTTON_FONT = ("Helvetica", 24) TEXT_COLOR = "#ECF0F1" speed = 100 mode_ = "Medium" in_game = False in_start_screen = True after_id = None # 新增:存储定时器ID class Snake: def __init__(self): self.body_size = BODY_PARTS self.coordinates = [] self.squares = [] for i in range(BODY_PARTS): self.coordinates.append([0, 0]) for x, y in self.coordinates: square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOUR, tag="snake") self.squares.append(square) class Food: def __init__(self): x = random.randint(0, (GAME_WIDTH / SPACE_SIZE) - 1) * SPACE_SIZE y = random.randint(0, (GAME_HEIGHT / SPACE_SIZE) - 1) * SPACE_SIZE self.coordinates = [x, y] canvas.create_oval(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=FOOD_COLOUR, tag="food") def next_turn(snake, food): global direction, score, after_id x, y = snake.coordinates[0] if direction == "up": y -= SPACE_SIZE elif direction == "down": y += SPACE_SIZE elif direction == "left": x -= SPACE_SIZE elif direction == "right": x += SPACE_SIZE snake.coordinates.insert(0, (x, y)) square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOUR) snake.squares.insert(0, square) if x == food.coordinates[0] and y == food.coordinates[1]: score += 1 label.config(text=f"Score: {score}") canvas.delete("food") food = Food() if mode_ == "Dynamic": global speed speed = max(10, speed - 5) else: del snake.coordinates[-1] canvas.delete(snake.squares[-1]) del snake.squares[-1] if check_collisions(snake): game_over() else: # 存储定时器ID after_id = window.after(speed, next_turn, snake, food) def change_direction(new_direction): global direction if new_direction == "left" and direction != "right": direction = new_direction elif new_direction == "right" and direction != "left": direction = new_direction elif new_direction == "up" and direction != "down": direction = new_direction elif new_direction == "down" and direction != "up": direction = new_direction def check_collisions(snake): x, y = snake.coordinates[0] if x < 0 or x >= GAME_WIDTH or y < 0 or y >= GAME_HEIGHT: return True for body_part in snake.coordinates[1:]: if x == body_part[0] and y == body_part[1]: return True return False def game_over(): global in_game, in_start_screen, after_id in_game = False in_start_screen = False # 取消定时器 if after_id: window.after_cancel(after_id) after_id = None canvas.delete(ALL) canvas.create_text(canvas.winfo_width() / 2, canvas.winfo_height() / 2, font=("Helvetica", 50, "bold"), text="GAME OVER", fill=FOOD_COLOUR, tag="gameover") canvas.create_text(canvas.winfo_width() / 2, canvas.winfo_height() / 1.5, font=("Helvetica", 20), text="\nPress SPACE to try again.\nPress ESC to return to the home screen.", fill=FOOD_COLOUR, tag="gameover") window.bind("<space>", lambda event: start_game()) window.bind("<Escape>", lambda event: go_to_start_screen()) def start_game(): global score, direction, food, snake, label, canvas, in_game, in_start_screen, after_id in_game = True in_start_screen = False # 先取消可能存在的旧定时器 if after_id: window.after_cancel(after_id) after_id = None canvas.delete("snake") canvas.delete("food") canvas.pack_forget() if label: label.destroy() if start_button: start_button.destroy() if title_label: title_label.destroy() if easy_button: easy_button.destroy() if medium_button: medium_button.destroy() if hard_button: hard_button.destroy() if dynamic_button: dynamic_button.destroy() canvas.pack(side=BOTTOM, anchor="n") canvas.delete(ALL) label = Label(window, text="Score: 0", font=("Helvetica", 40), bg=BACKGROUND_COLOUR, fg=TEXT_COLOR, width=23) label.pack(pady=10) score = 0 direction = "down" label.config(text=f"Score: {score}") snake = Snake() food = Food() next_turn(snake, food) window.bind("<Escape>", lambda event: go_to_start_screen()) def start_screen(): global start_button, title_label, easy_button, medium_button, hard_button, canvas, window, button_style, in_game, in_start_screen, dynamic_button in_game = False in_start_screen = True canvas.pack_forget() canvas.delete(ALL) window.geometry(f"{GAME_WIDTH}x{GAME_HEIGHT + 100}") window.configure(bg=BACKGROUND_COLOUR) title_label = Label(window, text="Snake Game", font=TITLE_FONT, bg=BACKGROUND_COLOUR, fg=TEXT_COLOR) title_label.pack(pady=(40, 20)) button_style = { 'font': ("Helvetica", 20), 'bg': "#2980B9", 'fg': TEXT_COLOR, 'activebackground': "#3498DB", 'activeforeground': TEXT_COLOR, 'bd': 0, 'width': 18, 'height': 2, 'cursor': 'hand2' } start_button = Button(window, text=f"Start Game\n({mode_})", **button_style, command=start_game) easy_button = Button(window, text="Easy", **button_style, command=lambda: update_mode("easy")) medium_button = Button(window, text="Medium", **button_style, command=lambda: update_mode("medium")) hard_button = Button(window, text="Hard", **button_style, command=lambda: update_mode("hard")) dynamic_button = Button(window, text="Dynamic", **button_style, command=lambda: update_mode("dynamic")) easy_button.pack(pady=10) medium_button.pack(pady=10) hard_button.pack(pady=10) dynamic_button.pack(pady=10) start_button.pack(pady=(20, 10)) window.unbind("<Escape>") def update_mode(selected_mode): global speed, mode_, start_button if selected_mode == "easy": speed = 100 mode_ = "Easy" elif selected_mode == "medium": speed = 75 mode_ = "Medium" elif selected_mode == "hard": speed = 50 mode_ = "Hard" elif selected_mode == "dynamic": speed = 100 mode_ = "Dynamic" start_button.config(text=f"Start Game\n({mode_})") def go_to_start_screen(): global canvas, in_game, in_start_screen, after_id if in_game or not in_start_screen: # 取消定时器 if after_id: window.after_cancel(after_id) after_id = None canvas.pack_forget() canvas.delete(ALL) canvas.delete("snake") canvas.delete("food") label.destroy() start_screen() window = Tk() window.title("Snake Game") label = Label(window, text="", bg=BACKGROUND_COLOUR) label.pack() canvas = Canvas(window, bg=BACKGROUND_COLOUR, height=GAME_HEIGHT, width=GAME_WIDTH) window.bind("<Left>", lambda event: change_direction("left")) window.bind("<Right>", lambda event: change_direction("right")) window.bind("<Up>", lambda event: change_direction("up")) window.bind("<Down>", lambda event: change_direction("down")) window.bind("<a>", lambda event: change_direction("left")) window.bind("<d>", lambda event: change_direction("right")) window.bind("<w>", lambda event: change_direction("up")) window.bind("<s>", lambda event: change_direction("down")) start_screen() window.mainloop()
关键修改点说明
- 新增全局变量
after_id,保存window.after()返回的定时器ID - 在
next_turn中,将定时器ID赋值给after_id - 在
game_over、go_to_start_screen、start_game函数中,调用window.after_cancel(after_id)取消未执行的定时器,确保旧游戏循环完全停止
内容的提问来源于stack exchange,提问作者user27164932
相关产品推荐
相关产品推荐

