Menu
CoddyTech

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) / 1000

A 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 icon

Challenge

Medium

F switches the game between 60 and 30 frames a second. Make the easing take the same time at both:

  1. Make dt = 1 / 60 before the loop.
  2. At the end of the loop, set dt = clock.tick(fps) / 1000.
  3. Move with step = min(1, 6 * dt) instead of 0.1: x += (target_x - x) * step, and the same for y.

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()
quiz iconTest yourself

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