Smooth at Any Frame Rate
Part of the Sound and Game Feel section of Coddy's Pygame journey. Lesson 19 of 23.
x += (target_x - x) * 0.1 moves a tenth of the way per frame. At 60 frames a second that is 60 steps a second; on a slow computer that only manages 30, it is 30 steps, and the circle takes twice as long to arrive. Press F to see it: the game runs at 30 and everything eases slower.
The fix is to measure time. clock.tick() returns the milliseconds since the last frame. Divided by 1000 that is dt, the frame's length in seconds: about 0.017 at 60 frames a second, 0.033 at 30.
dt = 1 / 60 # before the loop
...
step = min(1, 6 * dt)
x += (target_x - x) * step
...
dt = clock.tick(fps) / 1000A longer frame takes a bigger step, so the circle covers the same ground in the same time at any frame rate. The 6 sets the speed: at 60 frames a second, 6 * dt is about 0.1, the same tenth as before. min(1, ...) stops a very long frame from stepping past the target.
The same goes for plain movement: player_x += 240 * dt is 240 pixels a second whatever the frame rate. Most small games fix their rate with tick(60) and never need this; use dt when the rate can change.
Challenge
MediumF switches the game between 60 and 30 frames a second. Make the easing take the same time at both:
- Make
dt = 1 / 60before the loop. - At the end of the loop, set
dt = clock.tick(fps) / 1000. - Move with
step = min(1, 6 * dt)instead of 0.1:x += (target_x - x) * step, and the same fory.
Try it yourself
import pygame
pygame.init()
screen = pygame.display.set_mode((480, 360))
pygame.display.set_caption("Any Frame Rate")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 28)
x, y = 240, 180
target_x, target_y = 240, 180
fps = 60
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
target_x, target_y = event.pos
elif event.type == pygame.KEYDOWN and event.key == pygame.K_f:
fps = 30 if fps == 60 else 60
x += (target_x - x) * 0.1
y += (target_y - y) * 0.1
screen.fill((30, 30, 46))
pygame.draw.circle(screen, (80, 200, 220), (x, y), 12)
label = font.render(f"{fps} FPS (F to change)", True, (240, 240, 240))
screen.blit(label, (10, 10))
pygame.display.flip()
clock.tick(fps)
pygame.quit()
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Sound and Game Feel
Practice on your own: Pygame playground