016.7 ms33.3 ms1while running:2 dt = clock.tick(60)3 read_input()4 update(dt / 1000)5 draw()6 flip()60 fps30 fps
Back: the loop wheel (input, update, draw) and the two buffers (back buffer, screen)Front: the loop in Python, the frame-time bar, and the 60 fps and 30 fps lanesMovers: the frame token, the balls, and the time marker on the barThe loop wheel at the back left: a frame token rides from Read input to Update to Draw and round again, one trip per frame. Draw paints into the hidden back buffer; flip swaps it with the screen. The frame-time bar shows each trip against the 16.7 ms budget of a 60 Hz screen; a late frame waits for the 33.3 ms refresh. On the lanes, moving 4 pixels a frame leaves the 30 fps ball behind; moving speed × dt brings both balls to 240 pixels in one second. The phase times on the frame-time bar (input 1 ms, update 3 ms, draw 8 ms) are a made-up example to show the budget. Real games vary a lot, and drawing is often handed to the graphics chip (GPU).

The loop

  1. A game is one loop that never stops: read the player's input, update the world, draw a picture. One trip round the loop makes one frame, a single still picture. Shown fast enough, frames look like smooth motion.

The frame budget

Time and motion

What happens

;; step 1/11 · loop
game loop   repeat forever:
  1  read input     what did the player do?
  2  update         move the world on a little
  3  draw           make one picture (a frame)
one trip round = one frame
about 60 trips a second look like smooth motion

The phase times on the frame-time bar (input 1 ms, update 3 ms, draw 8 ms) are a made-up example to show the budget. Real games vary a lot, and drawing is often handed to the graphics chip (GPU). Without vsync a new frame can replace the old one halfway through a screen refresh, which shows as a horizontal tear. With vsync a late frame waits a whole refresh instead, which shows as a stutter.