marienbad
marienbad

Reputation: 1453

Pygame mask collision only putting damage on base on first collision

I am writing a simple invaders game. To add damage to the bases I figured I could blit a small, black surface on the base at bullet impact, and use a mask to check if the bullet was on the damage or the base, but it isn't working and I feel I am misunderstanding the mask. The first collision is detected but after that it also detects a collision but doesn't put any more damage on the base. I thought because the surface was black the base mask wouldn't include it, but it isn't working. Here is a short test to demo this. Press space (or any key) to fire a bullet at the base. I thought maybe I should generate a new mask for the base but that doesn't work. The mask collide is from the pygame sprite code on github.

import sys, pygame, random
from pygame.locals import *

screenwidth = 600
screenheight = 400

pygame.init()
screen = pygame.display.set_mode((screenwidth, screenheight))
pygame.display.set_caption("shoot 'em up")

screenrect = screen.get_rect()

black = (0, 0, 0)

blue = (10, 10, 255)
yellow = (238, 238, 0)

base_width = 80
base_height = 40

bullet_width = 3
bullet_height = 10

class Bullet(pygame.Surface):
    
    def __init__(self, point):
        super().__init__((bullet_width, bullet_height), pygame.SRCALPHA)
        self.rect = self.get_rect()
        self.rect.midbottom = point
        self.fill(yellow)
        self.velocity = -5
        self.alive = True
        self.mask = pygame.mask.from_surface(self)
            
    def update(self):
        
        self.rect.top += self.velocity
        
    def draw(self, surf):
        surf.blit(self, self.rect)

class Base(pygame.Surface):

    def __init__(self, x, y, colour):
        super().__init__((base_width, base_height), pygame.SRCALPHA)
        self.rect = self.get_rect()
        self.rect.x = x
        self.rect.y = y 
        self.fill(colour)
        self.alive = True
        
    def add_damage(self, bullet):
        width = random.randint(3, 6)
        height = random.randint(8, 12)
        damage = pygame.Surface((width, height), pygame.SRCALPHA)
        damage.fill(black)
        rect = damage.get_rect()
        rect.x = bullet.rect.x - self.rect.x
        rect.y = bullet.rect.top - self.rect.top 
        self.blit(damage, rect)
        #self.mask = pygame.mask.from_surface(self) 
    
    def draw(self, surf):
        surf.blit(self, self.rect)
        
class Test(pygame.Surface):

    def __init__(self):
        super().__init__((600, 400))

        self. base = Base(50, 300, blue)
        self.bullets = []
        
    def run(self):
    
        while 1:
            self.get_events()
            self.update()
            self.draw()
                
    def get_events(self):

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            
            if event.type == pygame.KEYDOWN:
                bullet = Bullet((60, 380))
                self.bullets.append(bullet)
                    
    def update(self):
        if self.bullets:
            for bullet in self.bullets:
                bullet.update()
                self.collision_check(bullet)
            
            for bullet in self.bullets:
                if not bullet.alive:
                    self.bullets.remove(bullet)
                    
    def collision_check(self, bullet):
        if bullet.rect.colliderect(self.base):
            if self.collide_mask(bullet, self.base):
                print("collide")
                self.base.add_damage(bullet)
                bullet.alive = False
                
    def collide_mask(self, left, right):

        xoffset = right.rect[0] - left.rect[0]
        yoffset = right.rect[1] - left.rect[1]

        try:
            leftmask = left.mask
        except AttributeError:
            leftmask = pygame.mask.from_surface(left)
        try:
            rightmask = right.mask
        except AttributeError:
            rightmask = pygame.mask.from_surface(right)
    
        return leftmask.overlap(rightmask, (xoffset, yoffset))

    def draw(self):
        self.fill(black)
        self.base.draw(self)
        
        for bullet in self.bullets:
            bullet.draw(self)
            
        screen.blit(self, (0,0))
        
        pygame.display.flip()
        
if __name__=="__main__":
    t = Test()
    t.run()

As you can see this is not using pygame sprites.

Upvotes: 1

Views: 99

Answers (1)

Rabbid76
Rabbid76

Reputation: 210968

if the pygame.Surface object is changed you need to recreate the mask with pygame.mask.from_surface. However, the mask is generated form the Surface's alpha channel. Therefore, you need to make the damaged area transparent. Create a completely transparent rectangle (RGBA = 0, 0, 0, 0) and blit the rectangle using the special flag BLEND_RGBA_MULT (or BLEND_RGBA_MIN). Finally recreate the mask:

damage = pygame.Surface((width, height), pygame.SRCALPHA)
self.blit(damage, rect, special_flags=pygame.BLEND_RGBA_MULT)
self.mask = pygame.mask.from_surface(self) 

add_damage Mehtod:

class Base(pygame.Surface):
    # [...]

    def add_damage(self, bullet):
        width = random.randint(3, 6)
        height = random.randint(8, 12)
        damage = pygame.Surface((width, height), pygame.SRCALPHA)
        rect = damage.get_rect()
        rect.x = bullet.rect.x - self.rect.x
        rect.y = bullet.rect.top - self.rect.top 
        self.blit(damage, rect, special_flags=pygame.BLEND_RGBA_MULT)
        self.mask = pygame.mask.from_surface(self) 

Upvotes: 1

Related Questions