[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[pygame] Pixel perfect collision detection sugestion



Hi,

I've written a small and fast pixel perfect collision detection algorithm
that I thought I might share with everyone.

I've been using this algorithm in several other languages (C#, Java, Flash)
with excelent performance.

The example code is actually a suggestion to be added in the Sprite module,
but if the PyGame developers doesn't like it it's very easy to just add to
your code.

Please have a look and tell me what you think!

Best Regards
/John Eriksson
#/usr/bin/env python

import pygame
from pygame.locals import *

# --- THIS IS THE PROPOSED CODE ADDITIONS (4 methods) ---
def _pixelPerfectCollisionDetection(sp1,sp2):
    """
    Internal method used for pixel perfect collision detection.
    """
    rect1 = sp1.rect;     
    rect2 = sp2.rect;                            
    rect  = rect1.clip(rect2)
                    
    hm1 = sp1.hitmask
    hm2 = sp2.hitmask
            
    x1 = rect.x-rect1.x
    y1 = rect.y-rect1.y
    x2 = rect.x-rect2.x
    y2 = rect.y-rect2.y

    for r in range(0,rect.height):      
        for c in range(0,rect.width):
            if hm1[c+x1][r+y1] & hm2[c+x2][r+y2]:
                return 1        

    return 0

def spritecollide_pp(sprite, group, dokill):
    """pygame.sprite.spritecollide_pp(sprite, group, dokill) -> list
       pixel perfect collision detection between sprite and group

       given a sprite and a group of sprites, this will
       return a list of all the sprites that intersect
       the given sprite.
       all sprites must have a "hitmap" value, which is a 2d array 
       that contains a value larger than zero for all pixels that 
       can collide. the "hitmap" 2d array can be set by using 
       pygame.surfarray.array_colorkey() or pygame.surfarray.array_alpha(). 
       all sprites must have a "rect" value, which is a
       rectangle of the sprite area.if the dokill argument
       is true, the sprites that do collide will be
       automatically removed from all groups."""
    crashed = []
    spritecollide = sprite.rect.colliderect
    if dokill:
        for s in group.sprites():
            if spritecollide(s.rect):
                if _pixelPerfectCollisionDetection(sprite,s):
                    s.kill()
                    crashed.append(s)
    else:
        for s in group.sprites():
            if _pixelPerfectCollisionDetection(sprite,s):
                crashed.append(s)
    return crashed


def groupcollide_pp(groupa, groupb, dokilla, dokillb):
    """pygame.sprite.groupcollide_pp(groupa, groupb, dokilla, dokillb) -> dict
       collision detection between group and group by using pixel perfect 
       collision detection

       given two groups, this will find the intersections
       between all sprites in each group. it returns a
       dictionary of all sprites in the first group that
       collide. the value for each item in the dictionary
       is a list of the sprites in the second group it
       collides with. the two dokill arguments control if
       the sprites from either group will be automatically
       removed from all groups."""
    crashed = {}
    SC = spritecollide_pp
    if dokilla:
        for s in groupa.sprites():
            c = SC(s, groupb, dokillb)
            if c:
                crashed[s] = c
                s.kill()
    else:
        for s in groupa.sprites():
            c = SC(s, groupb, dokillb)
            if c:
                crashed[s] = c
    return crashed

def spritecollideany_pp(sprite, group):
    """pygame.sprite.spritecollideany_pp(sprite, group) -> sprite
       finds any sprites that collide by using pixel perfect 
       collision detection

       given a sprite and a group of sprites, this will
       return return any single sprite that collides with
       with the given sprite. If there are no collisions
       this returns None.

       if you don't need all the features of the
       spritecollide function, this function will be a
       bit quicker.

       all sprites must have a "hitmap" value, which is a 2d array 
       that contains a value larger than zero for all pixels that 
       can collide. the "hitmap" 2d array can be set by using 
       pygame.surfarray.array_colorkey() or pygame.surfarray.array_alpha(). 

       all sprites must have a "rect" value, which is a
       rectangle of the sprite area.       
       """
    spritecollide = sprite.rect.colliderect
    for s in group.sprites():
        if spritecollide(s.rect):
            if _pixelPerfectCollisionDetection(sprite,s):
                return s
    return None

# --- END ---                             

def makeFoo(x,y):
    img = pygame.Surface([20,20])
    img = img.convert()
    img.fill((0xff, 0xff, 0xff))
    img.set_colorkey((0xff, 0xff, 0xff), RLEACCEL)
    pygame.draw.line(img, (255,0,0), (0,0), (19,19), 3)
    pygame.draw.line(img, (255,0,0), (0,19), (19,0), 3)
    foo = pygame.sprite.Sprite()
    foo.image = img
    foo.rect = img.get_rect()
    foo.rect.centerx = x
    foo.rect.centery = y
    # set the hitmask value
    foo.hitmask = pygame.surfarray.array_colorkey(img)
    return foo

def main():
    # init pygame
    pygame.init()    

    # setup the display
    screen = pygame.display.set_mode((300, 300),1)
    pygame.display.set_caption('PixelPerfectCollision')        

    # make a boom text surface
    font = pygame.font.Font(None, 72)
    boom_text = font.render("BOOOOM!", 1, (0, 0, 0))
    boom_textpos = boom_text.get_rect()
    boom_textpos.centerx = 150
    boom_textpos.centery = 50

    # create a very simple hero sprite
    img = pygame.Surface([20,20])
    img = img.convert()
    img.fill((0xff, 0xff, 0xff))
    img.set_colorkey((0xff, 0xff, 0xff), RLEACCEL)
    pygame.draw.line(img, (0,0,255), (0,0), (19,19), 3)
    pygame.draw.line(img, (0,0,255), (0,19), (19,0), 3)
    hero = pygame.sprite.Sprite()
    hero.image = img
    hero.rect = img.get_rect()
    hero.rect.centerx = 150
    hero.rect.centery = 150
    # you can use either array_colorkey() or array_alpha() as hitmask
    hero.hitmask = pygame.surfarray.array_colorkey(img)
    hero_group = pygame.sprite.RenderPlain(hero)

    # create some foo sprites
    foo_group = pygame.sprite.RenderPlain()
    foo_group.add(makeFoo(150-40,150-40))
    foo_group.add(makeFoo(150-40,150+40))
    foo_group.add(makeFoo(150+40,150-40))
    foo_group.add(makeFoo(150+40,150+40))

    clock = pygame.time.Clock()
    dx = 0
    dy = 0

    while 1:
        clock.tick(30)        
    
        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    return
                elif event.key == K_UP:
                    dy = -1
                elif event.key == K_DOWN:
                    dy = 1
                elif event.key == K_LEFT:
                    dx = -1
                elif event.key == K_RIGHT:
                    dx = 1
            elif event.type == KEYUP:
                if event.key == K_ESCAPE:
                    return
                elif event.key == K_UP and dy == -1:
                    dy = 0
                elif event.key == K_DOWN and dy == 1:
                    dy = 0
                elif event.key == K_LEFT and dx == -1:
                    dx = 0
                elif event.key == K_RIGHT and dx == 1:
                    dx = 0

        # update the hero location
        hero.rect.x += dx
        hero.rect.y += dy        

        # clear screen
        screen.fill((255,255,255),(0,0,300,300))           
        
        # check for collisions
        li = spritecollide_pp(hero, foo_group, 0)
        if len(li):
            screen.blit(boom_text, boom_textpos)                    
                
        # update the screen                                
        foo_group.draw(screen)
        hero_group.draw(screen)            
        pygame.display.flip()
             
 
#this calls the 'main' function when this script is executed
if __name__ == '__main__': main()