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

Tkinter太空侵略者游戏:激光随空格键按压次数加速问题求助

问题:持续按空格键发射激光时速度不断加快

我正在开发一款太空侵略者风格的课程项目游戏,遇到一个技术问题:每次持续按空格键发射激光时,激光速度会不断加快,无法维持初始设定值。我考虑单独创建Laser类,但不确定是否能解决问题,以下是我的代码:

from tkinter import *
from PIL import ImageTk, Image
import random

def main():
    screen = Tk()

    width = screen.winfo_screenwidth()
    height = screen.winfo_screenheight()
    num_allies = 4
    x_spawn_coords = [120, 340, 560, 780, 1000]

    class MovingShip:
        def __init__(self, canvas, ship_image_path, bg_image_path, laser_img_path, window):
            self.window = window
            self.canvas = canvas
            self.background_image = Image.open(bg_image_path)
            self.resized_background = self.background_image.resize((int(self.background_image.width * 0.6), int(self.background_image.height * 0.6)))
            self.background_photo = ImageTk.PhotoImage(self.resized_background)
            self.background_identifier = self.canvas.create_image(0, 0, anchor = 'nw', image = self.background_photo)
            self.ship_image = PhotoImage(file = ship_image_path)
            self.resized_ship_image = self.ship_image.subsample(5, 5)  # Resizing the image
            self.ship_identifier = self.canvas.create_image(560, 535, image = self.resized_ship_image)
            self.laser_image = Image.open(laser_img_path) 
            self.laser_image = self.laser_image.resize((25, 50))  # Replace desired_width and desired_height with your preferred dimensions
            self.laser_photo = ImageTk.PhotoImage(self.laser_image)
            

            self.canvas.bind('<Right>', lambda event: self.move_ship(10, 0))
            self.canvas.bind('<Left>', lambda event: self.move_ship(-10, 0))
            self.canvas.bind('<Down>', lambda event: self.move_ship(0, 10))
            self.canvas.bind('<Up>', lambda event: self.move_ship(0, -10))
            self.canvas.bind('<s>', lambda event: self.move_ship(0, 10))
            self.canvas.bind('<w>', lambda event: self.move_ship(0, -10))
            self.canvas.bind('<a>', lambda event: self.move_ship(-10, 0))
            self.canvas.bind('<d>', lambda event: self.move_ship(10, 0))
            self.canvas.bind('<space>', self.shoot)

        def move_ship(self, dx, dy):
            x, y = self.canvas.coords(self.ship_identifier)
            new_x = x + dx
            new_y = y + dy

            # Check if the new position is within the canvas boundaries
            if 45 <= new_y <= 540 and 30 <= new_x <= 1120:
                self.canvas.move(self.ship_identifier, dx, dy)

        def move_laser(self):
            self.canvas.move(self.laser_identifier, 0, -10)
            self.laser_loop = self.window.after(10, self.move_laser)

        
        def shoot(self, event):
            self.ship_boundary = self.canvas.bbox(self.ship_identifier)
            self.tip_of_ship = (self.ship_boundary[0] + self.ship_boundary[2]) / 2
            self.centre_of_ship = self.ship_boundary[1]
            self.laser_identifier = self.canvas.create_image(self.tip_of_ship, self.centre_of_ship, image = self.laser_photo)
            self.move_laser()


    class MovingAllies():
        ally_height = 60
        ally_width = 85

        def __init__(self, canvas, image_path, x, y, dy, custom_number = None):
            self.canvas = canvas
            self.ally_image = PhotoImage(file = image_path)
            self.resized_ally_image = self.ally_image.subsample(5, 5)  # Resizing the image
            self.ally_identifier = None
            self.x = x
            self.start_x = x  # Store the starting x-coordinate
            self.y = y
            self.dy = dy
            self.start_y = y  # Store the starting y-coordinate

            self.number_label = Label(canvas, font = ('Arial', 12, 'bold'), fg = 'white', bg='black')

            if custom_number is not None:
                self.number = custom_number
            else:
                self.number = random.randint(1, 99)

        def spawn_ally(self):
            if len(x_spawn_coords) == 0:
                x_spawn_coords.extend([120, 340, 560, 780, 1000])
            random.shuffle(x_spawn_coords)
            x_coord = x_spawn_coords.pop(0)
            self.x = x_coord
            self.ally_identifier = self.canvas.create_image(x_coord, self.start_y, image = self.resized_ally_image)

            self.number_label.config(text=str(self.number))
            self.number_label.place(x = self.x - 12, y=self.start_y - 50)

        def move_ally(self):
            if self.ally_identifier is None:
                self.spawn_ally()

            x, y = self.canvas.coords(self.ally_identifier)
            if 0 <= y <= 570 and 0 <= x <= 1100:
                self.canvas.move(self.ally_identifier, 0, self.dy)
                self.number_label.place(x=self.x - 12, y = y - 50)
            else:
                self.canvas.delete(self.ally_identifier)
                self.ally_identifier = None
                self.number_label.place_forget()
                self.spawn_ally()

    def open_game():
        game_screen = Toplevel(screen)
        game_screen.geometry("%dx%d" % (width, height))
        game_screen.configure(bg = 'light grey')
        game_canvas = Canvas(game_screen, width = 900, height = 570, highlightthickness = 2, highlightbackground = "light grey")
        game_canvas.place(x = 0, y = 0, relwidth = 0.9, relheight = 0.8)
        ship = MovingShip(game_canvas, "spaceinvadership.png", "spacebg2.jpg", "bullet.png", game_screen)
        start_coords = 50

        ally1 = MovingAllies(game_canvas, "enemyimg.png", None, start_coords, 0.8)
        ally2 = MovingAllies(game_canvas, "enemyimg.png", None, start_coords, 0.8)
        ally3 = MovingAllies(game_canvas, "enemyimg.png", None, start_coords, 0.8)
        ally4 = MovingAllies(game_canvas, "enemyimg.png", None, start_coords, 0.8)
        enemy = MovingAllies(game_canvas, "enemyimg.png", None, start_coords, 0.8, custom_number = 50)

        def move_allies():
            ally1.move_ally()
            ally2.move_ally()
            ally3.move_ally()
            ally4.move_ally()
            enemy.move_ally()
            game_screen.after(10, move_allies)

        move_allies()

        game_canvas.focus_set()  # Set focus to the canvas to capture key events
        game_screen.mainloop()

    def open_intructions():
        instructions_screen = Toplevel(screen)

    def open_difficulty():
        difficulty_level_screen = Toplevel(screen)

    # Main screen
    label = Label(screen, text = "Mathematical Miseries", font = ('Helvetica', 35, 'bold'))
    label.place(relx=0.5, rely=0.1, anchor=CENTER)

    play_game_button = Button(screen, text = "Play Game", command = open_game, font = ("Comic Sans", 30), fg = "grey",
                              bg = "white", activeforeground = "grey", activebackground = "white", state = ACTIVE)
    play_game_button.place(relx = 0.5, rely = 0.35, anchor = CENTER)

    instructions_button = Button(screen, text = "Instructions For Game", command = open_intructions,
                                 font = ("Comic Sans", 30), fg = "grey", bg = "white", activeforeground = "grey", activebackground = "white", state = ACTIVE)
    instructions_button.place(relx = 0.5, rely = 0.57, anchor = CENTER)

    difficulty_button = Button(screen, text = "Difficulty Level", command = open_difficulty, font = ("Comic Sans", 30),
                               fg = "grey", bg = "white", activeforeground = "grey", activebackground = "white", state = ACTIVE)
    difficulty_button.place(relx = 0.5, rely = 0.79, anchor = CENTER)

    screen.geometry('800x500')

    screen.mainloop()

