Search code examples
pythonturtle-graphicslisten

Turtle OnKey() doesn't work despite all I've tried


The Onkey() does'nt work though I've tried all I've come across. Please I really need help to fix this as soon as possible. Here i'm trying to build a game with turtle. Breakout game to be precise.

class Player(Turtle):
    def __init__(self):
        super().__init__()
        self.player = Turtle("square")
        self.player.shapesize(stretch_len=8, stretch_wid=1)
        self.player.penup()
        self.player.color("white")
        self.player.goto(0, -200)
        self.player.speed("fastest")

    def go_left(self):
        self.setheading(0)
        self.forward(10)
        # screen.update()

    def go_right(self):
        self.setheading(180)
        self.forward(30)
        # screen.update()

screen = Screen()
screen.bgcolor("black")
screen.setup(width=800, height=600)
screen.title("BREAKOUT GAME")
screen.tracer(0)

player = Player()
block = Block()
ball = Ball()

screen.update()
screen.listen()
screen.onkey(player.go_right, "Right")
screen.onkey(player.go_left, "Left")

Solution

  • I'm not a big fan of subclassing Turtle, which practically guarantees confusion as to which methods you're invoking, as is the case here. I suggest using composition rather than inheritance.

    The general debugging strategy is to minimize your problem systematically. If you add a print("here") in the go_ methods, you'll see they're called just fine on the corresponding arrow key presses.

    The next step is to determine if the screen is being updated. It's not, because screen.update() was commented out. This method is necessary whenever you use turtle.tracer(0) to disable the internal turtle loop. Bring these update calls back.

    After making that adjustment, player movement still isn't functional. A quick inspection of the class shows that most of the logic involves self.player = Turtle("square"), which is the turtle you see on the screen. This class is actually using composition and inheritance, so Player both is-a turtle (self) and has-a turtle (self.player). Pick one or the other and use it throughout, preferably the has-a turtle (self.player).

    Also, self.player.speed("fastest") is unnecessary since you disabled tracer and the player moves at different speeds in each direction.

    Here's the fixed code:

    from turtle import Screen, Turtle
    
    class Player:
        def __init__(self):
            self.player = Turtle("square")
            self.player.shapesize(stretch_len=8, stretch_wid=1)
            self.player.penup()
            self.player.color("white")
            self.player.goto(0, -200)
    
        def go_left(self):
            self.player.setheading(0)
            self.player.forward(30)
            screen.update()
    
        def go_right(self):
            self.player.setheading(180)
            self.player.forward(30)
            screen.update()
    
    screen = Screen()
    screen.bgcolor("black")
    screen.setup(width=800, height=600)
    screen.title("BREAKOUT GAME")
    screen.tracer(0)
    
    player = Player()
    #block = Block()
    #ball = Ball()
    
    screen.update()
    screen.listen()
    screen.onkey(player.go_right, "Right")
    screen.onkey(player.go_left, "Left")
    screen.exitonclick()
    

    Although this works, calling screen.update() in the event handlers is suboptimal. As you bring in more entities, you'll have to call this everywhere, and as we've seen, forgetting a call can lead to a bug. Once you're ready to implement continuous movement, I'd suggest using your own rendering/update loop that calls screen.update one time after updates have been applied to all entities. This allows you to press-and-hold keys to move the player padddle. See How to bind several key presses together in turtle graphics? for my suggested setup.