What you will learn

In this chapter, you will learn to draw geometric shapes on the screen: rectangles, circles, and lines. These are the building blocks (pun intended) of any 2D game. By the end, you will have a paddle, a ball, and a wall of colored bricks displayed on screen.

The concept

Pygame provides a pygame.draw module that contains functions to draw directly onto a Surface. The game window is itself a Surface. At each frame, we erase everything with fill() then redraw each element.

The main functions are:

  • pygame.draw.rect(surface, color, rectangle): draws a rectangle
  • pygame.draw.circle(surface, color, center, radius): draws a circle
  • pygame.draw.line(surface, color, start, end): draws a line

A rectangle is defined by a tuple (x, y, width, height). The (x, y) coordinates represent the top-left corner. Colors are RGB tuples from 0 to 255.

The order in which you draw matters: whatever is drawn last appears on top of everything else. This is the painter's algorithm. Draw the background first, then the bricks, then the paddle, then the ball.

Step by step

1. Defining element dimensions

We start by defining constants for each game element. Using uppercase constants makes the code readable and easy to tweak.

python
# Paddle
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 15
PADDLE_COLOR = (200, 200, 200)
paddle_x = SCREEN_WIDTH // 2 - PADDLE_WIDTH // 2
paddle_y = SCREEN_HEIGHT - 40

# Ball
BALL_RADIUS = 8
BALL_COLOR = (255, 255, 255)
ball_x = SCREEN_WIDTH // 2
ball_y = SCREEN_HEIGHT // 2

# Bricks
BRICK_ROWS = 5
BRICK_COLS = 10
BRICK_WIDTH = 70
BRICK_HEIGHT = 20
BRICK_PADDING = 5
BRICK_OFFSET_X = 30
BRICK_OFFSET_Y = 50

Notice that paddle_x is calculated to center the paddle horizontally. The // operator performs integer division, which matters because pixels are integers.

2. Color palette for bricks

Each row of bricks gets a different color. We store the colors in a list, indexed by row number.

python
BRICK_COLORS = [
    (231, 76, 60),   # Red
    (230, 126, 34),  # Orange
    (241, 196, 15),  # Yellow
    (46, 204, 113),  # Green
    (52, 152, 219),  # Blue
]

3. Drawing bricks with a nested loop

We use two nested loops: one for rows, one for columns. Each brick's position is calculated from its row and column, accounting for spacing (BRICK_PADDING) and the initial offset.

python
# Draw bricks
for row in range(BRICK_ROWS):
    for col in range(BRICK_COLS):
        x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING)
        y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING)
        color = BRICK_COLORS[row]
        pygame.draw.rect(screen, color, (x, y, BRICK_WIDTH, BRICK_HEIGHT))

4. Drawing the paddle and ball

The paddle is a simple rectangle. The ball is a circle. Note that draw.circle takes the center of the circle, while draw.rect takes the top-left corner.

python
# Draw paddle
pygame.draw.rect(screen, PADDLE_COLOR,
                 (paddle_x, paddle_y, PADDLE_WIDTH, PADDLE_HEIGHT))

# Draw ball
pygame.draw.circle(screen, BALL_COLOR, (ball_x, ball_y), BALL_RADIUS)

Key takeaways

Tip: to draw only the outline of a rectangle (no fill), add a width parameter: pygame.draw.rect(screen, color, rect, 2) draws a 2-pixel-thick outline.

Common pitfall: if your bricks seem misaligned, check that you are not forgetting the padding in the position calculation. A classic mistake is writing col * BRICK_WIDTH instead of col * (BRICK_WIDTH + BRICK_PADDING).

Pygame functions used:

  • pygame.draw.rect(): draws a rectangle (filled or outline)
  • pygame.draw.circle(): draws a circle

Complete code

python
"""Brick Breaker - Chapter 2: Drawing Primitives"""
import pygame
import sys

pygame.init()

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
BG_COLOR = (20, 20, 40)

# Paddle
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 15
PADDLE_COLOR = (200, 200, 200)
paddle_x = SCREEN_WIDTH // 2 - PADDLE_WIDTH // 2
paddle_y = SCREEN_HEIGHT - 40

# Ball
BALL_RADIUS = 8
BALL_COLOR = (255, 255, 255)
ball_x = SCREEN_WIDTH // 2
ball_y = SCREEN_HEIGHT // 2

# Bricks
BRICK_ROWS = 5
BRICK_COLS = 10
BRICK_WIDTH = 70
BRICK_HEIGHT = 20
BRICK_PADDING = 5
BRICK_OFFSET_X = 30
BRICK_OFFSET_Y = 50
BRICK_COLORS = [
    (231, 76, 60),   # Red
    (230, 126, 34),  # Orange
    (241, 196, 15),  # Yellow
    (46, 204, 113),  # Green
    (52, 152, 219),  # Blue
]

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Brick Breaker - Chapter 2")
clock = pygame.time.Clock()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            running = False

    screen.fill(BG_COLOR)

    # Draw bricks
    for row in range(BRICK_ROWS):
        for col in range(BRICK_COLS):
            x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING)
            y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING)
            color = BRICK_COLORS[row]
            pygame.draw.rect(screen, color, (x, y, BRICK_WIDTH, BRICK_HEIGHT))

    # Draw paddle
    pygame.draw.rect(screen, PADDLE_COLOR,
                     (paddle_x, paddle_y, PADDLE_WIDTH, PADDLE_HEIGHT))

    # Draw ball
    pygame.draw.circle(screen, BALL_COLOR, (ball_x, ball_y), BALL_RADIUS)

    pygame.display.flip()
    clock.tick(FPS)

pygame.quit()
sys.exit()

Download

Download the source code for this chapter: brick-breaker-ch02.zip