[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()