Simple Collision Detection Example in Pygame

If you haven’t read Simple Animation In Pygame yet, go check it out. Because I’ll reuse many of the code in that post.

Let’s get started by showing the code:

import pygame, sys
import random as rd
from pygame.locals import *

# set up pygame
pygame.init()

# set up window
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
window_surface = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT), 0, 32)
pygame.display.set_caption('Animation')
game_clock = pygame.time.Clock()

# set up direction
DOWN_LEFT = 1
DOWN_RIGHT = 2
UP_LEFT = 3
UP_RIGHT = 4
directions = [DOWN_LEFT, DOWN_RIGHT, UP_LEFT, UP_RIGHT]
MOVEMENT_SPEED = 4

# set up colors
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
SIZE = 20
counter = 0

bouncer = {'rect': pygame.Rect(300, 80, 50, 80), 'dir':UP_RIGHT}
foods = []
for i in xrange(20):
    foods.append(pygame.Rect(rd.randint(0, WINDOW_WIDTH), rd.randint(0, WINDOW_HEIGHT), SIZE, SIZE))

def check_overlap(rect1, rect2):
    """
    :param rect1: pygame rectangle
    :param rect2: pygame rectangle
    :return: True if rect1 overlap rect2
    """
    # check if one of four corners of rect1 is inside rect2
    for a, b in ([rect1, rect2], [rect2, rect1]):
        if is_point_inside_rect(a.left, a.top, b) \
        or is_point_inside_rect(a.left, a.bottom, b) \
        or is_point_inside_rect(a.right, a.top, b) \
        or is_point_inside_rect(a.right, a.bottom, b):
            return True
    else:
        return False

def is_point_inside_rect(x, y, rect):
    return (x > rect.left and x < rect.right and y > rect.top and y < rect.bottom)

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

    window_surface.fill(BLACK) # set background color of window to black

    if bouncer['dir'] == DOWN_LEFT:
        bouncer['rect'].left -= MOVEMENT_SPEED
        bouncer['rect'].top += MOVEMENT_SPEED
    if bouncer['dir'] == DOWN_RIGHT:
        bouncer['rect'].left += MOVEMENT_SPEED
        bouncer['rect'].top += MOVEMENT_SPEED
    if bouncer['dir'] == UP_LEFT:
        bouncer['rect'].left -= MOVEMENT_SPEED
        bouncer['rect'].top -= MOVEMENT_SPEED
    if bouncer['dir'] == UP_RIGHT:
        bouncer['rect'].left += MOVEMENT_SPEED
        bouncer['rect'].top -= MOVEMENT_SPEED

    # check out of bound
    if bouncer['rect'].top < 0: # over the top of window
        if bouncer['dir'] == UP_LEFT:
            bouncer['dir'] = rd.choice(directions) #DOWN_LEFT
        if bouncer['dir'] == UP_RIGHT:
            bouncer['dir'] = rd.choice(directions) #DOWN_RIGHT
    if bouncer['rect'].bottom > WINDOW_HEIGHT: # below the bottom of window
        if bouncer['dir'] == DOWN_LEFT:
            bouncer['dir'] = rd.choice(directions) #UP_LEFT
        if bouncer['dir'] == DOWN_RIGHT:
            bouncer['dir'] = rd.choice(directions) #UP_RIGHT
    if bouncer['rect'].left < 0: # out of left side
        if bouncer['dir'] == DOWN_LEFT:
            bouncer['dir'] = rd.choice(directions) #DOWN_RIGHT
        if bouncer['dir'] == UP_LEFT:
            bouncer['dir'] = rd.choice(directions) #UP_RIGHT
    if bouncer['rect'].right > WINDOW_WIDTH: # out of right side
        if bouncer['dir'] == DOWN_RIGHT:
            bouncer['dir'] = rd.choice(directions) #DOWN_LEFT
        if bouncer['dir'] == UP_RIGHT:
            bouncer['dir'] = rd.choice(directions) #UP_LEFT
    # draw the bouncer
    pygame.draw.rect(window_surface, GREEN, bouncer['rect'])

    # check collisions between foods and bouncer
    for food in foods[:]:
        if check_overlap(food, bouncer['rect']):
            foods.remove(food)

    # draw 'foods'
    for food in foods:
        pygame.draw.rect(window_surface, BLUE, food)

    # after each 40 loops, we add new food
    counter += 1
    if counter > 20:
        counter = 0
        foods.append(pygame.Rect(rd.randint(0, WINDOW_WIDTH), rd.randint(0, WINDOW_HEIGHT), SIZE, SIZE))

    # draw the window onto the screen
    pygame.display.update()
    # sleep 0.02 second (around 50 frame per seconds)
    game_clock.tick(40) # 40 frame per second

What we’re doing is we create a bouncer, and many boxes. Whenever the bouncer hit a box, the box will be removed from the screen.
What we should focus here is the two important functions, check_overlap and is_point_inside_rect.

Let’s go with is_point_inside_rect_first, since it’s used by check_overlap.
This picture virtually how to check a point’s inside or outside of a rectangle:

capture3

A point (x,y) is inside a rectangle if its x is bigger than left and smaller than right side value of rectangle, same goes for top and bottom. In the above pictures, there are three point inside the rectangle.

Now let’s go to the check_overlap function. This function check if four corners of a rect1 is inside rect2 [rect1, rect2] and vice versa [rect2, rect1].

The code inside game loop has been changed a little bit. Line 63 to 74 move the bouncer, line 77 to 96 check if the bouncer hit one of the four sides of the window, if it is, randomly change the direction of it. The for loop at line 101 check if the bouncer collide with any food, and if it collide, it will eat the food, the food will disappear. Notice that we get food from food[:] not food. This is because in Python you should not remove or add a list while iterating through it. For example it’s difficult if you’re counting the number of jelly beans in a jar while someone’s adding or removing jelly beans, same thing goes for list.

Line 111 check if counter > 20, if it is, add more food into foods. The last line , game_clock.tick(), I just simply understand that it kind of sets the frame rate of the game. No matter how fast or slow your computer is, the while loop will always run 40 times per second.

Result of the code above:

capture

That’s it for this tutorial. Feel free to donate, many thanks.

Advertisements