user9185513
user9185513

Reputation: 3

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.

Upvotes: 0

Views: 4002

Answers (2)

Jason Ngo
Jason Ngo

Reputation: 231

Holding down a key is essentially the same as pressing that key repeatedly. What you're doing by adding to/subtracting from the self.x/self.y attributes in your move_* functions is you're increasing the amount that the canvas will move your player sprite in each function call (e.g. from 1.5 to 3 to 4.5 to 6, etc. as you hold down a direcitonal key).

Since the canvas will be moving your player by (self.x, self.y) units every time "move" is called under the "PlayerSprite" class, we want self.x and self.y to be either 0 or whatever speed you desire (1.5 in the following code). So instead of adding to self.x and self.y, we should assign it to a constant value:

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

Also, instead of using "halt", what you could do is include 'KeyRelease-*' bindings to stop your player once you've stopped holding down a directional key:

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)

(I've generalized the left and right directions to horz as well as up and down to vert to save on the number of function definitions.)

Then you can create functions that assign your self.x value or self.y value to 0, so that your player doesn't move once "move" is called.

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

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

Upvotes: 0

pastaleg
pastaleg

Reputation: 1838

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.

Upvotes: 1

Related Questions