Learning Goals
3 minBy the end of this lesson you can:
- Explain what the game loop is and why it runs ~60 times per second.
- Write an
update()function that changes a variable on every tick. - Write a
draw()function that reads that variable and displays it on screen.
Warm-Up · What Does a Screen Actually Show?
5 minIn PZ-01 you learned that draw() is called ~60 times a second. Before we add movement, predict what this snippet prints:
counter = 0 for tick in range(5): counter = counter + 1 print("Tick:", counter)
Show the answer
Output
Tick: 5
Each loop pass adds 1. In a game, the same idea happens inside update() — but instead of a for loop, Pygame Zero is the one calling it repeatedly.
update() is called before draw() every single frame. Change your data in update(), display your data in draw(). Keep these two jobs separate.
New Concept · The Two-Step Game Loop
12 minThink of a flipbook animation. Each page is a new picture drawn slightly differently from the one before. Pygame Zero is the thumb flipping the pages — very fast, about 60 times a second. You draw each page inside draw(), and you decide what changes between pages inside update().
The order every frame
- Pygame Zero calls your
update()— you change variables (move things, count points). - Pygame Zero calls your
draw()— you paint the screen using those updated variables. - The new frame appears on screen. Repeat.
update() in its simplest form
score = 0 def update(): global score score = score + 1
The global keyword is needed because update() is a function, and we want to change the module-level score variable, not create a local copy.
draw() shows the current state
def draw(): screen.fill("midnightblue") screen.draw.text(str(score), center=(300, 200), fontsize=72, color="white")
screen.fill wipes the previous frame first. Then we draw the updated value. Without that fill, old frames pile up and everything looks smeared.
Worked Example · A Rising Score Counter
12 minThe story
Wei Jie wants to show a score that climbs automatically — like a speedrun timer. Save the file as counter.py:
# counter.py — a rising score counter import pgzrun WIDTH = 480 HEIGHT = 320 TITLE = "Score Counter" score = 0 # global variable: changed in update, read in draw def update(): global score score = score + 1 # called ~60 times/sec, so score climbs fast def draw(): screen.fill("black") screen.draw.text( "Score: " + str(score), center=(240, 160), fontsize=60, color="limegreen", ) pgzrun.go()
What you'll see
The number climbs about 60 counts per second. That is fast! Next lesson you will learn how to use update(dt) to tie changes to real-world time instead of raw frame count.
Set TITLE = "My Game" at the top and Pygame Zero puts that string in the window's title bar.
Try It Yourself
13 minThe score climbs very fast. Add a second variable frame = 0 that also increments in update(). Only add to score when frame % 60 == 0 — that way score increases once per second.
Hint
frame = 0 score = 0 def update(): global frame, score frame = frame + 1 if frame % 60 == 0: score = score + 1
Add a second variable lives = 3. Display both score and lives on screen — score near the top-left and lives near the top-right. Use screen.draw.text(..., topleft=(x, y)) for left-aligned text.
Hint
def draw(): screen.fill("black") screen.draw.text("Score: " + str(score), topleft=(10, 10), fontsize=32, color="limegreen") screen.draw.text("Lives: " + str(lives), topleft=(360, 10), fontsize=32, color="tomato")
Mini-Challenge 🔥 · Buggy Tick Counter
8 minFaiz wrote this tick counter but something is wrong — the number never changes on screen. Can you find and fix all the bugs?
# faiz_counter.py — buggy
import pgzrun
WIDTH = 480
HEIGHT = 320
ticks = 0
def Update():
ticks = ticks + 1
def draw():
screen.draw.text(str(ticks), center=(240, 160), fontsize=60, color="white")
pgzrun.go()It works if…
the number on screen grows every frame
Show the fix
# faiz_counter.py — fixed import pgzrun WIDTH = 480 HEIGHT = 320 ticks = 0 def update(): # bug 1: was Update() — must be lowercase global ticks # bug 2: missing global — ticks never updated ticks = ticks + 1 def draw(): screen.fill("black") # bug 3: missing fill — frames pile up screen.draw.text(str(ticks), center=(240, 160), fontsize=60, color="white") pgzrun.go()
Three bugs: wrong capitalisation on update, missing global, and no screen.fill to wipe old frames.
Recap
3 minEvery Pygame Zero game runs a loop: update() changes the world, then draw() paints the screen — about 60 times every second. Keep data-changing code in update() and drawing code in draw(). Always wipe the screen at the start of draw() with screen.fill() or screen.clear(). Use global inside functions that change module-level variables.
Vocabulary Card
- game loop
- The repeated cycle of update → draw that runs ~60 times per second inside Pygame Zero.
- update()
- The function Pygame Zero calls each frame to let you change game state (positions, scores, counters).
- draw()
- The function Pygame Zero calls each frame to let you paint the screen with the current state.
- global
- A Python keyword used inside a function to say "I mean the module-level variable, not a new local one".
Homework
4 minMake a countdown timer. Start a variable countdown = 300 (representing 300 frames, roughly 5 seconds). In update() subtract 1 each frame, but stop at zero. In draw() show the number in large text. Save as countdown.py and bring a screenshot of it at zero.
Stretch. When countdown reaches zero, change the text to "Time's up!" using an if statement in draw().
Sample · countdown.py
# countdown.py — a frame-based countdown timer import pgzrun WIDTH = 480 HEIGHT = 320 TITLE = "Countdown" countdown = 300 # ~5 seconds at 60 fps def update(): global countdown if countdown > 0: countdown = countdown - 1 def draw(): screen.fill("midnightblue") if countdown > 0: screen.draw.text(str(countdown), center=(240, 160), fontsize=80, color="white") else: screen.draw.text("Time's up!", center=(240, 160), fontsize=56, color="tomato") pgzrun.go()
Your window size and colours will differ — the key idea is the if countdown > 0 guard in both functions.