Rectangles and collisions
Pygame fundamentals · Lesson 4 of 7
A pygame.Rect holds a position and a size, and it can tell you when two things touch.
A Rect is a box you can move
pygame.Rect(x, y, width, height) remembers a box. Change rect.x and rect.y to move it, and read its edges and middle by name: rect.left, rect.right, rect.top, rect.bottom, rect.center. You can also set them: rect.center = (240, 160) moves the box so its middle is there.
Bouncing is a change of direction: the ball has a speed across (dx) and a speed down (dy), and hitting a wall turns one of them around.
import pygame pygame.init() screen = pygame.display.set_mode((480, 320)) clock = pygame.time.Clock() ball = pygame.Rect(100, 80, 16, 16) dx, dy = 4, 3 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False ball.x += dx ball.y += dy if ball.left < 0 or ball.right > 480: dx = -dx if ball.top < 0 or ball.bottom > 320: dy = -dy screen.fill((16, 18, 22)) pygame.draw.ellipse(screen, (255, 212, 59), ball) pygame.display.flip() clock.tick(60) pygame.quit()
Touching: colliderect
a.colliderect(b) is True when two rectangles overlap. Here the ball bounces off the paddle only while it is moving down (dy > 0), so it cannot get stuck inside the paddle and bounce back and forth.
paddle.clamp_ip(screen.get_rect()) keeps the paddle inside the window, a shorter way to write the max and min from the last lesson.
import pygame pygame.init() screen = pygame.display.set_mode((480, 320)) clock = pygame.time.Clock() font = pygame.font.Font(None, 30) ball = pygame.Rect(240, 60, 14, 14) paddle = pygame.Rect(200, 290, 80, 12) dx, dy = 3, 4 bounces = 0 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: paddle.x -= 6 if keys[pygame.K_RIGHT]: paddle.x += 6 paddle.clamp_ip(screen.get_rect()) ball.x += dx ball.y += dy if ball.left < 0 or ball.right > 480: dx = -dx if ball.top < 0: dy = -dy if dy > 0 and ball.colliderect(paddle): dy = -dy bounces += 1 if ball.top > 320: # missed: serve again from the top ball.topleft = (240, 60) bounces = 0 screen.fill((16, 18, 22)) pygame.draw.rect(screen, (255, 212, 59), paddle, border_radius=5) pygame.draw.ellipse(screen, (230, 232, 235), ball) screen.blit(font.render(f"Bounces {bounces}", True, (230, 232, 235)), (12, 10)) pygame.display.flip() clock.tick(60) pygame.quit()
The mouse: collidepoint
A MOUSEBUTTONDOWN event carries event.pos, the point that was clicked. rect.collidepoint(point) says whether that point is inside the rectangle, which is all a clickable button or target needs.
import random import pygame pygame.init() screen = pygame.display.set_mode((480, 320)) clock = pygame.time.Clock() font = pygame.font.Font(None, 30) target = pygame.Rect(200, 120, 60, 60) hits = 0 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEBUTTONDOWN and target.collidepoint(event.pos): hits += 1 target.topleft = (random.randint(0, 420), random.randint(40, 260)) screen.fill((16, 18, 22)) pygame.draw.ellipse(screen, (235, 95, 85), target) pygame.draw.ellipse(screen, (255, 255, 255), target.inflate(-30, -30)) screen.blit(font.render(f"Hits {hits}", True, (230, 232, 235)), (12, 10)) pygame.display.flip() clock.tick(60) pygame.quit()
Your turn
Change the programs above in the game panel, then press Run.
- Make the ball a little faster every time it bounces off the paddle.
- Shrink the target by a few pixels with every hit (
target.inflate_ip(-4, -4)). - Add a second ball with its own
dxanddy.
Breakout is this lesson grown up: a ball, a paddle and sixty Rects to hit. Open Breakout.