Difference between revisions of "Collisions"

From TRCCompSci - AQA Computer Science
Jump to: navigation, search
(Created page with "==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 sp...")
 
 
(6 intermediate revisions by the same user not shown)
Line 8: Line 8:
 
     def update(self, new_position):
 
     def update(self, new_position):
 
         self.rect.topleft = new_position
 
         self.rect.topleft = new_position
 +
 +
    def draw(self, screen):
 +
        screen.blit(self.image, self.rect)
  
 
     def __init__(self, initial_position):
 
     def __init__(self, initial_position):
Line 16: Line 19:
 
         self.rect = self.image.get_rect() # set rectangle for sprite
 
         self.rect = self.image.get_rect() # set rectangle for sprite
 
         self.rect.topleft = initial_position # set position to value passed into method
 
         self.rect.topleft = initial_position # set position to value passed into method
        self.going_down = True # Start going downwards
 
        self.next_update_time = 0 # update() hasn’t been called yet.
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 28: Line 29:
 
     def update(self, new_position):
 
     def update(self, new_position):
 
         self.rect.topleft = new_position
 
         self.rect.topleft = new_position
 +
 +
    def draw(self, screen):
 +
        screen.blit(self.image, self.rect)
  
 
     def __init__(self, initial_position):
 
     def __init__(self, initial_position):
 
         pygame.sprite.Sprite.__init__(self) # run the init for the base class
 
         pygame.sprite.Sprite.__init__(self) # run the init for the base class
 
         if EnemySprite.image is None:
 
         if EnemySprite.image is None:
             HEnemySprite.image = pygame.image.load("Enemy.png") # load image for sprite
+
             EnemySprite.image = pygame.image.load("Enemy.png") # load image for sprite
 
         self.image = EnemySprite.image # set image for the sprite
 
         self.image = EnemySprite.image # set image for the sprite
 
         self.rect = self.image.get_rect() # set rectangle for sprite
 
         self.rect = self.image.get_rect() # set rectangle for sprite
 
         self.rect.topleft = initial_position # set position to value passed into method
 
         self.rect.topleft = initial_position # set position to value passed into method
        self.going_down = True # Start going downwards
 
        self.next_update_time = 0 # update() hasn’t been called yet.
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 51: Line 53:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Now in the game loop we can make the player move (you can't have collisions if you can't move):
+
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:
 +
 
 +
<syntaxhighlight lang=python>
 +
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)
 +
</syntaxhighlight>
 +
 
 +
Now we have moved you can check if this position collides with another sprite:
  
 
<syntaxhighlight lang=python>
 
<syntaxhighlight lang=python>
Line 64: Line 82:
 
             elif events.key == pygame.K_DOWN:
 
             elif events.key == pygame.K_DOWN:
 
                 pos[1]=pos[1]+10 # pos[1] should be second item in the tuple
 
                 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")
 +
</syntaxhighlight>
 +
 +
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:
 +
 +
<syntaxhighlight lang=python>
 +
h.draw(self, SCREEN)
 +
e.draw(self, Screen)
 +
pygame.display.update()
 +
</syntaxhighlight>
 +
 +
==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:
 +
 +
<syntaxhighlight lang=python>
 +
    self.mask = pygame.mask.from_surface(self.image)
 +
</syntaxhighlight>
 +
 +
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:
 +
 +
<syntaxhighlight lang=python>
 +
enemies = pygame.sprite.Group()
 +
enemies.add_internal(e) # assuming e is the name of your enemy instance
 +
</syntaxhighlight>
 +
 +
Now change the if statement which checks for a collision to use this instead:
 +
 +
<syntaxhighlight lang=python>
 +
if pygame.sprite.spritecollide(h, enemies, False, pygame.sprite.collide_mask):
 +
    print ("sprites have collided!")
 +
else:
 +
    print("no collision")
 +
</syntaxhighlight>
 +
 +
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:
 +
 +
<syntaxhighlight lang=python>
 +
    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
  
        h.update(h, pos)
+
            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")
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 15:14, 14 March 2018

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