main()

问题原因分析

问题出在MovingShip类的shoot和move_laser方法中:

  • 每次按空格键时,shoot方法会创建新的激光,但如果之前的激光还存在,新的move_laser调用会叠加到同一个self.laser_identifier上,导致同一个激光被多个after循环同时驱动,移动速度自然翻倍/加速。
  • 当前代码没有管理激光的生命周期,激光移出画布后仍会继续执行move_laser循环,造成资源浪费。

修复方案

方案1:临时修复现有代码

在shoot方法中,先停止之前的激光循环,再创建新激光:

class MovingShip:
    def __init__(self, canvas, ship_image_path, bg_image_path, laser_img_path, window):
        # 原有初始化代码...
        self.laser_loop = None  # 新增:存储激光的after循环ID

    def move_laser(self):
        x, y = self.canvas.coords(self.laser_identifier)
        if y > 0:  # 激光在画布内才继续移动
            self.canvas.move(self.laser_identifier, 0, -10)
            self.laser_loop = self.window.after(10, self.move_laser)
        else:  # 激光移出画布,删除并终止循环
            self.canvas.delete(self.laser_identifier)
            self.laser_loop = None

    def shoot(self, event):
        # 如果已有激光在移动,先停止循环
        if self.laser_loop is not None:
            self.window.after_cancel(self.laser_loop)
            self.canvas.delete(self.laser_identifier)
        
        self.ship_boundary = self.canvas.bbox(self.ship_identifier)
        self.tip_of_ship = (self.ship_boundary[0] + self.ship_boundary[2]) / 2
        self.centre_of_ship = self.ship_boundary[1]
        self.laser_identifier = self.canvas.create_image(self.tip_of_ship, self.centre_of_ship, image = self.laser_photo)
        self.move_laser()

方案2:创建独立Laser类(推荐)

单独创建Laser类可以更好地管理每个激光的状态和生命周期,支持同时发射多个激光,代码结构更清晰:

在main函数内添加Laser类:

class Laser:
    def __init__(self, canvas, x, y, laser_photo, window, speed=-10, interval=10):
        self.canvas = canvas
        self.window = window
        self.speed = speed
        self.interval = interval
        self.laser_id = canvas.create_image(x, y, image=laser_photo)
        self.loop_id = None
        self.start_move()

    def move(self):
        x, y = self.canvas.coords(self.laser_id)
        if y > 0:  # 激光未移出画布
            self.canvas.move(self.laser_id, 0, self.speed)
            self.loop_id = self.window.after(self.interval, self.move)
        else:  # 清理激光
            self.canvas.delete(self.laser_id)
            if self.loop_id:
                self.window.after_cancel(self.loop_id)

    def start_move(self):
        self.move()

然后修改MovingShip类,用列表存储所有激光实例:

class MovingShip:
    def __init__(self, canvas, ship_image_path, bg_image_path, laser_img_path, window):
        # 原有初始化代码...
        self.lasers = []  # 存储所有激光实例

    # 移除原有move_laser方法
    def shoot(self, event):
        self.ship_boundary = self.canvas.bbox(self.ship_identifier)
        tip_x = (self.ship_boundary[0] + self.ship_boundary[2]) / 2
        tip_y = self.ship_boundary[1]
        # 创建新激光并加入列表
        new_laser = Laser(self.canvas, tip_x, tip_y, self.laser_photo, self.window)
        self.lasers.append(new_laser)

这种方式不仅解决了速度加快的问题,还支持同时发射多束激光,后续添加碰撞检测等功能也更方便。

内容的提问来源于stack exchange,提问作者Avinash Rajaram

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 14:54:52