Search code examples
pythonpython-3.xtkinterkeypress

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


As a novice when it comes to Python, I've tried programming my own game to start, with the advice of a guidebook. However, for this game, I'm trying to detect when a key is held down consistently instead of just pressed. The current code I'm using doesn't make the character move, and without the halt(self, evt) code being implemented, causes the ship to speed up uncontrollably after the button is held down for long enough.

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()

My goal is to have my character move at a constant rate (as opposed to uncontrollably fast after a while) when the respective key is pressed.


Solution

  • The most straightforward solution to your question would be to avoid adding a value at every keypress, but rather set a constant value.

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

    The movement would however lose its dynamic, but it will be constant. Otherwise, you could create a max value. Something like this:

    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
    

    Thereby forcing self.x to remain capped and constant if it has reached the max_val.