Collisions

From TRCCompSci - AQA Computer Science
Revision as of 15:14, 14 March 2018 by Admin (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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")