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"