Code of the Day
BeginnerCollision and Polish

Collision in code

Use pygame.Rect and its colliderect method to detect overlaps between the player and obstacles, then respond to them.

Game DevBeginner10 min read
By the end of this lesson you will be able to:
  • Create and position pygame.Rect objects
  • Use colliderect to test whether two rects overlap
  • Respond to a collision by resetting or blocking movement
  • Draw debug outlines to visualise hitboxes

pygame.Rect is pygame's workhorse for both drawing and collision. You have been using it implicitly in every pygame.draw.rect call. This lesson makes it explicit and shows you how to use it for detection.

pygame.Rect basics

A Rect stores position and size and exposes useful attributes:

r = pygame.Rect(100, 80, 50, 40)   # (x, y, width, height)

r.left    # 100   — left edge x
r.right   # 150   — right edge x  (left + width)
r.top     # 80    — top edge y
r.bottom  # 120   — bottom edge y (top + height)
r.centerx # 125   — horizontal centre
r.centery # 100   — vertical centre
r.width   # 50
r.height  # 40

You can move a rect by assigning to position attributes:

r.x += 5      # shift right
r.topleft = (200, 100)   # teleport

Rects are mutable objects — changing an attribute changes the underlying values immediately.

Detecting overlaps

.colliderect(other) returns True if the two rects overlap, False otherwise. It implements exactly the AABB check described in the previous lesson.

player = pygame.Rect(100, 100, 40, 40)
wall   = pygame.Rect(120, 110, 60, 60)

if player.colliderect(wall):
    print("collision!")

That's the whole API for basic collision. The complexity lives in how you respond to the collision, not in detecting it.

Collision response — push back

The simplest response to hitting a wall is to undo the move that caused the collision. Store the previous position before updating, and restore it if a collision occurs.

Pyodide (the in-browser Python runner) does not support pygame's display system. Read through this code, then run it locally: pip install pygame followed by python collision.py.

import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Collision")
clock = pygame.time.Clock()

BG      = (20, 20, 30)
PLAYER_C = (100, 210, 120)
WALL_C   = (180, 80, 80)
DEBUG_C  = (255, 255, 0)   # yellow outline for hitboxes

SPEED = 4

player = pygame.Rect(60, 200, 40, 40)

# A list of wall rects the player cannot pass through
walls = [
    pygame.Rect(200, 100, 20, 280),   # vertical wall
    pygame.Rect(350, 200, 200, 20),   # horizontal wall
]

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

    # Save position before moving
    old_x, old_y = player.x, player.y

    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

    # Clamp inside window
    player.clamp_ip(screen.get_rect())

    # Check each wall; restore position if the player overlaps
    for wall in walls:
        if player.colliderect(wall):
            player.x, player.y = old_x, old_y
            break

    # Render
    screen.fill(BG)
    for wall in walls:
        pygame.draw.rect(screen, WALL_C, wall)
    pygame.draw.rect(screen, PLAYER_C, player)

    # Debug: draw hitbox outlines (comment out when satisfied)
    pygame.draw.rect(screen, DEBUG_C, player, 1)
    for wall in walls:
        pygame.draw.rect(screen, DEBUG_C, wall, 1)

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

pygame.quit()
sys.exit()

What each part does

old_x, old_y = player.x, player.y snapshots the position before any movement. If a collision fires, these values restore the player to where they were before the offending move.

player.clamp_ip(screen.get_rect()) is a built-in rect method that moves the rect inward until it fits inside the argument rect. _ip means "in place" — it modifies the rect directly. This replaces the manual min/max clamping from the earlier lessons.

player.colliderect(wall) is the AABB test. If it returns True, we immediately undo the player's last move with the snapshot values. The break exits the wall loop early — once a collision is found, there's no need to check the rest.

pygame.draw.rect(screen, colour, rect, 1) draws an outline (width=1) rather than a filled rectangle. This makes hitboxes visible during development without hiding the sprites underneath. Remove or comment out debug drawing before releasing.

The push-back approach is simple and works well for rectangular obstacles. For smoother collision response — sliding along walls instead of stopping — you would separate the x and y move into two steps and resolve each axis independently. That is a natural next step once the basics are solid.

Where to go next

Next: lab — polish — add obstacles, a score counter, and a restart mechanic to the basic game you built in the previous module.

Finished reading? Mark it complete to track your progress.

On this page