Pixel perfect collision detection with PygameZero

PygameZero is a great framework to help young coders step up from block-based coding in Scratch. However it doesn’t have all the features of Scratch: one notable omission is pixel perfect collision detection.

As the name suggests, PygameZero is based on Pygame and this does have many collision detection methods, so can we use Pygame features to improve PygameZero?

The answer is Yes! It took a bit of digging, and here’s how to do it…

Add this import at the start of your code:

from pygame import mask

Then add this function:

def collide_pixels(actor1, actor2):

    # Get masks for pixel perfect collision detection:
    for a in [actor1, actor2]:
        if not hasattr(a, 'mask'):
            a.mask = mask.from_surface(images.load(a.image))

    # Check rectangles first, this is faster
        if not actor1.colliderect(actor2):
            return None

    # Offsets based on current positions of actors
    xoffset = int(actor2.left - actor1.left)
    yoffset = int(actor2.top - actor1.top)

    # Check for overlap => a collision
    return actor1.mask.overlap(actor2.mask, (xoffset, yoffset))

Now you can call this function with two actors and you’ll get pixel perfect collision detection based on the transparent pixels in each image (the alpha channel).

Here’s an example program that shows the function above in action.

You’ll need 3 PNG images (with transparency):

  • cave.png, which is a 500x500 pixel background with some brush strokes to mark the walls
  • me.png, which is our game character (I just used a dot for testing)
  • me-hit.png, a hit version of our character (I just used a different colour).

Here’s the complete program:

from pygame import mask

WIDTH = 500
HEIGHT = 500

cave = Actor('cave', (250,250))
me = Actor('me', (250,250))
me.speed_x = me.speed_y = 0

def collide_pixels(actor1, actor2):

    # Get masks for pixel perfect collision detection:
    for a in [actor1, actor2]:
        if not hasattr(a, 'mask'):
            a.mask = mask.from_surface(images.load(a.image))

    # Check rectangles first, this is faster
    if not actor1.colliderect(actor2):
        return None

    # Offsets based on current positions of actors
    xoffset = int(actor2.left - actor1.left)
    yoffset = int(actor2.top - actor1.top)

    # Check for overlap => a collision
    return actor1.mask.overlap(actor2.mask, (xoffset, yoffset))

def draw():
    cave.draw()
    me.draw()

def update():
    screen.clear()
    me.x += me.speed_x
    me.y += me.speed_y
    if collide_pixels(cave, me):
        print("collission!")
        me.image = 'me-hit'
    else:
        me.image = 'me'

def on_key_up(key):
    if key == keys.LEFT:
        me.speed_x += -1
    if key == keys.RIGHT:
        me.speed_x += 1
    if key == keys.UP:
        me.speed_y += -1
    if key == keys.DOWN:
        me.speed_y += 1

When you run this program you should see the me sprite change costume when it hits the walls. Now you can see how you can use collide_pixels in your own games to provide better gameplay.