Search code examples
pythonpygamespriteoverlap

Pygame sprites overlapping issue


i'm learning python by myself, and started with a simple game with pygame. The game consists, so far, in a ball that's been chased by other balls, i have created a loop that avoid the chasing balls to overlap. The way the loop works its by a nested loop that moves thru a list that includes all the chasing balls, then measures the distance between them, if the distance is less than the ball radius, it is moved away. It seems to work most of the time, but sometimes a ball overlaps. I dont know why, if anyone can take a look at my code and give me a hint i would apreciate it, i think the error happens when there are more than 3 balls and at the moment when the player's ball (pelota) collides.

import pygame, random, math
pygame.init()

ancho , alto = 800 , 600
negro = (0,0,0)
blanco=(255,255,255)
FPS = 60     
velocidad = 7
velocidadnalguis = velocidad - 2
contadordecolisiones = 1
perseguidores =  []

pantalla=pygame.display.set_mode((ancho,alto))
pygame.display.set_caption("un jueguito")
reloj=pygame.time.Clock()

class Pelota(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("pelota.png").convert()
        self.image = pygame.transform.scale(self.image, (30, 30))
        self.image.set_colorkey(negro)
        self.rect = self.image.get_rect()
        self.rect.center = (ancho/2,alto/2) 
        self.speed = 0

    def update(self):
        self.speedx = 0
        self.speedy = 0
        tecla  = pygame.key.get_pressed()
        
        if tecla[pygame.K_LEFT]:
            self.speedx = -velocidad
        
        if tecla[pygame.K_RIGHT]:
            self.speedx = velocidad      

        if tecla[pygame.K_UP]:
            self.speedy = -velocidad

        if tecla[pygame.K_DOWN]:
            self.speedy = velocidad

        self.rect.x += self.speedx
        self.rect.y += self.speedy

        if self.rect.right > ancho:
            self.rect.right = ancho

        if self.rect.bottom > alto:
            self.rect.bottom = alto

        if self.rect.left < 0:
            self.rect.left = 0

        if self.rect.top < 0:
            self.rect.top = 0

pelota = Pelota()

class Perseguidor(pygame.sprite.Sprite):
    def __init__(self,):
        super().__init__()
        self.image = pygame.image.load('nalguis.png').convert()
        self.image = pygame.transform.scale(self.image, (30, 30))
        self.image.set_colorkey(negro)
        self.rect = self.image.get_rect()
        self.velocidad = velocidadnalguis
        self.radius = self.rect.width/2
        self.center = self.rect.center
        self.contadordecolisiones = 1
        self.enerandom = random.randint(0,4)

        if self.enerandom == 0:
             self.rect.center = (random.randrange(-130,-30),random.randrange(-130,alto+130))
        if self.enerandom == 1:
             self.rect.center = (random.randrange(-130,ancho+130),random.randrange(-130,-30))
        if self.enerandom == 2:
             self.rect.center = (random.randrange(ancho+30,ancho+130),random.randrange(-130,alto+130))
        if self.enerandom == 3:
            self.rect.center = (random.randrange(-130,ancho+130),random.randrange(alto+30,alto+130))

    def update(self):
        
        self.pely = pelota.rect.y
        self.pelx = pelota.rect.x
        self.perx = self.rect.x
        self.pery = self.rect.y

        self.dist = math.sqrt(((self.pely-self.pery)**2) + ((self.pelx-self.perx)**2))/velocidadnalguis

        self.angulo = math.atan2(self.pely-self.pery,self.pelx-self.perx)

        self.speedx = 0
        self.speedy = 0
        self.speedx = math.cos(self.angulo) 
        self.speedy = math.sin(self.angulo) 
        
        if self.dist>=1:
            self.rect.x += velocidadnalguis * self.speedx
            self.rect.y += velocidadnalguis * self.speedy

all_sprites = pygame.sprite.Group()
perseguidor_grupo = pygame.sprite.Group()
all_sprites.add(pelota)

terminar=False

while not terminar:
    reloj.tick(FPS)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            terminar=True
    
    if len(perseguidor_grupo)<1:
        perseguidor=Perseguidor()
        perseguidor_grupo.add(perseguidor)
        all_sprites.add(perseguidor)

    hit = pygame.sprite.spritecollide(pelota, perseguidor_grupo, True, pygame.sprite.collide_circle)

    if hit: 
        
        if len(perseguidor_grupo)<25:
            perseguidor = Perseguidor()
            perseguidor1 = Perseguidor()
            all_sprites.add(perseguidor, perseguidor1)
            perseguidor_grupo.add(perseguidor, perseguidor1)
            perseguidores.append(perseguidor1)
            perseguidores.append(perseguidor)
            
        else :
            perseguidor = Perseguidor()
            all_sprites.add(perseguidor)
            perseguidor_grupo.add(perseguidor)
            perseguidores.append(perseguidor)
        
    dist=1
    
    for i in  range (len(perseguidores)):
        for j in range(i+1,len(perseguidores)):
            perseguidoresxy = [perseguidores[i].rect.centerx,perseguidores[i].rect.centery]
            dist=math.hypot(perseguidores[i].rect.centerx - perseguidores[j].rect.centerx , perseguidores[i].rect.centery - perseguidores[j].rect.centery)
                            
            if dist <= perseguidor.radius*2:
                if perseguidores[i].rect.centerx < perseguidores[j].rect.centerx:
                    perseguidores[i].rect.centerx -= 3
                if perseguidores[i].rect.centery < perseguidores[j].rect.centery:
                    perseguidores[i].rect.centery -= 3
                if perseguidores[i].rect.centerx > perseguidores[j].rect.centerx:
                    perseguidores[i].rect.centerx += 3  
                if perseguidores[i].rect.centery > perseguidores[j].rect.centery:   
                    perseguidores[i].rect.centery -= 3
                                        
    if len(perseguidores)> len(perseguidor_grupo):
        del perseguidores [0]       
    
    all_sprites.update()
    pantalla .fill(negro)
    all_sprites.draw(pantalla)
    
    pygame.display.flip()
    
pygame.quit()
quit()

Solution

  • [...] then measures the distance between them, if the distance is less than the ball radius, it is moved away. It seems to work most of the time, but sometimes a ball overlaps. [...]

    Of course.You only consider 2 balls when moving a ball away. This means that if you move a ball away from one ball, it can happen that you move it straight onto another ball. You need to check that there is no other ball in the position where you are moving the ball.

    Make sure the balls don't overlap as they spawn:

    requested_balls = 1
    
    while not terminar:
        # [...]
    
        hit = pygame.sprite.spritecollide(pelota, perseguidor_grupo, True, pygame.sprite.collide_circle)
        if hit: 
            requested_balls = min(25, requested_balls+1)
        if len(perseguidor_grupo) < requested_balls:
            perseguidor = Perseguidor()
            if not pygame.sprite.spritecollide(perseguidor, perseguidor_grupo, True, pygame.sprite.collide_circle):
                all_sprites.add(perseguidor)
                perseguidor_grupo.add(perseguidor)
                perseguidores.append(perseguidor)
    

    Only move a ball if the new position of the ball is not yet occupied by a ball:

    class Perseguidor(pygame.sprite.Sprite):
        # [...]
    
        def update(self):
            # [...]
    
            old_rect = self.rect.copy()
            if self.dist>=1:
                self.rect.x += velocidadnalguis * self.speedx
                self.rect.y += velocidadnalguis * self.speedy
    
            hit = pygame.sprite.spritecollide(self, perseguidor_grupo, False, pygame.sprite.collide_circle)
            if len(hit) > 1: # at last 1, because the ball hits itself
                if random.randrange(2) == 0:
                    self.rect.x = old_rect.x
                else:
                    self.rect.y = old_rect.y
                hit = pygame.sprite.spritecollide(self, perseguidor_grupo, False, pygame.sprite.collide_circle)
                if len(hit) > 1:
                        self.rect = old_rect
    

    Complete example:

    import pygame, random, math
    pygame.init()
    
    ancho , alto = 800 , 600
    negro = (0,0,0)
    blanco=(255,255,255)
    FPS = 60     
    velocidad = 7
    velocidadnalguis = velocidad - 2
    contadordecolisiones = 1
    perseguidores =  []
    
    pantalla=pygame.display.set_mode((ancho,alto))
    pygame.display.set_caption("un jueguito")
    reloj=pygame.time.Clock()
    
    class Pelota(pygame.sprite.Sprite):
        def __init__(self):
            pygame.sprite.Sprite.__init__(self)
            self.image = pygame.image.load("pelota.png").convert()
            self.image = pygame.transform.scale(self.image, (30, 30))
            self.image.set_colorkey(negro)
            self.rect = self.image.get_rect()
            self.rect.center = (ancho/2,alto/2) 
            self.speed = 0
    
        def update(self):
            self.speedx = 0
            self.speedy = 0
            tecla  = pygame.key.get_pressed()
            
            if tecla[pygame.K_LEFT]:
                self.speedx = -velocidad
            
            if tecla[pygame.K_RIGHT]:
                self.speedx = velocidad      
    
            if tecla[pygame.K_UP]:
                self.speedy = -velocidad
    
            if tecla[pygame.K_DOWN]:
                self.speedy = velocidad
    
            self.rect.x += self.speedx
            self.rect.y += self.speedy
    
            if self.rect.right > ancho:
                self.rect.right = ancho
    
            if self.rect.bottom > alto:
                self.rect.bottom = alto
    
            if self.rect.left < 0:
                self.rect.left = 0
    
            if self.rect.top < 0:
                self.rect.top = 0
    
    pelota = Pelota()
    
    class Perseguidor(pygame.sprite.Sprite):
        def __init__(self,):
            super().__init__()
            self.image = pygame.image.load('nalguis.png').convert()
            self.image = pygame.transform.scale(self.image, (30, 30))
            self.image.set_colorkey(negro)
            self.rect = self.image.get_rect()
            self.velocidad = velocidadnalguis
            self.radius = self.rect.width/2
            self.center = self.rect.center
            self.contadordecolisiones = 1
            self.enerandom = random.randint(0,4)
    
            if self.enerandom == 0:
                 self.rect.center = (random.randrange(-130,-30),random.randrange(-130,alto+130))
            if self.enerandom == 1:
                 self.rect.center = (random.randrange(-130,ancho+130),random.randrange(-130,-30))
            if self.enerandom == 2:
                 self.rect.center = (random.randrange(ancho+30,ancho+130),random.randrange(-130,alto+130))
            if self.enerandom == 3:
                self.rect.center = (random.randrange(-130,ancho+130),random.randrange(alto+30,alto+130))
    
        def update(self):
            
            self.pely = pelota.rect.y
            self.pelx = pelota.rect.x
            self.perx = self.rect.x
            self.pery = self.rect.y
    
            self.dist = math.sqrt(((self.pely-self.pery)**2) + ((self.pelx-self.perx)**2))/velocidadnalguis
    
            self.angulo = math.atan2(self.pely-self.pery,self.pelx-self.perx)
    
            self.speedx = 0
            self.speedy = 0
            self.speedx = math.cos(self.angulo) 
            self.speedy = math.sin(self.angulo) 
            
            old_rect = self.rect.copy()
            if self.dist>=1:
                self.rect.x += velocidadnalguis * self.speedx
                self.rect.y += velocidadnalguis * self.speedy
    
            hit = pygame.sprite.spritecollide(self, perseguidor_grupo, False, pygame.sprite.collide_circle)
            if len(hit) > 1: # at last 1, because the ball hits itself
                if random.randrange(2) == 0:
                    self.rect.x = old_rect.x
                else:
                    self.rect.y = old_rect.y
                hit = pygame.sprite.spritecollide(self, perseguidor_grupo, False, pygame.sprite.collide_circle)
                if len(hit) > 1:
                        self.rect = old_rect
    
            
    
    all_sprites = pygame.sprite.Group()
    perseguidor_grupo = pygame.sprite.Group()
    all_sprites.add(pelota)
    
    terminar=False
    
    requested_balls = 1
    
    while not terminar:
        reloj.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                terminar=True
        
        if len(perseguidor_grupo)<1:
            perseguidor=Perseguidor()
            perseguidor_grupo.add(perseguidor)
            all_sprites.add(perseguidor)
    
        hit = pygame.sprite.spritecollide(pelota, perseguidor_grupo, True, pygame.sprite.collide_circle)
        if hit: 
            requested_balls = min(25, requested_balls+1)
        if len(perseguidor_grupo) < requested_balls:
            perseguidor = Perseguidor()
            if not pygame.sprite.spritecollide(perseguidor, perseguidor_grupo, True, pygame.sprite.collide_circle):
                all_sprites.add(perseguidor)
                perseguidor_grupo.add(perseguidor)
                perseguidores.append(perseguidor)
            
        dist=1
                                            
        if len(perseguidores)> len(perseguidor_grupo):
            del perseguidores [0]       
        
        all_sprites.update()
        pantalla .fill(negro)
        all_sprites.draw(pantalla)
        
        pygame.display.flip()
        
    pygame.quit()
    quit()