You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 17:23:30