pythonpygameflappy-bird-clone

Pygame Collision Bug


I'm quite new to pygame and came across a bug that i just can't fix on my own. I'm trying to program a Flappy Bird game. The Problem is that the collision detection works, but it also messes with my sprites. If i manage to get past the first obstacle while playing, then the gap resets itself randomly. But the gap should always be the same, just on another position. If i remove the collision detection, it works perfectly fine. Any ideas?

import pygame    
import random      

randomy = random.randint(-150, 150)

class Bird(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface((25,25))
        self.image.fill((255,255,255))
        self.rect = self.image.get_rect()
        self.rect.center = (100, 200)
        self.velocity = 0.05
        self.acceleration =0.4

    def update(self):
        self.rect.y += self.velocity
        self.velocity += self.acceleration
        if self.rect.bottom > 590:
            self.velocity = 0
            self.acceleration = 0                                


class Pipe1(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface((85, 500))
        self.image.fill((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.center = (500, randomy)
        self.randomyupdate = random.randint(-150, 150)

    def update(self):
        self.rect.x -= 2
        if self.rect.x < -90:
            self.randomyupdate = random.randint(-150, 150)
            self.rect.center = (450, self.randomyupdate)

class Pipe2(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface((85, 500))
        self.image.fill((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.center = (500, (randomy +640))

    def update(self):
        self.rect.x -= 2
        self.randomyupdate = Pipe1.randomyupdate
        if self.rect.x < -90:
            self.rect.center = (450, (self.randomyupdate + 640))


pygame.init()
pygame.mouse.set_visible(1)
pygame.key.set_repeat(1, 30)
pygame.display.set_caption('Crappy Bird')
clock = pygame.time.Clock()

Bird_sprite = pygame.sprite.Group()
Pipe_sprite = pygame.sprite.Group()
Bird = Bird()
Pipe1 = Pipe1()
Pipe2 = Pipe2 ()
Bird_sprite.add(Bird)
Pipe_sprite.add(Pipe1)
Pipe_sprite.add(Pipe2)


def main():
    running = True
    while running:
        clock.tick(60)
        screen = pygame.display.set_mode((400,600))
        screen.fill((0,0,0))
        Bird_sprite.update()
        Pipe_sprite.update()
        Bird_sprite.draw(screen)
        Pipe_sprite.draw(screen)
The line im talking about:
        collide = pygame.sprite.spritecollideany(Bird, Pipe_sprite)
        if collide:
            running = False

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    pygame.event.post(pygame.event.Event(pygame.QUIT))
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    Bird.rect.y -= 85
                    Bird.velocity = 0.05
                    Bird.acceleration = 0.4
                    Bird.rect.y += Bird.velocity
                    Bird.velocity += Bird.acceleration
        pygame.display.flip()


if __name__ == '__main__':
    main()

Solution

  • This has nothing to do with the collision detection, it has to do with the order of the sprites in the sprite group which can vary because sprite groups use dictionaries internally which are unordered (in Python versions < 3.6). So if the Pipe1 sprite comes first in the group, the game will work correctly, but if the Pipe2 sprite comes first, then its update method is also called first and the previous randomyupdate of Pipe1 is used to set the new centery coordinate of the sprite.

    To fix this you could either turn the sprite group into an ordered group, e.g.

    Pipe_sprite = pygame.sprite.OrderedUpdates()
    

    or update the rect of Pipe2 each frame,

    def update(self):
        self.rect.x -= 2
        self.rect.centery = Pipe1.randomyupdate + 640
    
        if self.rect.x < -90:
            self.rect.center = (450, Pipe1.randomyupdate + 640)
    

    Also, remove the global randomy variable and always use the randomyupdate attribute of Pipe1.