Menu
CoddyTech

Pixel-Perfect Sprites

Part of the Sprites and Images section of Coddy's Pygame journey. Lesson 23 of 36.

Sprites can use masks too, and pygame does the offset for you. Give each sprite a self.mask, made once in __init__:

self.mask = pygame.mask.from_surface(self.image)

Then pass pygame.sprite.collide_mask as a fourth argument to spritecollide:

hits = pygame.sprite.spritecollide(player, balls, False, pygame.sprite.collide_mask)

The fourth argument is the test used for each pair. Without it the test is the Rects. With collide_mask it is the masks, placed by each sprite's Rect. groupcollide takes the same fifth argument.

Mask tests cost more than Rect tests, but overlap only compares the pixels where the two Rects overlap, so pairs far apart are quick. For a few dozen sprites you will not notice the cost. If a sprite has no mask, collide_mask makes one from its image on every test, so set it in __init__.

challenge icon

Challenge

Medium
  1. Give Player and Ball a self.mask made from self.image.
  2. Pass pygame.sprite.collide_mask to spritecollide, so Hit! shows only while the ship's picture touches a ball.

Try it yourself

import pygame

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

ship_image = pygame.image.load("assets/images/ship.png").convert_alpha()
ball_image = pygame.image.load("assets/images/spike_ball.png").convert_alpha()
font = pygame.font.Font(None, 40)


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = ship_image
        self.rect = self.image.get_rect(topleft=(420, 320))

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.rect.x -= 4
        if keys[pygame.K_RIGHT]:
            self.rect.x += 4
        if keys[pygame.K_UP]:
            self.rect.y -= 4
        if keys[pygame.K_DOWN]:
            self.rect.y += 4


class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = ball_image
        self.rect = self.image.get_rect(center=(x, y))


player = Player()
balls = pygame.sprite.Group(Ball(160, 240), Ball(320, 240), Ball(480, 240))

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

    player.update()
    hits = pygame.sprite.spritecollide(player, balls, False)

    screen.fill((20, 22, 40))
    balls.draw(screen)
    screen.blit(player.image, player.rect)
    if hits:
        text = font.render("Hit!", True, (240, 240, 240))
        screen.blit(text, (10, 10))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

All lessons in Sprites and Images

Practice on your own: Pygame playground