Learning Goals
3 minBy the end of this lesson you can:
- Explain what
actor_a.colliderect(actor_b)checks and when it returnsTrue. - Use
Rect((x, y), (w, h)).colliderect(other_rect)to test two plain rectangles. - Run a script where two coloured boxes change to red the moment their bounding rectangles overlap.
Warm-Up · Does It Overlap?
5 minYou used colliderect in PZ-18 to catch the mouse. Before writing more code, make sure the idea is clear. Two rectangles overlap when neither is completely to the left, right, above, or below the other. Try this logic:
# Two rectangles as (x, y, width, height) ax, ay, aw, ah = 100, 100, 80, 60 bx, by, bw, bh = 150, 120, 80, 60 # They overlap if none of the four "no overlap" cases is true: no_overlap = (ax + aw <= bx) or (bx + bw <= ax) or (ay + ah <= by) or (by + bh <= ay) print("overlap:", not no_overlap)
Show the answer
Output
overlap: True
Rectangle A reaches from x=100 to x=180. Rectangle B starts at x=150 — they share the strip x=150 to x=180. colliderect runs this logic in one C-speed call so you never have to write it yourself.
New Concept · How colliderect Works
12 minThink of each actor or Rect as a cardboard cutout lying on a table. colliderect asks: do these two cardboard rectangles touch or overlap right now? It returns True if they do, False if there is any gap.
With two Actors
def update(): if player.colliderect(enemy): print("Hit!") # called every frame they overlap
The bounding box of each Actor is calculated from the image size and .pos. You never set the box manually.
With two Rects
Rect is Pygame Zero's plain rectangle object. Create one with Rect((x, y), (width, height)):
box_a = Rect((100, 100), (80, 60)) box_b = Rect((150, 120), (80, 60)) if box_a.colliderect(box_b): print("boxes overlap")
Rect attributes you can read and set
r = Rect((200, 150), (60, 40)) print(r.x, r.y) # top-left corner print(r.right, r.bottom) # opposite corner print(r.center) # centre point as (x, y) tuple print(r.width, r.height)
Why it matters
colliderect runs at native speed. Checking a hundred pairs per frame is still instant. It is the foundation of platformer ground checks, bullet hits, item pickups, and push-back walls.
Worked Example · Two Boxes That Turn Red
12 minThe story
Arjun wants to understand exactly when collision fires. He builds a minimal scene: one box he can move with arrow keys, one stationary box. Both turn red the moment their rectangles overlap. Save as collide_demo.py:
# collide_demo.py — part 1: setup import pgzrun WIDTH = 600 HEIGHT = 400 TITLE = "colliderect Demo" SPEED = 3 # Moving box — tracked as a Rect mover = Rect((50, 180), (60, 60)) # Static box in the middle wall = Rect((260, 160), (80, 80))
# collide_demo.py — part 2: draw and update def draw(): screen.fill("white") touching = mover.colliderect(wall) mover_col = "red" if touching else "dodgerblue" wall_col = "red" if touching else "darkorange" screen.draw.filled_rect(mover, mover_col) screen.draw.filled_rect(wall, wall_col) screen.draw.rect(mover, "black") screen.draw.rect(wall, "black") status = "TOUCHING" if touching else "separate" screen.draw.text( f"Status: {status}", topleft=(10, 10), fontsize=26, color="black", ) screen.draw.text( "Arrow keys to move blue box", topleft=(10, 370), fontsize=18, color="grey", ) def update(): if keyboard.right: mover.x += SPEED if keyboard.left: mover.x -= SPEED if keyboard.up: mover.y -= SPEED if keyboard.down: mover.y += SPEED pgzrun.go()
What you'll see
Try It Yourself
13 minAdd a second static rectangle somewhere else on screen. Make all three boxes turn red when the mover touches either static box. Use an or in the collision check.
Hint
obstacle = Rect((440, 200), (70, 70)) # In draw(): touching = mover.colliderect(wall) or mover.colliderect(obstacle) # then draw obstacle with the same colour logic
When the mover touches the wall, stop it moving into it further. After the movement in update(), check mover.colliderect(wall) and if True, undo the last move by shifting the mover back by SPEED in the direction it came from.
Hint
def update(): if keyboard.right: mover.x += SPEED if mover.colliderect(wall): mover.x -= SPEED # push back if keyboard.left: mover.x -= SPEED if mover.colliderect(wall): mover.x += SPEED
Mini-Challenge 🔥 · Debug: Kavitha's Coin Collector
8 minKavitha wrote a coin-collector game. The player moves right and picks up coins. But the collision never fires — the score stays at 0 forever. Find and fix the two bugs.
# kavitha_coins.py — buggy
import pgzrun
import random
WIDTH = 600
HEIGHT = 400
score = 0
player = Rect((50, 180), (40, 40))
coins = [Rect((random.randint(100, 580), random.randint(50, 350)), (20, 20))
for _ in range(5)]
def draw():
screen.fill("lightyellow")
screen.draw.filled_rect(player, "dodgerblue")
for coin in coins:
screen.draw.filled_circle(coin, 10, "gold") # bug 1
screen.draw.text(f"Score: {score}", topleft=(10,10), fontsize=28, color="black")
def update():
global score
if keyboard.right:
player.x += 3
for coin in coins:
if player.collidepoint(coin): # bug 2
score += 1
coin.x = random.randint(100, 580)
coin.y = random.randint(50, 350)
pgzrun.go()It works if…
moving the player over a gold circle increments the score
Show the fix
# kavitha_coins.py — fixed import pgzrun import random WIDTH = 600 HEIGHT = 400 score = 0 player = Rect((50, 180), (40, 40)) coins = [Rect((random.randint(100, 580), random.randint(50, 350)), (20, 20)) for _ in range(5)] def draw(): screen.fill("lightyellow") screen.draw.filled_rect(player, "dodgerblue") for coin in coins: screen.draw.filled_circle(coin.center, 10, "gold") # fix 1: use coin.center screen.draw.text(f"Score: {score}", topleft=(10, 10), fontsize=28, color="black") def update(): global score if keyboard.right: player.x += 3 for coin in coins: if player.colliderect(coin): # fix 2: colliderect not collidepoint score += 1 coin.x = random.randint(100, 580) coin.y = random.randint(50, 350) pgzrun.go()
Bug 1: filled_circle needs a centre point (x, y) — pass coin.center, not the whole Rect. Bug 2: collidepoint checks if a single point is inside one rect; colliderect checks if two rects overlap — this is a rect-vs-rect test.
Recap
3 mincolliderect returns True when two rectangles share any overlapping area this frame. It works between two Actors, two Rects, or an Actor and a Rect. Rect((x, y), (w, h)) is a plain rectangle with handy attributes like .center, .right, and .bottom. Use or to check multiple objects at once.
Vocabulary Card
- colliderect(other)
- Returns
Trueif two rectangles (actors or Rects) overlap at all — the core of nearly every collision system. - Rect((x, y), (w, h))
- A plain rectangle object. Supports
.colliderect(),.collidepoint(), and attributes.x .y .right .bottom .center .width .height. - bounding box
- The invisible rectangle that wraps an actor's image — used by
colliderectto test overlap. - push-back
- Undoing a move when a collision fires, to prevent actors from passing through solid objects.
Homework
4 minBuild a small maze. Draw 4–6 wall rectangles using screen.draw.filled_rect. Move a player box with arrow keys. Use the push-back technique from Try-It 02 so the player cannot walk through any wall. Save as maze_walls.py and bring a screenshot next class.
Hint: put your walls in a list and loop through them to check all push-backs in one go.
Sample · maze_walls.py
# maze_walls.py — player + solid walls import pgzrun WIDTH = 600 HEIGHT = 400 SPEED = 3 player = Rect((40, 40), (30, 30)) walls = [ Rect((100, 0), (20, 260)), Rect((200, 140), (20, 260)), Rect((300, 0), (20, 200)), Rect((400, 200), (20, 200)), Rect((480, 0), (20, 300)), ] def draw(): screen.fill("cornsilk") screen.draw.filled_rect(player, "steelblue") for wall in walls: screen.draw.filled_rect(wall, "saddlebrown") def update(): if keyboard.right: player.x += SPEED for wall in walls: if player.colliderect(wall): player.x -= SPEED if keyboard.left: player.x -= SPEED for wall in walls: if player.colliderect(wall): player.x += SPEED if keyboard.up: player.y -= SPEED for wall in walls: if player.colliderect(wall): player.y += SPEED if keyboard.down: player.y += SPEED for wall in walls: if player.colliderect(wall): player.y -= SPEED pgzrun.go()
Your wall layout will differ — that is fine. The push-back loop pattern is what matters. It works correctly because we check after each direction separately.