pyodide: loading…

[concept]Game Development with Pygame

Text & HUD Rendering

# theory

rendering text in Pygame

Pygame doesn't have a text box you can just type into. To show text on screen you render it onto a surface, then blit that surface onto the screen. It's a two-step process.

font = pygame.font.SysFont(None, 36)  # None = default system font, 36 = size
text_surface = font.render("Hello!", True, (255, 255, 255))  # text, antialias, color
screen.blit(text_surface, (10, 10))  # draw it at position (10, 10)

That's the whole pattern. Everything else is just variations on it.


font options

System Fonts

# Use any font installed on the system
font = pygame.font.SysFont("arial", 28)
font = pygame.font.SysFont("courier", 20)

# None = pygame default font (always available)
font = pygame.font.SysFont(None, 36)

# Bold and italic
font = pygame.font.SysFont("arial", 28, bold=True, italic=True)

# See what fonts are available:
print(pygame.font.get_fonts())

Custom Font Files

# Load a .ttf file from your project folder
font = pygame.font.Font("assets/fonts/PressStart2P.ttf", 16)

Custom fonts make your game look unique. Free pixel fonts from Google Fonts or dafont.com work great for retro games.


render()

text_surface = font.render(text, antialias, color, background=None)
ParamWhat it does
textThe string to display
antialiasTrue = smooth edges, False = pixelated
color(R, G, B) text color
backgroundOptional (R, G, B) background color

Anti-aliasing: Use True for most text. Use False for pixel art games where you want that crispy look.

# Smooth text
smooth = font.render("Score: 100", True, (255, 255, 255))

# Pixel-crisp text (retro style)
crispy = font.render("GAME OVER", False, (255, 0, 0))

# Text with background color (like a highlight)
highlighted = font.render("NEW HIGH SCORE!", True, (255, 255, 0), (0, 0, 100))

positioning text

font.render() returns a surface. Positioning it on the screen is a separate step.

Basic Positioning

# Top-left corner
screen.blit(text_surface, (10, 10))

# But what if you want it centered?
text_rect = text_surface.get_rect()
text_rect.center = (400, 300)  # center of an 800x600 screen
screen.blit(text_surface, text_rect)

Alignment Shortcuts

rect = text_surface.get_rect()

# Center on screen
rect.center = (SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)

# Top-center
rect.midtop = (SCREEN_WIDTH // 2, 10)

# Right-aligned
rect.topright = (SCREEN_WIDTH - 10, 10)

# Bottom-center (for UI at bottom of screen)
rect.midbottom = (SCREEN_WIDTH // 2, SCREEN_HEIGHT - 10)

building a HUD (heads-up display)

A HUD shows game info that updates every frame: score, lives, timer, level.

The Pattern

class HUD:
    def __init__(self):
        self.font = pygame.font.SysFont(None, 28)
        self.score = 0
        self.lives = 3
        self.level = 1

    def draw(self, screen):
        # Score; top left
        score_text = self.font.render(f"Score: {self.score}", True, (255, 255, 255))
        screen.blit(score_text, (10, 10))

        # Lives; top right
        lives_text = self.font.render(f"Lives: {self.lives}", True, (255, 100, 100))
        lives_rect = lives_text.get_rect(topright=(screen.get_width() - 10, 10))
        screen.blit(lives_text, lives_rect)

        # Level; top center
        level_text = self.font.render(f"Level {self.level}", True, (200, 200, 200))
        level_rect = level_text.get_rect(midtop=(screen.get_width() // 2, 10))
        screen.blit(level_text, level_rect)

Using It

hud = HUD()

while running:
    # ... game logic ...

    if brick_destroyed:
        hud.score += 10

    if player_hit:
        hud.lives -= 1

    # Draw everything
    screen.fill((0, 0, 0))
    # ... draw game objects ...
    hud.draw(screen)  # HUD goes LAST (on top of everything)
    pygame.display.flip()

Key insight: Draw the HUD last so it renders on top of everything else.


performance tip: cache rendered text

Re-rendering text every frame is wasteful if the text hasn't changed. Cache it:

class HUD:
    def __init__(self):
        self.font = pygame.font.SysFont(None, 28)
        self._score = 0
        self._cached_score_surface = None

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        self._score = value
        # Only re-render when score actually changes
        self._cached_score_surface = self.font.render(
            f"Score: {value}", True, (255, 255, 255)
        )

    def draw(self, screen):
        if self._cached_score_surface:
            screen.blit(self._cached_score_surface, (10, 10))

For simple games this optimization doesn't matter much, but it's a good habit. Rendering text is one of the slower operations in Pygame.


timer display

start_time = pygame.time.get_ticks()

# In game loop:
elapsed_ms = pygame.time.get_ticks() - start_time
seconds = elapsed_ms // 1000
minutes = seconds // 60
display_seconds = seconds % 60
timer_text = font.render(f"{minutes:02d}:{display_seconds:02d}", True, WHITE)

The :02d format pads with zeros: "01:05" instead of "1:5".

# examples [2]

# example 01 · centering text on screen

Use get_rect() with center to position text in the middle of the screen.

1
2
3
4
🐍
Loading PythonSetting up pandas & numpy...

pygame needs a real window — copy this into a .py file and run it locally.

# example 02 · dynamic score with right alignment

Right-align the score so it doesn't jump around as the number gets bigger.

1
2
3
4
🐍
Loading PythonSetting up pandas & numpy...

pygame needs a real window — copy this into a .py file and run it locally.

# challenges [3]

# challenge 01/03todo
What are the two steps to display text in Pygame? Why can't you just 'print' text to the screen?
pygame needs a real window. copy this into a .py file and run it locally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
🐍
Loading PythonSetting up pandas & numpy...
# challenge 02/03todo
How do you center text horizontally on an 800px wide screen?
pygame needs a real window. copy this into a .py file and run it locally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
🐍
Loading PythonSetting up pandas & numpy...
# challenge 03/03todo
Why should you draw your HUD last in the draw step, after all other game objects?
pygame needs a real window. copy this into a .py file and run it locally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
🐍
Loading PythonSetting up pandas & numpy...