weird collision detection when blitting on a surface other than the main screen

Viewed 26

When I shoot a missile when i click with the mouse and it collides with one of the blocks, I want the block and the missile to be removed. However, I want to put shape on a transparent surface to move it easily, and this is where the problem is, because the block is not removed and the collision is detected late.

from math import *
import sys
import pygame
from pygame.locals import *

pygame.init()

fps = 60
fpsClock = pygame.time.Clock()
ADD_MISSILE = pygame.USEREVENT + 1
width, height = 1244, 740
screen = pygame.display.set_mode((width, height))


shape = [
'              **************              ',
'          ******          ******          ',
'        ****                  ****        ',
'      **                          **      ',
'    **                              **    ',
'  ****                              ****  ',
'  **                                  **  ',
'****                                  ****',
'**                                      **',
'**                                      **',
'**                                      **',
'**                                      **',
'**                                      **',
'****                                  ****',
'  **                                  **  ',
'  ****                              ****  ',
'    **                              **    ',
'      **                          **      ',
'        ****                  ****        ',
'          ******          ******          ',
'              **************              ',
]

BLOCKSIZE = 8


class Block(pygame.sprite.Sprite):
    def __init__(self, size, color, x, y):
        super().__init__()
        self.image = pygame.Surface((size, size))
        self.image.fill(color)
        self.rect = self.image.get_rect(topleft=(x, y))

class Missile(pygame.sprite.Sprite):
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((6, 2))
        self.image.fill('red')
        self.rect = self.image.get_rect(center=(pos))
        self.vx = 4

    def update(self):
        self.rect.x -= self.vx

def setup_blocks():
    for row_index, row in enumerate(shape):
        for col_index, col in enumerate(row):
            if col == '*':
                x = BLOCKSIZE*col_index
                y = BLOCKSIZE*row_index
                blocks_sprite.add(Block(BLOCKSIZE,'red',x, y))

def collisions():
    if pygame.sprite.groupcollide(blocks_sprite,missiles_sprite,True,True):
        print("collision")
            

blocks_sprite = pygame.sprite.Group()
missiles_sprite = pygame.sprite.Group()

setup_blocks()
width = len(shape[0])
height = len(shape)
surf = pygame.Surface((width*BLOCKSIZE, height*BLOCKSIZE),SRCALPHA)
surf_rect = surf.get_rect()
#surf.fill('green')

while True:
    screen.fill('white')
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == MOUSEBUTTONDOWN:
            missiles_sprite.add(Missile(pygame.mouse.get_pos()))

    keys = pygame.key.get_pressed()
    if keys[pygame.K_RIGHT]:
        surf_rect.x += 5
    
    missiles_sprite.update()
    missiles_sprite.draw(screen)

    for block in blocks_sprite.sprites():
        surf.blit(block.image,block.rect)
        #screen.blit(block.image,block.rect)
    screen.blit(surf,surf_rect)
    collisions()
    pygame.display.update()
    fpsClock.tick(fps)
1 Answers

Actually the block is removed. However you have to clear the Surface on which the blocks are drawn:

while True:
   # [...]

    screen.fill('white')
    missiles_sprite.draw(screen)

    surf.fill((0, 0, 0, 0))                # <-- clear "surf"
    for block in blocks_sprite.sprites():
        surf.blit(block.image,block.rect)
    screen.blit(surf,surf_rect)
    
    pygame.display.update()
    fpsClock.tick(fps)

Anyway, it's a bad idea to draw the blocks on a separate Surface as this will break collision detection. In this case the rect attributes of the blocks are relative to surf. However, pygame.sprite.groupcollide only works if the rect attributes are relative to the screen. I recommend moving the blocks instead. This is less code needed to implement your own collision detection:

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == MOUSEBUTTONDOWN:
            missiles_sprite.add(Missile(pygame.mouse.get_pos()))

    keys = pygame.key.get_pressed()
    if keys[pygame.K_RIGHT]:
        for block in blocks_sprite.sprites():
            block.rect.x += 5
    
    missiles_sprite.update()
    collisions()

    screen.fill('white')
    blocks_sprite.draw(screen)
    missiles_sprite.draw(screen)
    pygame.display.update()
    fpsClock.tick(fps)

Alternatively your own collision control can look like this:

def collisions(surf_rect):
    for block in blocks_sprite:
        blokc_rect = block.rect.move(surf_rect.x, surf_rect.y)
        for missel in missiles_sprite:
            if blokc_rect.colliderect(missel.rect):
                block.kill()
                missel.kill()

blocks_sprite = pygame.sprite.Group()
missiles_sprite = pygame.sprite.Group()

setup_blocks()
width = len(shape[0])
height = len(shape)
surf = pygame.Surface((width*BLOCKSIZE, height*BLOCKSIZE),SRCALPHA)
surf_rect = surf.get_rect()

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == MOUSEBUTTONDOWN:
            missiles_sprite.add(Missile(pygame.mouse.get_pos()))

    keys = pygame.key.get_pressed()
    if keys[pygame.K_RIGHT]:
        surf_rect.x += 5
    
    missiles_sprite.update()
    collisions(surf_rect)     

    screen.fill('white')
    missiles_sprite.draw(screen)

    print(len(blocks_sprite.sprites()))
    surf.fill((0, 0, 0, 0))   
    for block in blocks_sprite.sprites():
        surf.blit(block.image,block.rect)
    screen.blit(surf,surf_rect)
    
    pygame.display.update()
    fpsClock.tick(fps)

Related