Moving things
Implement the full game loop and use it to animate a rectangle across the screen by updating its position each frame.
- Implement a complete game loop with input, update, and render phases
- Draw a rectangle on screen using pygame.draw.rect
- Update a position variable each frame to produce smooth motion
- Enforce a frame rate with pygame.time.Clock
The game loop is not a concept you understand by reading — you understand it by
writing one. This lesson adds the missing pieces: the while loop, the clock,
position state, and drawing. By the end, a rectangle will move steadily across
the screen because you update its position every frame.
Position is state. The rectangle has an x and a y. Each pass through the
loop you change x by a small amount. The render step draws the rectangle at
the new position. At 60 frames per second, that sequence of tiny nudges looks
like smooth motion.
The complete loop
Pyodide (the in-browser Python runner) does not support pygame's display
system. Read through this code carefully, then run it locally:
pip install pygame followed by python moving.py.
import pygame
import sys
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Moving rectangle")
clock = pygame.time.Clock() # used to cap the frame rate
# --- State ---
x = 0 # horizontal position of the rectangle
y = 200 # fixed vertical position
speed = 3 # pixels moved per frame
WIDTH, HEIGHT = 50, 50
running = True
while running:
# Phase 1 — process input
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Phase 2 — update state
x += speed
if x > 640: # wrap around when the rectangle leaves the screen
x = -WIDTH
# Phase 3 — render
screen.fill((20, 20, 30)) # erase last frame
pygame.draw.rect(screen, (80, 180, 255), (x, y, WIDTH, HEIGHT))
pygame.display.flip() # push frame to screen
clock.tick(60) # cap at 60 FPS; pause if we're running too fast
pygame.quit()
sys.exit()What each part does
pygame.time.Clock() creates a timer object. Its tick(60) call at the
end of each loop iteration enforces a 60-FPS ceiling. Without it, the loop
runs as fast as the CPU allows — hundreds of iterations per second — and the
rectangle flies off screen instantly. tick(60) returns the milliseconds
since the last call, which you can store for delta-time calculations later.
x += speed is the entire update for this example. Position is just a
number. Movement is just arithmetic on that number. Everything fancier in a
real game — gravity, friction, bouncing — is still arithmetic on position and
velocity variables.
if x > 640: x = -WIDTH wraps the rectangle. When the left edge of the
rectangle passes the right edge of the screen, it resets to just off the left
edge. The -WIDTH offset makes it slide in smoothly rather than popping.
screen.fill((20, 20, 30)) paints the entire background each frame. This
is the erase step. Skip it and you get a smear — every previous position of
the rectangle drawn on top of each other.
pygame.draw.rect(screen, colour, (x, y, width, height)) draws a filled
rectangle. The four-element tuple is the rectangle's position and dimensions.
Colour is again an (R, G, B) tuple.
Notice that the render phase reads state but never changes it. The update phase changes state but never draws. Keeping these two responsibilities separate makes it easy to add new behaviour without creating subtle bugs.
Where to go next
Next: state in games — a broader look at what game state is, why it all lives in variables, and how the screen is just a rendering of that state.