如何在tkinter中处理重叠按键?
在用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
函数来实现。在这个定时器里,如果键是按下的状态,就移动角色。
这样一来,你就只需要关注键的当前状态(是按下还是抬起),而不再依赖状态变化(事件)了。而且,这样也很容易在定时器函数中加入其他需要持续进行的操作。