Search code examples
pythongame-physicsgame-developmentturtle-graphicspython-turtle

How to make a turtle jump in a game?


I created a code to make my game character jump. I want to input this jump function into my game.

For the code to not be limited to a particular coordinate, I defined o as a variable to hold the original y coordinate before jumping. In my game, I will be at multiple different y coordinates, but if I define it outside I will only be able to jump at a fixed place again. How do I fix this?

import turtle

dx = 1
dy = 1.8
mdy = -1.6

img = r"C:\Users\user\Desktop\python\pg\dragon.gif"
turtle.register_shape(img)

scrn = turtle.Screen()
scrn.tracer(0)

player = turtle.Turtle()
player.penup()
player.shape(img)
player.speed(10)
player.goto(100,-142)
scrn.update()

o=player.ycor() #original y coordinate

def jump():
    global dy
    ycor = player.ycor()
    if ycor > o:
        player.sety(ycor + dy)

    if ycor > o+100:
        dy = mdy
    else:
        pass

def jump_handler():
    if player.ycor() <= o+0.1:
        global dy
        dy = 1.8
        player.sety(o+0.1)
        
    else:
        pass

# Set up the event handler for the jump
scrn.listen()
scrn.onkey(jump_handler, "space")

while True:
    scrn.update()
    jump()

I tried putting it in the function but that wouldn't work, nor does it work in a while loop. I'm unsure as to how to keep it as the original y coordinate without it getting changed during the function.


Solution

  • I'm not sure if your design will work. There's no need to keep track of the original position. Jumping is usually implemented as follows:

    • Keep a velocity variable for the y-axis (I prefer vy but dy "direction y" works too).
    • On every frame, subtract a small gravity amount from the player's velocity.
    • On every frame, if the player has pressed space, add a large upward velocity. Optionally, ensure the player is touching the ground or a platform before jumping.
    • Clamp the velocity to avoid the player falling too quickly.

    Generally, in most real-time applications, changes to entity positions should never be done outside of the main update loop. This avoids strange OS-specific retrigger behavior.

    Make sure to use ontimer rather than while True: to run your loop so that it's stable from one machine to the next. (It'd probably be best to integrate a delta time, but for simplicity, I've omitted it).

    Here's a quick proof of concept:

    from turtle import Screen, Turtle
    
    
    def on_space_pressed():
        global space_pressed
        space_pressed = True
    
    
    def on_space_released():
        global space_pressed
        space_pressed = False
    
    
    def tick():
        global vy
    
        if space_pressed and player.ycor() <= ground:
            vy = jump_velocity
            player.sety(player.ycor() + 1)
    
        vy -= gravity
        vy = max(min_velocity, vy)
        player.sety(player.ycor() + vy)
    
        if player.ycor() <= ground:
            player.sety(ground)
            vy = 0
    
        screen.update()
        screen.ontimer(tick, 1000 // 60)
    
    
    vx = 0
    vy = 0
    ground = -100
    min_velocity = -25
    jump_velocity = 25
    gravity = 1
    
    screen = Screen()
    screen.tracer(0)
    
    space_pressed = False
    screen.onkeypress(on_space_pressed, "space")
    screen.onkeyrelease(on_space_released, "space")
    screen.listen()
    
    player = Turtle()
    player.penup()
    player.turtlesize(2, 2)
    player.shape("square")
    
    tick()
    screen.exitonclick()
    

    Going a step further and adding horizontal movement might look like:

    import time
    from turtle import Screen, Turtle
    
    
    last_time = time.perf_counter()
    def tick():
        global vx, vy, last_time
    
        curr_time = time.perf_counter()
        delta = curr_time - last_time
        last_time = curr_time
    
        if "space" in keys_pressed and player.ycor() <= ground:
            vy = jump_velocity
            player.sety(player.ycor() + 1)
    
        vy -= gravity * delta
        vy = max(min_velocity, vy)
        player.sety(player.ycor() + vy)
    
        if player.ycor() <= ground:
            player.sety(ground)
            vy = 0
    
        if "Left" in keys_pressed:
            vx -= movement_velocity * delta
    
        if "Right" in keys_pressed:
            vx += movement_velocity * delta
    
        player.setx(player.xcor() + vx)
        vx *= friction
        screen.update()
        screen.ontimer(tick, 1000 // 60)
    
    
    vx = 0
    vy = 0
    ground = -100
    friction = 0.8
    min_velocity = -25
    movement_velocity = 150
    jump_velocity = 25
    gravity = 50
    
    screen = Screen()
    screen.tracer(0)
    screen.listen()
    
    def bind(key):
        screen.onkeypress(lambda: keys_pressed.add(key), key)
        screen.onkeyrelease(lambda: keys_pressed.remove(key), key)
    
    keys = "space", "Left", "Right"
    keys_pressed = set()
    
    for key in keys:
        bind(key)
    
    player = Turtle()
    player.penup()
    player.turtlesize(2, 2)
    player.shape("square")
    
    tick()
    screen.exitonclick()
    

    There are plenty of improvements to make when the program grows, like avoiding loose global variables (move them into a Player class and drop the global keyword) and adding functions, left as an exercise to keep the post reasonably scoped.