pythonpygamecollision

Sprite collision test in PyGame (tried: rect and group)


I know there are near 5 or 6 questions similar to this one I'm making, but none of the answers helped me (they probably would if I weren't a complete noob), so I'll try to show my specific case.

I'm trying to make a sprite collision test in PyGame (Python lib) but I can't get it to work, all I get is "False" when colliding, it simply won't work and I can't figure out why, been searching for 2 days straight.

In my game I have the object Player (which I called "dude") and the object Enemy (which I called... "enemy"). The __init__ on both is very similar, and both have rects generated.

This is the __init__ for the player:

class dude(pygame.sprite.Sprite):
def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self.image = self.load_image(SPRITE_PLAYER)
    self.X = randint(34,ALTURA-10)
    self.Y = randint(34,LARGURA-10)
    self.rect = pygame.Rect(self.X,self.Y,25,25) #currently testing this
    #self.rect = self.image.get_rect()           already tried this
    self.speed = SPEED_PLAYER
    self.clock = pygame.time.Clock()

For the enemy, I have this:

class enemy(pygame.sprite.Sprite):
def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self.image = self.load_image(SPRITE_ENEMY)
    self.X = randint(34,ALTURA-10)
    self.Y = randint(34,LARGURA-10)
    self.rect = pygame.Rect(self.X,self.Y,25,25)
    self.speed = SPEED_ENEMY
    self.clock = pygame.time.Clock()
    self.xis=1
    self.yps=1

When testing collision, these are two methods that return something (which is already awesome for me, cause there were other tries that only returned errors) but they always return 0's even when I collide them in game. The collision should occur when player (controlled by key input) touches the enemy sprite your image (moved randomly though the stage).

These are the testing methods I have:

print pygame.sprite.collide_rect(player, enemy),pygame.sprite.collide_rect(player, enemy2)
print pygame.sprite.collide_rect(player, enemy),player.rect.colliderect(enemy2)
print player.rect.colliderect(enemy),player.rect.colliderect(enemy2)

(EDIT) I've been oriented to show more code so the problem can be found, so there it is (I'm also going to load the sprites globally, passing them as parameters to the objects, tanks for the tip).

Player Object:

# -*- coding: cp1252 -*-
import pygame, os
from pygame.locals import *
from Configs import *
from random import randint

class dude(pygame.sprite.Sprite):
def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self.image = self.load_image(SPRITE_PLAYER)
    self.X = randint(34,ALTURA-10)
    self.Y = randint(34,LARGURA-10)
    self.rectBox()
    self.speed = SPEED_PLAYER
    self.clock = pygame.time.Clock()

def load_image(self,image_loaded):
    try:
        image = pygame.image.load(image_loaded)
    except pygame.error, message:
        print "Impossivel carregar imagem: " + image_loaded
        raise SystemExit, message
    return image.convert_alpha()

def rectBox(self):
    self.rect = self.image.get_rect()
    self.image_w, self.image_h  = self.image.get_size()
    self.rect.move(self.X, self.Y)          
    self.rect.topleft = (self.X, self.Y)
    self.rect.bottomright = (self.X + self.image_w, self.Y + self.image_h)

def update(self,xis,yps,screen):
    time_passed = self.clock.tick()
    time_passed_seconds = time_passed/1000.0
    distance_moved = time_passed_seconds * (self.speed +100) #Distancia = tempo * velocidade
    
    self.X +=xis*distance_moved
    self.Y +=yps*distance_moved

    screen.blit(self.image, (self.X,self.Y))
    
    if self.X>ALTURA-52:
        self.X-=2

    elif self.X<30:
        self.X+=2

    if self.Y>LARGURA-52:
        self.Y-=2

    elif self.Y<30:
        self.Y+=2

Enemy Object:

# -*- coding: cp1252 -*-
import pygame, os
from pygame.locals import *
from Configs import *
from random import randint

