如何在tkinter中处理重叠按键?

1 投票
2 回答
53 浏览
提问于 2025-04-14 16:07

在用tkinter和Python制作《太空入侵者》游戏时,我遇到了一个问题。当我按住“a”键让角色向左移动,然后按空格键射击时,角色却停下来了,而不是继续向左移动。

我对如何解决这个问题一点头绪都没有,因为我对Python和tkinter都还很陌生。下面是能重现这个问题的最简代码:

import tkinter  

class Projectile():  
     def __init__(self, x, y) -> None:  
        self.x = x  
        self.y = y  

    def move(self) -> None:  
        self.y -= 7.5  

class Player():  
    def __init__(self, x, y) -> None:  
        self.x = x  
        self.y = y  

class World():  
    def __init__(self) -> None:  
        self.root = tkinter.Tk()  
        self.canvas = tkinter.Canvas(self.root, width=800, height=800, bg="black")  
        self.canvas.pack()  


class Game():  
    def __init__(self) -> None:  
        self.world = World()  
        self.player = Player(400, 700)  
        self.projectile_list = []  
        self.shooting_allowed = True  
        self.world.root.bind_all("<KeyPress-a>", self.move)  
        self.world.root.bind_all("<KeyPress-d>", self.move)  
        self.world.root.bind_all("<KeyPress-space>", self.shoot)  
        self.rendered_player = self.world.canvas.create_rectangle(self.player.x, self.player.y, self.player.x+20, self.player.y+20, fill="red")  

    def move(self, event) -> None:  
        if event.keysym == "a":  
            self.player.x -= 10  
        elif event.keysym == "d":  
            self.player.x += 10  
        if self.player.x+25 >= 800:  
            self.player.x -= 10  
        elif self.player.x-20 <= 0:  
            self.player.x += 10  
        self.world.canvas.coords(self.rendered_player, self.player.x, self.player.y, self.player.x+20, self.player.y+20)  

    def shoot(self, event) -> None:  
        x = self.player.x  
        y = self.player.y  
        if self.shooting_allowed:  
            if event.keysym == "space":  
                self.projectile_list.append([Projectile(x, y, "player"), self.world.canvas.create_rectangle(x, y, x+4, y+20, fill="red")])  

    def run(self) -> None:  
        for projectile in self.projectile_list:  
            projectile[0].move()  
            self.world.canvas.coords(projectile[1], projectile[0].x, projectile[0].y, projectile[0].x+4, projectile[0].y+20)  
        self.world.root.after(10, self.run)  

Game = Game()  
Game.run()  
Game.world.root.mainloop()

希望能得到你的帮助,

Luis

2 个回答

0

这个想法是同时检测多个按键。我觉得这里的方法非常聪明。如果我把他的想法用到你的程序里,整个代码会是:

import tkinter  

class Projectile():  
    def __init__(self, x, y) -> None:  
        self.x = x  
        self.y = y  

    def move(self) -> None:  
        self.y -= 7.5  

class Player():  
    def __init__(self, x, y) -> None:  
        self.x = x  
        self.y = y  

class World():  
    def __init__(self) -> None:  
        self.root = tkinter.Tk()  
        self.canvas = tkinter.Canvas(self.root, width=800, height=800)  
        self.canvas.pack()  

class Game():  
    def __init__(self) -> None:  
        self.world = World()  
        self.player = Player(400, 700)
        self.projectile_list = []  
        self.shooting_allowed = True  
        self.world.root.bind("<KeyPress>", self.keydown)
        self.world.root.bind("<KeyRelease>", self.keyup)
        self.rendered_player = self.world.canvas.create_rectangle(self.player.x, self.player.y, self.player.x+20, self.player.y+20, fill="red")  
        self.history = []

    def keyup(self, event):
        if event.char in self.history:
            self.history.pop(self.history.index(event.char))

    def keydown(self, event):
        if not event.char in self.history:
            self.history.append(event.char)
        self.movement()

    def movement(self) -> None:  # This 'detect' function need to run forever, to detect which key is pressed
        if 'a' in self.history:
            print("Go Left! (a is pressed)")
            # self.right()
            pass
        if 'd' in self.history:  
            print("Go Right! (d is pressed)")
            # self.left()
            pass
        if ' ' in self.history: # Space
            print("Shoot! (Space is pressed)")
            # self.shoot()
            pass

        #self.world.canvas.coords(self.rendered_player, self.player.x, self.player.y, self.player.x+20, self.player.y+20)  

    def shoot(self, event) -> None:  
        # Do something complicated.......
        pass
    
    def left(self):
        # Go Left
        pass

    def right(self):
        # Go Right
        pass

    def run(self) -> None:  
        pass

game = Game()  
game.run()  
game.world.root.mainloop()

在这段代码中,我创建了一个叫做'history'的列表。它记录你按下的所有按键。同时,我还定义了一个新的函数叫'movement',这个函数会检查'history'里有哪些按键,并执行相应的动作,比如射击或者向右移动。

1

你的脚本之所以能工作,其实是碰巧利用了键盘重复的“特性”。也就是说,当你按住一个键时,过了一会儿它会开始“重复”,这就意味着你会收到很多次按键事件。不过,这个特性是跟操作系统有关的,显然当你按下另一个键时,这个重复就会停止。而且,这也意味着在不同的电脑上,游戏的移动速度可能会不一样,因为每台电脑的重复间隔不同。

你可以看看这里的内容:Tkinter的按键按下和释放事件

正确的做法是“去抖动”或者禁用键盘重复的功能(可以参考上面提到的问题),然后自己来处理按键的重复。

  • 当按下某个键时,记住这个键现在是“按下”的状态。
  • 当释放这个键时,记住这个键是“抬起”的状态。
  • 设置一个定时器,每隔x毫秒运行一次。可以用after函数来实现。在这个定时器里,如果键是按下的状态,就移动角色。

这样一来,你就只需要关注键的当前状态(是按下还是抬起),而不再依赖状态变化(事件)了。而且,这样也很容易在定时器函数中加入其他需要持续进行的操作。

撰写回答