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
相关产品推荐
相关产品推荐