class enemy(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = self.load_image(SPRITE_ENEMY)
        self.X = randint(34,ALTURA-10)
        self.Y = randint(34,LARGURA-10)
        self.rectBox()
        self.speed = SPEED_ENEMY
        self.clock = pygame.time.Clock()
        self.xis=1
        self.yps=1

    def load_image(self,image_loaded):
        try:
            image = pygame.image.load(image_loaded)
        except pygame.error, message:
            print "Impossivel carregar imagem: " + image_loaded
            raise SystemExit, message
        return image.convert_alpha()

    def rectBox(self):
        self.rect = self.image.get_rect()
        self.image_w, self.image_h  = self.image.get_size()
        self.rect.move(self.X, self.Y)          
        self.rect.topleft = (self.X, self.Y)
        self.rect.bottomright = (self.X + self.image_w, self.Y + self.image_h)
    
    def update(self,screen):
        time_passed = self.clock.tick()
        time_passed_seconds = time_passed/1000.0
        distance_moved = time_passed_seconds * (self.speed +100)
        
        self.X +=self.xis*distance_moved
        self.Y -=self.yps*distance_moved

        screen.blit(self.image, (self.X,self.Y))

        #Maquina de estados finitos para inteligência da movimentação
        if self.X>ALTURA-50:
            self.X-=2
            self.xis=-1
        elif self.X<30:
            self.X+=2
            self.xis=1
        
        if self.Y>LARGURA-50:
            self.Y-=2
            self.yps=1
        elif self.Y<30:
            self.Y+=2
            self.yps=-1
    

Main script:

# -*- coding: cp1252 -*-
import pygame, os
from pygame.locals import *
from Configs import *
from Enemy import enemy as e
from Player import dude
from sys import exit

pygame.init()

#Função para carregar imagens
def load_image(image_loaded):
        try:
            image = pygame.image.load(image_loaded)
        except pygame.error, message:
            print "Impossivel carregar imagem: " + image_loaded
            raise SystemExit, message
        return image.convert()

#Funções do Pause
def pause():
    drawpause()
    while 1:
        p = pygame.event.wait()
        if p.type in (pygame.QUIT, pygame.KEYDOWN):
            return
  
def drawpause():
    font = pygame.font.Font(None, 48)
    text1 = font.render("PAUSE", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.centerx = screen.get_rect().centerx
    text1pos.centery = screen.get_rect().centery
    screen.blit(text1, text1pos)
    font = pygame.font.Font(None, 36)
    text2 = font.render("Pressione qualquer tecla para continuar", 1, (10, 10, 10))
    text2pos = text2.get_rect()
    text2pos.centerx = screen.get_rect().centerx
    text2pos.centery = screen.get_rect().centery + 50
    screen.blit(text2, text2pos)
    pygame.display.flip()

#Inicializa tela principal    
os.environ["SDL_VIDEO_CENTERED"] = "1"
screen = pygame.display.set_mode((ALTURA, LARGURA),0,32)
bg = load_image(BACKGROUND)
pygame.display.set_caption("Jogo teste para TCC - Top-Down")

#Inicialização do personagem
move_x, move_y = 0, 0
player = dude()

#Inicialização dos inimigos
enemy = e()
enemy2 = e()

#Atribuição de grupos de entidades
inimigos = pygame.sprite.Group()
inimigos.add(enemy)
inimigos.add(enemy2)

personagem = pygame.sprite.Group()
personagem.add(player)

#Objeto clock
clock = pygame.time.Clock()

#Loop principal sprite.get_height() sprite.get_width()
while True:

    #Eventos do jogo
    for event in pygame.event.get():
        #SAIR DO JOGO
        if event.type == QUIT:
            pygame.quit()
            exit()
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                pygame.quit()
                exit()
            elif event.key == K_p:
                pause()
                
        #TECLAS DE MOVIMENTAÇÃO PERSONAGEM
        if event.type == KEYDOWN:
            if event.key == K_LEFT:
                move_x = -1
            elif event.key == K_RIGHT:
                move_x = +1
            elif event.key == K_UP:
                move_y = -1
            elif event.key == K_DOWN:
                move_y = +1
        elif event.type == KEYUP:
            if event.key == K_LEFT:
                move_x = 0
            elif event.key == K_RIGHT:
                move_x = 0
            elif event.key == K_UP:
                move_y = 0
            elif event.key == K_DOWN:
                move_y = 0

    #Posicionamento do background
    screen.blit(bg, (0,0))

    #Movimentação do personagem
    player.update(move_x,move_y,screen)

    #Movimentação inimigos
    enemy.update(screen)
    enemy2.update(screen)

    #Teste colisão
    #print pygame.sprite.collide_rect(player, enemy),pygame.sprite.collide_rect(player, enemy2)
    #print pygame.sprite.collide_rect(player, enemy),player.rect.colliderect(enemy2)
    #print player.rect.colliderect(enemy),player.rect.colliderect(enemy2)

    #Refresh (por FPS)
    pygame.display.update()

That's it, thanks for the feedback on my post!


Solution

  • If you have a problem with collision detection, it often helps to print the rects of the sprites, e.g. print(player.rect). It'll show you that the rects are never updated and they stay at the same position, and because they're used for the collision detection your sprites won't collide.

    To fix your problem you have to set the self.rect.center (or self.rect.topleft) of the sprites to the current self.X and self.Y coordinates in the update methods.

    self.rect.center = self.X, self.Y
    

    I recommend to take a look at the pygame.sprite.spritecollide method. Put your enemies into a separate sprite group and then get the collided sprites in this way:

    collided_enemies = pg.sprite.spritecollide(player, enemies, False)
    for collided_enemy in collided_enemies:
        # Do something with the collided_enemy.
    

    Here's a minimal, complete example:

    import sys
    import pygame as pg
    
    
    class Player(pg.sprite.Sprite):
    
        def __init__(self, pos, color):
            super().__init__()
            self.image = pg.Surface((50, 30))
            self.image.fill(color)
            self.rect = self.image.get_rect(center=pos)
    
    
    def main():
        screen = pg.display.set_mode((640, 480))
        clock = pg.time.Clock()
        player = Player((100, 100), pg.Color('dodgerblue2'))
        enemy = Player((300, 100), pg.Color('sienna2'))
        enemy2 = Player((200, 300), pg.Color('sienna2'))
        all_sprites = pg.sprite.Group(player, enemy, enemy2)
        enemies = pg.sprite.Group(enemy, enemy2)
    
        done = False
    
        while not done:
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    done = True
                if event.type == pg.MOUSEMOTION:
                    player.rect.center = event.pos
    
            all_sprites.update()
    
            collided_enemies = pg.sprite.spritecollide(player, enemies, False)
            for collided_enemy in collided_enemies:
                print(collided_enemy.rect)
    
            screen.fill((50, 50, 50))
            all_sprites.draw(screen)
    
            pg.display.flip()
            clock.tick(30)
    
    
    if __name__ == '__main__':
        pg.init()
        main()
        pg.quit()
        sys.exit()