如何检测 Tkinter 中是否按下了某个键?

How to detect if a key is being held down in Tkinter?

作为 Python 的新手,我曾尝试编写自己的游戏以开始,并参考了指南的建议。但是,对于这个游戏,我试图检测何时持续按住某个键而不是仅仅按下。我正在使用的当前代码不会让角色移动,并且如果没有实现 halt(self, evt) 代码,会导致飞船在按下按钮足够长的时间后不受控制地加速。

from tkinter import *
import random
import time

class Game:
    def __init__(self):
        self.tk = Tk()
        self.tk.title("Shooter")
        self.tk.resizable(0, 0)
        self.tk.wm_attributes("-topmost", 1)
        self.canvas = Canvas(self.tk, width=500, height=1000, highlightthickness=0)
        self.canvas.pack()
        self.tk.update()
        self.canvas_height = 1000
        self.canvas_width = 500
        self.bg = PhotoImage(file="background.gif")
        w = self.bg.width()
        h = self.bg.height()
        for x in range(0, 5):
            for y in range(0, 10):
                self.canvas.create_image(x * w, y * h, \
                        image=self.bg, anchor='nw')
        self.sprites = []
        self.running = True

    def mainloop(self):
        while 1:
            if self.running == True:
                for sprite in self.sprites:
                    sprite.move()
            self.tk.update_idletasks()
            self.tk.update()
            time.sleep(0.01)

class Coords:
    def __init__(self, x1=0, y1=0, x2=0, y2=0):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

class Sprite:
    def __init__(self, game):
        self.game = game
        self.endgame = False
        self.coordinates = None
    def move(self):
        pass
    def coords(self):
        return self.coordinates

class PlayerSprite(Sprite):
    def __init__(self, game):
        Sprite.__init__(self, game)
        self.renderimage = [
            PhotoImage(file="player_1.gif"),
            PhotoImage(file="player_2.gif"),
            PhotoImage(file="player_3.gif"),
            PhotoImage(file="player_4.gif"),
        ]
        self.image = game.canvas.create_image(225, 900, \
                image=self.renderimage[0], anchor='nw')
        self.x = 0
        self.y = 0
        self.velx = 0
        self.current_image = 0
        self.current_image_add = 1
        self.shoot_timer = 0
        self.last_time = time.time()
        self.coordinates = Coords()
        x_move = None
        y_move = None
        game.canvas.bind_all('<KeyPress-Left>', self.move_left)
        game.canvas.bind_all('<KeyPress-Right>', self.move_right)
        game.canvas.bind_all('<KeyPress-Up>', self.move_up)
        game.canvas.bind_all('<KeyPress-Down>', self.move_down)
        game.canvas.bind_all('<KeyPress-Left>', self.halt)
        game.canvas.bind_all('<KeyPress-Right>', self.halt)
        game.canvas.bind_all('<KeyPress-Up>', self.halt)
        game.canvas.bind_all('<KeyPress-Down>', self.halt)
        game.canvas.bind_all('<space>', self.shoot)

    def move_left(self, evt):
        x_move = self.x - 1.5
        self.x = x_move

    def move_right(self, evt):
        x_move = self.x + 1.5
        self.x = x_move

    def move_up(self, evt):
        y_move = self.y - 1.5
        self.y = y_move

    def move_down(self, evt):
        y_move = self.y + 1.5
        self.y = y_move

    def halt(self, evt):
        time.sleep(0.01)
        if x_move < 0:
            x_move = -1.5
        elif x_move > 0:
            x_move = 1.5
        elif y_move < 0:
            y_move = -1.5
        elif y_move > 0:
            y_move = 1.5

    def shoot(self, evt):
        print("Placeholder")

    def move(self):
        self.game.canvas.move(self.image, self.x, self.y)

    def coords(self):
        xy = self.game.canvas.coords(self.image)
        self.coordinates.x1 = xy[0]
        self.coordinates.y1 = xy[1]
        self.coordinates.x2 = xy[0] + 24
        self.coordinates.y2 = xy[1] + 32
        return self.coordinates

g = Game()
sp = PlayerSprite(g)
g.sprites.append(sp)
g.mainloop()

我的目标是让我的角色在按下相应的键时以恒定的速度移动(而不是在一段时间后不受控制地快速移动)。

对于您的问题,最直接的解决方案是避免在每次按键时都添加一个值,而是设置一个常量值。

def move_left(self, evt):
    x_move = -5
    self.x = x_move

运动虽然会失去动力,但会保持不变。否则,您可以创建最大值。像这样:

def move_left(self, evt):
    int max_val_left = -10
    if( self.x < max_val_left):
        x_move = self.x - 1.5
        self.x = x_move

从而迫使 self.x 在达到 max_val 时保持上限并保持不变。

按住一个键与重复按下该键本质上是一样的。通过在你的 move_* 函数中添加来自 self.x/self.y 属性的 to/subtracting 你正在做的是增加 canvas 将在每个中移动你的玩家精灵的数量函数调用(例如,从 1.5 到 3 到 4.5 到 6,等等,当您按住方向键时)。

因为每次在 "PlayerSprite" 下调用 "move" 时 canvas 都会将玩家移动 (self.x, self.y) 个单位 class,我们希望 self.x 和 self.y 为 0 或您想要的任何速度(以下代码中的 1.5)。因此,我们应该将其分配给一个常量值,而不是添加到 self.x 和 self.y:

def move_left(self, evt):
    self.x = -1.5

def move_right(self, evt):
    self.x = 1.5

def move_up(self, evt):
    self.y = -1.5

def move_down(self, evt):
    self.y = -1.5

此外,您可以不使用 "halt",而是包含 'KeyRelease-*' 绑定,以便在您停止按住方向键后停止播放器:

game.canvas.bind_all('KeyRelease-Left'>, self.stop_horz_move)
game.canvas.bind_all('KeyRelease-Right'>, self.stop_horz_move)
game.canvas.bind_all('KeyRelease-Up'>, self.stop_vert_move)
game.canvas.bind_all('KeyRelease-Down'>, self.stop_vert_move)

(我已经将左右方向概括为水平,将上下方向概括为垂直,以节省函数定义的数量。)

然后您可以创建将 self.x 值或 self.y 值分配给 0 的函数,这样您的玩家在调用 "move" 后就不会移动。

def stop_move_horz(self, evt):
    self.x = 0

def stop_move_vert(self, evt):
    self.y = 0