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 with str() or use an f-string.
  • antialiasTrue for smooth edges, False for sharp pixelated edges. True looks 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.