35. Score and text
Almost every game shows information on screen: a score, a timer, a message when the player wins or loses. pygame can render text using system fonts or a built-in default font. This chapter covers displaying text, tracking a score, and building a simple win/lose condition.
Loading a font
Before rendering any text, create a font object. Two ways:
# Use a system font by name (Arial is available on Windows/Mac/most Linux)
font = pygame.font.SysFont("arial", 32)
# Use pygame's built-in default font (no name needed, always available)
font = pygame.font.Font(None, 32)The second argument is the size in points. Create the font object once, before the game loop — creating it inside the loop every frame is slow.
Rendering text to a Surface
font.render(text, antialias, color) converts a string to
a Surface (a small image of the text):
surface = font.render("Hello, world!", True, (255, 255, 255))text— the string to draw. It must be a string; convert numbers withstr()or use an f-string.antialias—Truefor smooth edges,Falsefor sharp pixelated edges.Truelooks better at larger sizes.color— an RGB tuple for the text colour.
font.render returns a Surface. Draw it on screen with
screen.blit.
Blitting (drawing) a surface
screen.blit(source_surface, (x, y)) copies a surface
onto screen at position (x, y). The position
is the top-left corner of the pasted image:
text_surface = font.render("Score: 0", True, (255, 255, 255))
screen.blit(text_surface, (10, 10))This draws "Score: 0" near the top-left corner of the
window.
Call blit inside the game loop draw section, after
screen.fill and before
pygame.display.flip.
Displaying a score
The standard pattern: render a new surface each frame from the current score value.
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Score")
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 28)
score = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
score += 1 # increase score on spacebar
screen.fill((20, 20, 40))
# Render and blit the score each frame
score_surf = font.render(f"Score: {score}", True, (255, 255, 255))
screen.blit(score_surf, (10, 10))
pygame.display.flip()
clock.tick(60)
pygame.quit()Rendering a new surface each frame from an f-string is idiomatic pygame. The surface is small and cheap to create. Do not cache it — always render fresh so the display matches the current value.
Tracking score with collision
In a game, the score usually increases when the player collects something. Combine the score display with the collision pattern from chapter 34:
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Collect")
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 28)
W, H = 800, 600
player = pygame.Rect(370, 270, 50, 50)
coin = pygame.Rect(random.randint(0, W-30), random.randint(0, H-30), 30, 30)
score = 0
speed = 5
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.x -= speed
if keys[pygame.K_RIGHT]:
player.x += speed
if keys[pygame.K_UP]:
player.y -= speed
if keys[pygame.K_DOWN]:
player.y += speed
player.clamp_ip(pygame.Rect(0, 0, W, H))
if player.colliderect(coin):
score += 1
coin.x = random.randint(0, W - coin.width)
coin.y = random.randint(0, H - coin.height)
screen.fill((20, 20, 40))
pygame.draw.circle(screen, (255, 220, 0),
coin.center, coin.width // 2)
pygame.draw.rect(screen, (100, 200, 255), player)
score_surf = font.render(f"Score: {score}", True, (255, 255, 255))
screen.blit(score_surf, (10, 10))
pygame.display.flip()
clock.tick(60)
pygame.quit()Win and lose conditions
A win condition checks whether the player has reached a goal. When the condition is met, stop updating the game and show a message.
WIN_SCORE = 10
# In the update section:
if score >= WIN_SCORE:
game_over = True
message = "You win!"A simple approach: use a game_over boolean. When
True, skip movement and collision updates but keep drawing,
and draw the message on top.
game_over = False
message = ""
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if not game_over:
# ... movement and collision ...
if score >= WIN_SCORE:
game_over = True
message = "You win! Press Escape to exit."
screen.fill((20, 20, 40))
# ... draw player, coins, score ...
if game_over:
msg_surf = font.render(message, True, (255, 220, 0))
screen.blit(msg_surf, (200, 250))
pygame.display.flip()
clock.tick(60)
pygame.quit()The same pattern works for a lose condition: if
timer <= 0, set game_over = True and a
"Time's up!" message.
Centering text
To centre text horizontally, find its width and subtract half from the centre x of the window:
msg_surf = font.render("Game Over", True, (255, 255, 255))
text_x = (800 - msg_surf.get_width()) // 2
text_y = (600 - msg_surf.get_height()) // 2
screen.blit(msg_surf, (text_x, text_y))get_width() and get_height() return the
pixel dimensions of the rendered text surface.
Open exercises/35/01-click-score.py. Each left mouse
click increases a score counter. Display the score on screen at
(10, 10). When the score reaches 5, show a
"Done!" message in the centre of the window.
Homework
Problem 1 — Score display
Open exercises/35/homework/01-score-display.py. Draw a
number on screen that starts at 0. Press the Up arrow to increase it,
the Down arrow to decrease it (minimum 0). The number must update
immediately on screen.
Problem 2 — Countdown timer
Open exercises/35/homework/02-countdown.py. Display a
timer that counts down from 10 to 0. Use clock.tick(60) to
control speed, and track elapsed time by adding 1/60 to a
float accumulator each frame. When the counter reaches 0, display
"Time's up!" and stop counting.
Problem 3 — Collect and score
Open exercises/35/homework/03-collect.py. Use the
arrow-key mover and collision pattern. Each time the player touches the
target, add one to the score and reposition the target. Display the
score at the top of the screen.
Challenge — Three lives
Open exercises/35/homework/04-three-lives.py. The player
collects coins (score += 1 per coin). A second set of red squares moves
toward the player (you can pick a simple movement direction per square).
If the player touches a red square, lives -= 1 and the square respawns.
Display both "Score: X" and "Lives: X" on
screen. When lives reach 0, show "Game Over" and stop the
game.
Stuck or finished? Open the homework solutions page.