Python game encountering random lag spikes

Viewed 57

For the first time ever, I tried my hand at creating a pretty simple game, pong. I did this using turtle and figured it out by using a mixture of searching online, previous knowledge, and some stack overflow questions, and for the most part, it's working perfectly. However, when running the game, I seem to encounter the occasional lag spike where the game will slow down by nearly 50%, and then all of a sudden go to 150%. I have a decent laptop, but even so, for a game this size, this shouldn't be happening, any idea why? The audio also seems to be delayed slightly too.

If you want to try it with the font + audio files: https://github.com/JamesJ42/PythonPong

# My attempt at pong (atari game)

import turtle
import winsound

wn = turtle.Screen()
wn.title("Pong")
wn.bgcolor("black")
wn.setup(width = 800, height = 600)
wn.tracer(0)

# Score
player_1_score = 0
player_2_score = 0

# Paddle A
paddle_a = turtle.Turtle()
paddle_a.speed(0)
paddle_a.shape("square")
paddle_a.color("white")
paddle_a.shapesize(stretch_wid = 5, stretch_len = 1)
paddle_a.penup()
paddle_a.goto(-350, 0)

# Paddle B
paddle_b = turtle.Turtle()
paddle_b.speed(0)
paddle_b.shape("square")
paddle_b.color("white")
paddle_b.shapesize(stretch_wid = 5, stretch_len = 1)
paddle_b.penup()
paddle_b.goto(350, 0)

# Ball
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
ball.penup()
ball.goto(0, 0)
ball.dx = 0.1
ball.dy = 0.1

# Pen
pen = turtle.Turtle()
pen.speed(0)
pen.color("white")
pen.penup()
pen.hideturtle()
pen.goto(0, 260)
pen.write("PLAYER 1: 0  PLAYER 2: 0", align = "center", font = ("EightBit Atari", 24, "normal"))

# Finish game
finish = turtle.Turtle()
finish.speed(0)
finish.color("white")
finish.penup()
finish.hideturtle()
finish.goto(0, 240)
finish.write("FINISH GAME: SHIFT + Q", align = "center", font = ("EightBit Atari", 10, "normal"))


# Functions
def paddle_a_up():
    y = paddle_a.ycor()
    y += 20
    paddle_a.sety(y)

def paddle_a_down():
    y = paddle_a.ycor()
    y -= 20
    paddle_a.sety(y)

def paddle_b_up():
    y = paddle_b.ycor()
    y += 20
    paddle_b.sety(y)

def paddle_b_down():
    y = paddle_b.ycor()
    y -= 20
    paddle_b.sety(y)

def exit_game():
    turtle.bye()
    
# Keyboard binding
wn.listen()
wn.onkeypress(paddle_a_up, "w")
wn.onkeypress(paddle_a_down, "s")
wn.onkeypress(paddle_b_up, "Up")
wn.onkeypress(paddle_b_down, "Down")
wn.onkeypress(exit_game, "Q")

# Main game loop
while True:
    wn.update()

    # Move the ball
    ball.setx(ball.xcor() + ball.dx)
    ball.sety(ball.ycor() + ball.dy)

    # Border checking
    if ball.ycor() > 290:
        ball.sety(290)
        ball.dy *= -1
        winsound.PlaySound("wall.wav", winsound.SND_ASYNC)

    if ball.ycor() < -290:
        ball.sety(- 290)
        ball.dy *= -1
        winsound.PlaySound("wall.wav", winsound.SND_ASYNC)

    if ball.xcor() > 390:
        ball.goto(0, 0)
        ball.dx *= -1
        player_1_score += 1
        pen.clear()
        pen.write(f"PLAYER 1: {player_1_score}  PLAYER 2: {player_2_score}", align = "center", font = ("EightBit Atari", 24, "normal"))
        winsound.PlaySound("score.wav", winsound.SND_ASYNC)
        
    if ball.xcor() < -390:
        ball.goto(0, 0)
        ball.dx *= -1
        player_2_score += 1
        pen.clear()
        pen.write(f"PLAYER 1: {player_1_score}  PLAYER 2: {player_2_score}", align = "center", font = ("EightBit Atari", 24, "normal"))
        winsound.PlaySound("score.wav", winsound.SND_ASYNC)

    # Paddle and ball collisions
    if (ball.xcor() > 340 and ball.xcor() < 350) and (ball.ycor() < paddle_b.ycor() + 40 and ball.ycor() > paddle_b.ycor() - 40):
        ball.setx(340)
        ball.dx *= -1
        winsound.PlaySound("paddle.wav", winsound.SND_ASYNC)

    if (ball.xcor() < -340 and ball.xcor() > -350) and (ball.ycor() < paddle_a.ycor() + 40 and ball.ycor() > paddle_a.ycor() - 40):
        ball.setx(-340)
        ball.dx *= -1
        winsound.PlaySound("paddle.wav", winsound.SND_ASYNC)
2 Answers

I found some Pygame documentation on my computer. It had some advice from David Clark, where he suggested you add .convert_alpha() at the end of all Pygame image loads. This increased my framerate from 9 to 32

or even try using Pyglet

I wasn't able to reproduce the lag spike problem on my slow laptop, but I'm guessing it's due to the while True: busy loop and the tiny step size here:

ball.dx = 0.1
ball.dy = 0.1

What seems to be happening is the render loop is running (or trying to run) as fast as possible, moving the ball a very small amount on each step. On a fast machine, this achieves the correct feel for the game speed, but on slower machines (like mine), the game runs at a snail's pace (it takes 10 seconds or so to get the ball across the screen once).

Although it's not likely to be perfectly consistent for all machines, if you use ontimer and a faster movement speed for the ball, you can take control of the framerate and ensure the process isn't just maxing out all of its CPU time trying to render as many frames as possible. Hopefully, this will reduce lag spikes that might be caused by periodic scheduling anomalies and interruptions.

# ...
ball.dx = 7  # bigger step per frame
ball.dy = 7
# ...

# Main game loop
def tick():
    # not the greatest practice; TODO convert to a function
    global player_1_score
    global player_2_score

    # Move the ball

    # ...

    wn.update()
    wn.ontimer(tick, frame_delay_ms)

frame_delay_ms = 1000 // 30
tick()
turtle.mainloop()

If this runs abnormally fast or slow on your machine, I'd be curious to hear. It can be improved to render frames at a fixed rate by checking the delta time between frames, but I'll assume it's good enough until I hear otherwise.

Related