Search code examples
pythonpygamecollision-detectionpygame-surfacepygame2

How do I detect collision in pygame?


I have made a list of bullets and a list of sprites using the classes below. How do I detect if a bullet collides with a sprite and then delete that sprite and the bullet?

#Define the sprite class
class Sprite:

    def __init__(self,x,y, name):
        self.x=x

        self.y=y

        self.image = pygame.image.load(name)

        self.rect = self.image.get_rect()

    def render(self):
        window.blit(self.image, (self.x,self.y))


# Define the bullet class to create bullets          
class Bullet:

    def __init__(self,x,y):
        self.x = x + 23
        self.y = y
        self.bullet = pygame.image.load("user_bullet.BMP")
        self.rect = self.bullet.get_rect()
        
    def render(self):
        window.blit(self.bullet, (self.x, self.y))

Solution

  • In PyGame, collision detection is done using pygame.Rect objects. The Rect object offers various methods for detecting collisions between objects. Even the collision between a rectangular and circular object such as a paddle and a ball can be detected by a collision between two rectangular objects, the paddle and the bounding rectangle of the ball.

    Some examples:

    • pygame.Rect.collidepoint:

      Test if a point is inside a rectangle

      repl.it/@Rabbid76/PyGame-collidepoint

      import pygame
      
      pygame.init()
      window = pygame.display.set_mode((250, 250))
      rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(100, 100)
      
      run = True
      while run:
          for event in pygame.event.get():
              if event.type == pygame.QUIT:
                  run = False
      
          point = pygame.mouse.get_pos()
          collide = rect.collidepoint(point)
          color = (255, 0, 0) if collide else (255, 255, 255)
      
          window.fill(0)
          pygame.draw.rect(window, color, rect)
          pygame.display.flip()
      
      pygame.quit()
      exit()
      
    • pygame.Rect.colliderect

      Test if two rectangles overlap

      See also How to detect collisions between two rectangular objects or images in pygame

      repl.it/@Rabbid76/PyGame-colliderect

      colliderect

      import pygame
      
      pygame.init()
      window = pygame.display.set_mode((250, 250))
      rect1 = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
      rect2 = pygame.Rect(0, 0, 75, 75)
      
      run = True
      while run:
          for event in pygame.event.get():
              if event.type == pygame.QUIT:
                  run = False
      
          rect2.center = pygame.mouse.get_pos()
          collide = rect1.colliderect(rect2)
          color = (255, 0, 0) if collide else (255, 255, 255)
      
          window.fill(0)
          pygame.draw.rect(window, color, rect1)
          pygame.draw.rect(window, (0, 255, 0), rect2, 6, 1)
          pygame.display.flip()
      
      pygame.quit()
      exit()
      

    Furthermore, pygame.Rect.collidelist and pygame.Rect.collidelistall can be used for the collision test between a rectangle and a list of rectangles. pygame.Rect.collidedict and pygame.Rect.collidedictall can be used for the collision test between a rectangle and a dictionary of rectangles.

    The collision of pygame.sprite.Sprite and pygame.sprite.Group objects, can be detected by pygame.sprite.spritecollide(), pygame.sprite.groupcollide() or pygame.sprite.spritecollideany(). When using these methods, the collision detection algorithm can be specified by the collided argument:

    The collided argument is a callback function used to calculate if two sprites are colliding.

    Possible collided callables are collide_rect, collide_rect_ratio, collide_circle, collide_circle_ratio, collide_mask

    Some examples:

    • pygame.sprite.spritecollide()

      repl.it/@Rabbid76/PyGame-spritecollide

      import pygame
      
      pygame.init()
      window = pygame.display.set_mode((250, 250))
      
      sprite1 = pygame.sprite.Sprite()
      sprite1.image = pygame.Surface((75, 75))
      sprite1.image.fill((255, 0, 0))
      sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
      sprite2 = pygame.sprite.Sprite()
      sprite2.image = pygame.Surface((75, 75))
      sprite2.image.fill((0, 255, 0))
      sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
      
      all_group = pygame.sprite.Group([sprite2, sprite1])
      test_group = pygame.sprite.Group(sprite2)
      
      run = True
      while run:
          for event in pygame.event.get():
              if event.type == pygame.QUIT:
                  run = False
      
          sprite1.rect.center = pygame.mouse.get_pos()
          collide = pygame.sprite.spritecollide(sprite1, test_group, False)
      
          window.fill(0)
          all_group.draw(window)
          for s in collide:
              pygame.draw.rect(window, (255, 255, 255), s.rect, 5, 1)
          pygame.display.flip()
      
      pygame.quit()
      exit()
      

    For a collision with masks, see How can I make a collision mask? or Pygame mask collision

    See also Collision and Intersection

    • pygame.sprite.spritecollide() / collide_circle

      repl.it/@Rabbid76/PyGame-spritecollidecollidecircle

      import pygame
      
      pygame.init()
      window = pygame.display.set_mode((250, 250))
      
      sprite1 = pygame.sprite.Sprite()
      sprite1.image = pygame.Surface((80, 80), pygame.SRCALPHA)
      pygame.draw.circle(sprite1.image, (255, 0, 0), (40, 40), 40)
      sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
      sprite1.radius = 40
      sprite2 = pygame.sprite.Sprite()
      sprite2.image = pygame.Surface((80, 89), pygame.SRCALPHA)
      pygame.draw.circle(sprite2.image, (0, 255, 0), (40, 40), 40)
      sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
      sprite2.radius = 40 
      
      all_group = pygame.sprite.Group([sprite2, sprite1])
      test_group = pygame.sprite.Group(sprite2)
      
      run = True
      while run:
          for event in pygame.event.get():
              if event.type == pygame.QUIT:
                  run = False
      
          sprite1.rect.center = pygame.mouse.get_pos()
          collide = pygame.sprite.spritecollide(sprite1, test_group, False, pygame.sprite.collide_circle)
      
          window.fill(0)
          all_group.draw(window)
          for s in collide:
              pygame.draw.circle(window, (255, 255, 255), s.rect.center, s.rect.width // 2, 5)
          pygame.display.flip()
      
      pygame.quit()
      exit()
      

    What does this all mean for your code?

    pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The position of the rectangle can be specified by a keyword argument. For example, the centre of the rectangle can be specified with the keyword argument center. These keyword arguments are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a list of the keyword arguments).
    See *Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?

    You do not need the x and y attributes of Sprite and Bullet at all. Use the position of the rect attribute instead:

    #Define the sprite class
    class Sprite:
        def __init__(self, x, y, name):
            self.image = pygame.image.load(name)
            self.rect = self.image.get_rect(topleft = (x, y))
    
        def render(self):
            window.blit(self.image, self.rect)
    
    # Define the bullet class to create bullets          
    class Bullet:
        def __init__(self, x, y):
            self.bullet = pygame.image.load("user_bullet.BMP")
            self.rect = self.bullet.get_rect(topleft = (x + 23, y))
    
        def render(self):
            window.blit(self.bullet, self.rect)
    

    Use pygame.Rect.colliderect() to detect collisions between instances of Sprite and Bullet.
    See How to detect collisions between two rectangular objects or images in pygame:

    my_sprite = Sprite(sx, sy, name)
    my_bullet = Bullet(by, by)
    
    while True:
        # [...]
    
        if my_sprite.rect.colliderect(my_bullet.rect):
            printe("hit")