Collisions
Rectangular Collision
The sprite class has built in methods to handle collision detection, one of the methods is collide_rect. For this example we are going to create 2 sprite based classes first, one for the enemy and one for the player:
class HeroSprite(pygame.sprite.Sprite):
image = None
def update(self, new_position):
self.rect.topleft = new_position
def draw(self, screen):
screen.blit(self.image, self.rect)
def __init__(self, initial_position):
pygame.sprite.Sprite.__init__(self) # run the init for the base class
if HeroSprite.image is None:
HeroSprite.image = pygame.image.load("hero.png") # load image for sprite
self.image = HeroSprite.image # set image for the sprite
self.rect = self.image.get_rect() # set rectangle for sprite
self.rect.topleft = initial_position # set position to value passed into method
The code above will load the image for the sprite, set its position and its bounds (rect). We can create another for the enemy:
class EnemySprite(pygame.sprite.Sprite):
image = None
def update(self, new_position):
self.rect.topleft = new_position
def draw(self, screen):
screen.blit(self.image, self.rect)
def __init__(self, initial_position):
pygame.sprite.Sprite.__init__(self) # run the init for the base class
if EnemySprite.image is None:
EnemySprite.image = pygame.image.load("Enemy.png") # load image for sprite
self.image = EnemySprite.image # set image for the sprite
self.rect = self.image.get_rect() # set rectangle for sprite
self.rect.topleft = initial_position # set position to value passed into method
Before the game loop you can now create an instance of each class, and run the init method for each:
e = EnemySprite
e.__init__(e, [100,100])
pos = [0,0]
h = HeroSprite
h.__init__(h, pos)
Now in the game loop we can make the player move (you can't have collisions if you can't move). h.update(h, pos) will change the position of the hero object:
for events in pygame.event.get(): #get all pygame events
if events.type == pygame.KEYDOWN:
if events.key == pygame.K_LEFT:
pos[0]=pos[0]-10 # pos[0] should be first item in the tuple
elif events.key == pygame.K_RIGHT:
pos[0]=pos[0]+10 # pos[0] should be first item in the tuple
elif events.key == pygame.K_UP:
pos[1]=pos[1]-10 # pos[1] should be second item in the tuple
elif events.key == pygame.K_DOWN:
pos[1]=pos[1]+10 # pos[1] should be second item in the tuple
h.update(h, pos)
Now we have moved you can check if this position collides with another sprite:
for events in pygame.event.get(): #get all pygame events
if events.type == pygame.KEYDOWN:
if events.key == pygame.K_LEFT:
pos[0]=pos[0]-10 # pos[0] should be first item in the tuple
elif events.key == pygame.K_RIGHT:
pos[0]=pos[0]+10 # pos[0] should be first item in the tuple
elif events.key == pygame.K_UP:
pos[1]=pos[1]-10 # pos[1] should be second item in the tuple
elif events.key == pygame.K_DOWN:
pos[1]=pos[1]+10 # pos[1] should be second item in the tuple
h.update(h, pos)
if pygame.sprite.collide_rect(b, h):
print("collision")
else:
print("no collision")
Remember for this to work we also need to blit each object to the screen (or if the class as a draw method) and we also need to update the display:
h.draw(self, SCREEN)
e.draw(self, Screen)
pygame.display.update()
Per Pixel Collision
You can use masks in pygame, this will create an image of a single colour which essentially fills the non transparent parts of the sprite. Follow the instructions above to create a rectangular based collision example. in the init method for each sprite class add the following line:
self.mask = pygame.mask.from_surface(self.image)
Now we need to create a sprite group, this is because the method we will use in the next section can only check per pixel collision between a sprite and a group of sprites. So add the following code after you have created an instance of your enemy:
enemies = pygame.sprite.Group()
enemies.add_internal(e) # assuming e is the name of your enemy instance
Now change the if statement which checks for a collision to use this instead:
if pygame.sprite.spritecollide(h, enemies, False, pygame.sprite.collide_mask):
print ("sprites have collided!")
else:
print("no collision")
The False in the code above is to not kill the collided sprites, if you set this to true the collided sprites will be killed.
Undo Action If Collision
for either method, we are only printing a message to say if a collision has happened or not. We can undo the player movement instead, so have a look at this code from the game loop:
for events in pygame.event.get(): #get all pygame events
if events.type == pygame.KEYDOWN:
temp = h.rect.topleft # get current position of object
if events.key == pygame.K_LEFT:
pos[0]=pos[0]-10
elif events.key == pygame.K_RIGHT:
pos[0]=pos[0]+10
elif events.key == pygame.K_UP:
pos[1]=pos[1]-10
elif events.key == pygame.K_DOWN:
pos[1]=pos[1]+10
h.update(h, pos) # update the new position after movement
if pygame.sprite.spritecollide(h, enemies, False, pygame.sprite.collide_mask):
print("collision")
pos = [temp[0],temp[1]] # collision happened so reset pos to before movement
h.update(h, temp) # update the object with the old position to undo movement
else:
print("no collision")