pygame is
Python
Simple DirectMedia Layer
 
 
pygame.org is
Site Swing
Wiki

FastPixelPerfect

      
Search:  
 
 

This function checks if two objects truly collide. This is useful when you have two objects whose rects collide even when they are not actually touching each other, and you want to know when the actual objects collide. This code will check 1000 collisions between 2 rects that are 25 * 10 in size in about 0.015 to 0.07 seconds. PyGame's rect.colliderect will do the same collisions in 0.001 seconds.

All objects must have a 'rect' attribute and a 'hitmask' attribute. The 'blank' attribute is voluntary, as 0 will most likely be its value, and using the pixel as a boolean statement would then work to the same effect. The hitmask can be any type of surfarray, but you will most likely use array_alpha(image) or array_colorkey(image).

The following is a revision of Joshua Gram's version, by Alfonso Crawford.

def PixelPerfectCollision(obj1, obj2):
    """
    If the function finds a collision, it will return True;
    if not, it will return False. If one of the objects is
    not the intended type, the function instead returns None.
    """
    try:
        #create attributes
        rect1, mask1, blank1 = obj1.rect, obj1.hitmask, obj1.blank
        rect2, mask2, blank2 = obj2.rect, obj2.hitmask, obj2.blank
        #initial examination
        if rect1.colliderect(rect2) is False:
            return False
    except AttributeError:
        return None
 
    #get the overlapping area
    clip = rect1.clip(rect2)
 
    #find where clip's top-left point is in both rectangles
    x1 = clip.left - rect1.left
    y1 = clip.top  - rect1.top
    x2 = clip.left - rect2.left
    y2 = clip.top  - rect2.top
 
    #cycle through clip's area of the hitmasks
    for x in range(clip.width):
        for y in range(clip.height):
            #returns True if neither pixel is blank
            if mask1[x1+x][y1+y] is not blank1 and \
               mask2[x2+x][y2+y] is not blank2:
                return True
 
    #if there was neither collision nor error
    return False

Note: You can see an example using an algorithm almost identical to Joshua's at John Eriksson's project, PixelPerfect.

Here is a cleaned up and bug-fixed version of the original by RB[0],
that now uses the for loop(as suggested here by Joshua Grams), instead of while loops, which are slower.
There are also a few helper functions that are used to create hitmasks without using pygames surfarray module, as it is currently broken(at least for me), these dont even require Numeric.

This pixelperfect implementation is anywhere from 1.2 to 2.5 times faster than the one shown above.

def check_collision(obj1,obj2):
    """checks if two objects have collided, using hitmasks"""
    try:rect1, rect2, hm1, hm2 = obj1.rect, obj2.rect, obj1.hitmask, obj2.hitmask
    except AttributeError:return False
    rect=rect1.clip(rect2)
    if rect.width==0 or rect.height==0:
        return False
    x1,y1,x2,y2 = rect.x-rect1.x,rect.y-rect1.y,rect.x-rect2.x,rect.y-rect2.y
    for x in xrange(rect.width):
        for y in xrange(rect.height):
            if hm1[x1+x][y1+y] and hm2[x2+x][y2+y]:return True
            else:continue
    return False
 
def get_colorkey_hitmask(image, rect, key=None):
    """returns a hitmask using an image's colorkey.
       image->pygame Surface,
       rect->pygame Rect that fits image,
       key->an over-ride color, if not None will be used instead of the image's colorkey"""
    if key==None:colorkey=image.get_colorkey()
    else:colorkey=key
    mask=[]
    for x in range(rect.width):
        mask.append([])
        for y in range(rect.height):
            mask[x].append(not image.get_at((x,y)) == colorkey)
    return mask
 
def get_alpha_hitmask(image, rect, alpha=0):
    """returns a hitmask using an image's alpha.
       image->pygame Surface,
       rect->pygame Rect that fits image,
       alpha->the alpha amount that is invisible in collisions"""
    mask=[]
    for x in range(rect.width):
        mask.append([])
        for y in range(rect.height):
            mask[x].append(not image.get_at((x,y))[3]==alpha)
    return mask
 
def get_colorkey_and_alpha_hitmask(image, rect, key=None, alpha=0):
    """returns a hitmask using an image's colorkey and alpha."""
    mask=[]
    for x in range(rect.width):
        mask.append([])
        for y in range(rect.height):
            mask[x].append(not (image.get_at((x,y))[3]==alpha or\
                                image.get_at((x,y))==colorkey))
    return mask
 
def get_full_hitmask(image, rect):
    """returns a completely full hitmask that fits the image,
       without referencing the images colorkey or alpha."""
    mask=[]
    for x in range(rect.width):
        mask.append([])
        for y in range(rect.height):
            mask[x].append(True)
    return mask
For an example of use:
store the above code as pixelperfect.py,
download this image http://www.mediafire.com/?f4yz4mm2o1m and save it as carrots.png, in the same directory
Now, create a new file(in the same folder) with the following code:
import pygame, pixelperfect, time
from pygame.locals import *
from pixelperfect import *
 
def load_image(name, colorkey=None, alpha=False):
    """loads an image into memory"""
    try:
        image = pygame.image.load(name)
    except pygame.error, message:
        print 'Cannot load image:', name
        raise SystemExit, message
    if alpha:image = image.convert_alpha()
    else:image=image.convert()
    if colorkey is not None:
        if colorkey is -1:
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image, image.get_rect()
 
class my_object(object):
    def __init__(self, image,colorkey=None,alpha=None):
        self.image, self.rect=load_image(image,colorkey=colorkey, alpha=alpha)
        if colorkey and alpha:
            self.hitmask=get_colorkey_and_alpha_hitmask(self.image, self.rect,
                                                        colorkey, alpha)
        elif colorkey:
            self.hitmask=get_colorkey_hitmask(self.image, self.rect,
                                              colorkey)
        elif alpha:
            self.hitmask=get_alpha_hitmask(self.image, self.rect,
                                           alpha)
        else:
            self.hitmask=get_full_hitmask(self.image, self.rect)
 
pygame.init()
screen = pygame.display.set_mode([200,200])
screen.fill([255,255,255])
 
 
a=my_object('carrots.png',-1,None)
a.rect.center=(25,25)
 
b=my_object('carrots.png',None,True)
b.rect.center=(50,50)
 
screen.blit(a.image, a.rect)
screen.blit(b.image, b.rect)
pygame.display.flip()
 
def main():
    av=0
    for i in xrange(999):
        st_time=time.clock()
        check_collision(a, b)
        av+=time.clock()-st_time
 
    print "time:", av, check_collision(a, b)
 
main()

If you have any suggestions, please email me at "roebros (at) gmail.com"

spotlight

 
our projects
pygame.org welcomes all python game, art, music, sound, video and multimedia projects. If they use pygame or not.
 
recent releases
Apr 21, 2014


Apr 19, 2014

Apr 16, 2014

Apr 13, 2014

Apr 9, 2014

Mar 18, 2014


Mar 15, 2014


Mar 14, 2014

Mar 13, 2014

... more!
 
for pygame related questions, comments, and suggestions, please see help (lists, irc)