What you will learn

This chapter is all about handling user input. You will discover the crucial difference between events (a one-time key press) and key states (a key being held down). You will also learn how to use the mouse as an alternative control method. By the end, your paddle will finally move!

The concept

Pygame offers two approaches for reading the keyboard:

  1. Events (pygame.event.get()): they fire once when a key is pressed or released. Ideal for discrete actions: opening a menu, pausing, switching modes.
  2. Key states (pygame.key.get_pressed()): returns a dictionary of booleans indicating which keys are currently held down. Ideal for continuous movement: moving a character, a paddle.

The golden rule: use events for one-shot actions, and key states for continuous actions.

For the mouse, pygame.mouse.get_pos() returns the current cursor position as a tuple (x, y). It is perfect for direct, fluid paddle control.

Step by step

1. Event for mode switching

We use a KEYDOWN event to toggle between keyboard and mouse mode with the M key. This is a one-shot action: press once, mode changes.

python
use_mouse = False

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 event.key == pygame.K_m:
            use_mouse = not use_mouse

If we used get_pressed() for this action, the mode would toggle 60 times per second while M is held, which would be unusable.

2. Key states for movement

For paddle movement, we read key states every frame. As long as the left arrow is held, the paddle moves left. We also support A and D keys for players used to WASD controls.

python
if use_mouse:
    mouse_x, _ = pygame.mouse.get_pos()
    paddle_x = mouse_x - PADDLE_WIDTH // 2
else:
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] or keys[pygame.K_a]:
        paddle_x -= PADDLE_SPEED
    if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
        paddle_x += PADDLE_SPEED

In mouse mode, we center the paddle on the cursor by subtracting half its width. In keyboard mode, we move the paddle by PADDLE_SPEED pixels per frame.

3. Clamping the paddle to the screen

Without constraints, the paddle could go off-screen. We use max() and min() to keep it within bounds. This is called clamping.

python
# Clamp paddle to screen
paddle_x = max(0, min(paddle_x, SCREEN_WIDTH - PADDLE_WIDTH))

This line guarantees that paddle_x will never be less than 0 (left edge) or greater than SCREEN_WIDTH - PADDLE_WIDTH (right edge). We subtract PADDLE_WIDTH because paddle_x represents the left edge of the paddle.

4. Displaying the active mode

We display the current control mode and available instructions at the bottom of the screen, so the player always knows how to interact.

python
mode = "MOUSE" if use_mouse else "KEYBOARD"
info = font.render(
    f"Mode: {mode}  [M] Switch  [Arrows/AD] Move  [ESC] Quit",
    True, (150, 150, 150)
)
screen.blit(info, (10, SCREEN_HEIGHT - 25))

Key takeaways

Tip: you can combine both approaches. For example, use a KEYDOWN event to fire a projectile (once per press) and get_pressed() for continuous ship movement.

Common pitfall: be careful, K_LEFT is not the same as K_a. The first is an arrow key, the second is the letter A. On some keyboard layouts (AZERTY), you may need to handle K_q instead of K_a.

Common pitfall: remember that PADDLE_SPEED = 8 means 8 pixels per frame. At 60 FPS, that is 480 pixels per second. Adjust this value for comfortable movement.

Pygame functions used:

  • pygame.key.get_pressed(): state of all keys (True/False)
  • pygame.mouse.get_pos(): cursor position (x, y)
  • pygame.K_LEFT, K_RIGHT, etc.: key constants

Complete code

python
"""Brick Breaker - Chapter 4: Handling Input"""
import pygame
import sys

pygame.init()

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

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

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

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


def rainbow_color(row, total_rows):
    hue = int(360 * row / total_rows)
    color = pygame.Color(0)
    color.hsva = (hue, 100, 100, 100)
    return (color.r, color.g, color.b)


screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Brick Breaker - Chapter 4")
clock = pygame.time.Clock()
font = pygame.font.SysFont("monospace", 14)
use_mouse = False

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 event.key == pygame.K_m:
                use_mouse = not use_mouse

    # Input: key states (continuous) vs events (discrete)
    if use_mouse:
        mouse_x, _ = pygame.mouse.get_pos()
        paddle_x = mouse_x - PADDLE_WIDTH // 2
    else:
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            paddle_x -= PADDLE_SPEED
        if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            paddle_x += PADDLE_SPEED

    # Clamp paddle to screen
    paddle_x = max(0, min(paddle_x, SCREEN_WIDTH - PADDLE_WIDTH))

    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 = rainbow_color(row, BRICK_ROWS)
            if (row + col) % 2 == 0:
                color = tuple(min(255, c + 30) for c in color)
            pygame.draw.rect(screen, color, (x, y, BRICK_WIDTH, BRICK_HEIGHT))
            pygame.draw.rect(screen, (255, 255, 255),
                             (x, y, BRICK_WIDTH, BRICK_HEIGHT), 1)

    # 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)

    # Instructions
    mode = "MOUSE" if use_mouse else "KEYBOARD"
    info = font.render(
        f"Mode: {mode}  [M] Switch  [Arrows/AD] Move  [ESC] Quit",
        True, (150, 150, 150)
    )
    screen.blit(info, (10, SCREEN_HEIGHT - 25))

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

pygame.quit()
sys.exit()

Download

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