基于tkinter与OOP实现的Space Invader子弹击中后程序冻结问题排查
Space Invaders子弹击中敌人后程序冻结的问题修复
问题根源
程序冻结的核心原因是在遍历列表的同时直接修改列表结构:
- 在
game_loop中遍历self.enemies时调用self.enemies.remove(enemy),遍历self.bullets时调用self.bullets.remove(bullet),这会破坏迭代器的正常遍历流程,引发隐性异常导致tkinter事件循环阻塞。 - 子弹移出屏幕后,仅删除了Canvas上的图形元素,但未从
self.bullets列表中移除对象,导致无效对象堆积,进一步加剧逻辑混乱。
修复后的完整代码
import tkinter as tk import random class Game: def __init__(self, master): self.master = master self.master.title("Space Invaders") self.master.resizable(False, False) self.canvas = tk.Canvas(master, width=600, height=600, bg="black") self.canvas.pack() self.player = Player(self.canvas) self.enemies = [Enemy(self.canvas) for _ in range(5)] self.bullets = [] self.master.bind("<Left>", self.move_left) self.master.bind("<Right>", self.move_right) self.master.bind("<space>", self.shoot) self.game_loop() def move_left(self, event): self.player.move(-20) def move_right(self, event): self.player.move(20) def shoot(self, event): bullet = Bullet(self.canvas, self.player.x, self.player.y) self.bullets.append(bullet) def game_loop(self): # 遍历敌人列表的副本,避免遍历原列表时修改结构 for enemy in list(self.enemies): enemy.move() # 遍历子弹列表的副本 for bullet in list(self.bullets): if bullet.check_collision(enemy): self.enemies.remove(enemy) self.canvas.delete(enemy.id) self.bullets.remove(bullet) self.canvas.delete(bullet.id) break # 避免同一子弹触发多次碰撞 if self.player.check_collision(enemy): self.canvas.delete("all") self.canvas.create_text(300, 300, text="Game Over", fill="white", font=("Helvetica", 36)) return # 处理子弹移动和屏幕外清理 for bullet in list(self.bullets): bullet.move() # 检查子弹是否移出屏幕,若已移出则从列表删除 if self.canvas.coords(bullet.id)[1] < 0: self.bullets.remove(bullet) self.canvas.delete(bullet.id) self.canvas.update() self.master.after(100, self.game_loop) class Player: def __init__(self, canvas): self.canvas = canvas self.id = canvas.create_rectangle(275, 550, 325, 600, fill="blue") self.x = 300 self.y = 575 def move(self, dx): self.canvas.move(self.id, dx, 0) self.x += dx def check_collision(self, other): x1, y1, x2, y2 = self.canvas.coords(self.id) other_coords = other.get_coords() return (x1 < other_coords[2] and x2 > other_coords[0]) and (y1 < other_coords[3] and y2 > other_coords[1]) class Enemy: def __init__(self, canvas): self.canvas = canvas self.id = canvas.create_oval(275, 25, 325, 75, fill="red") self.x = random.randint(0, 550) self.y = 50 self.dx = random.choice([-20, 20]) def move(self): self.canvas.move(self.id, self.dx, 0) self.x += self.dx if self.x >= 550 or self.x <= 0: self.dx *= -1 def get_coords(self): return self.canvas.coords(self.id) class Bullet: def __init__(self, canvas, x, y): self.canvas = canvas self.id = canvas.create_rectangle(x, y, x + 5, y + 10, fill="yellow") self.y_speed = -20 def move(self): self.canvas.move(self.id, 0, self.y_speed) def check_collision(self, other): x1, y1, x2, y2 = self.canvas.coords(self.id) other_coords = other.get_coords() return (x1 < other_coords[2] and x2 > other_coords[0]) and (y1 < other_coords[3] and y2 > other_coords[1]) if __name__ == "__main__": root = tk.Tk() game = Game(root) root.mainloop()
关键修改点:
- 遍历
self.enemies和self.bullets时使用list()创建列表副本,避免遍历原列表时修改结构。 - 子弹碰撞后添加
break,防止同一子弹触发多次碰撞逻辑。 - 单独处理子弹移出屏幕的逻辑,从
self.bullets列表中移除无效子弹对象。 - 移除Bullet类
move方法中的删除逻辑,统一在game_loop中处理,确保列表和Canvas元素同步。
长期优化方案
- 列表遍历规范:永远不要在遍历原列表的同时修改列表结构,优先使用副本遍历,或者先收集需要删除的元素,再批量移除。
- 对象生命周期管理:为动态创建的对象(子弹、敌人)建立明确的创建/销毁流程,确保内存中只保留有效对象,避免逻辑混乱和内存泄漏。
- 碰撞检测优化:将碰撞检测逻辑封装为独立工具函数,或使用空间划分(如按行/列分组)减少每次需要检测的对象数量,提升游戏运行效率。
- 异常捕获机制:在tkinter主循环中添加全局异常捕获,避免小错误直接导致程序冻结,便于调试定位问题。
内容的提问来源于stack exchange,提问作者jayash patnaik
相关产品推荐
相关产品推荐

