Search code examples
pythonpygamecollision-detection

collision detection not functioning properly in pygame


I'm working on my first pygame project, and can't seem to get collision detection to work properly, this is what i have so far, I'm not quite sure what the problem is

import random
import sys
import pygame


pygame.init()
screen = pygame.display.set_mode((288, 512))
clock = pygame.time.Clock()
game_font = pygame.font.Font('freesansbold.ttf', 40)


def draw_bg():
    screen.blit(bg_surface, (0, bg_y_pos))
    screen.blit(bg_surface, (0, bg_y_pos + 512))


def create_hazzard():
    random_hazzard_pos = random.choice(hazzard_length)
    left_hazzard = hazzard_surface.get_rect(midright=(random_hazzard_pos, 520))
    right_hazzard = hazzard_surface.get_rect(midleft=(random_hazzard_pos + 75, 520))
    return right_hazzard, left_hazzard


def move_hazzards(hazzards):
    for hazzard in hazzards:
        hazzard.centery -= 2
    visible_hazzard = [hazzard for hazzard in hazzards if hazzard.top > -50]
    return visible_hazzard


def draw_hazzards(hazzards):
    for hazzard in hazzards:
        if hazzard.left >= 288:
            screen.blit(hazzard_surface, hazzard)
        else:
            flip_hazzard = pygame.transform.flip(hazzard_surface, True, False)
            screen.blit(flip_hazzard, hazzard)


def check_collision(hazzards):
    for hazzard in hazzards:
        if player_rect.colliderect(hazzard):
            death_sound.play()
            print('collision')
            return False
        else:
            return True



bg_y_pos = 0

player_x_pos = 144
player_y_pos = 256
player_x_change = 0


character_movement = 0
game_active = True
score = 0

player_surface = pygame.image.load('player.png').convert_alpha()
player_surface = pygame.transform.scale2x(player_surface)
player_rect = player_surface.get_rect(center=(player_x_pos, player_y_pos))
bg_surface = pygame.image.load('temp.png').convert()
hazzard_surface = pygame.image.load('temp_hazard.png')

death_sound = pygame.mixer.Sound('temp_hit.mp3')

hazzard_list = []
SPAWNHAZARD = pygame.USEREVENT
pygame.time.set_timer(SPAWNHAZARD, 1200)
hazzard_length = [200, 50, 100]

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                player_x_change -= 2
            if event.key == pygame.K_RIGHT:
                player_x_change += 2

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or pygame.K_RIGHT:
                player_x_change = 0

        if event.type == SPAWNHAZARD:
            hazzard_list.extend(create_hazzard())

    player_x_pos += player_x_change
    # scolling background
    bg_y_pos -= 2

    if bg_y_pos <= -512:
        bg_y_pos = 0

    if game_active:
        draw_bg()
        hazzard_list = move_hazzards(hazzard_list)
        draw_hazzards(hazzard_list)
        player_rect.centerx += player_x_change
        screen.blit(player_surface, player_rect)
        check_collision(hazzard_list)

    pygame.display.update()
    clock.tick(120)

only right_hazzard detects collision, left_hazzard does not. If i return left_hazzard before right_hazzard, then left_hazzard can detect collision but not right_hazzard.


Solution

  • The issue here is with the logic of the check_collision function as you've probably figured out. Specifically the issue is that the function is returning a result before it fully gets to the end of the loop.

    The logic I'm guessing you're looking for is if 'left_hazard' or 'right_hazard' has an issue, then return false, but extending this for an arbitrarily long list. Since it's effectively a long 'or' statement. You may still return 'False' when a collision is detected, however do not return true until the loop has completed.

    So the code will look more like this:

    def check_collision(hazzards):
      for hazzard in hazzards:
        if player_rect.colliderect(hazzard):
            death_sound.play()
            print('collision')
            return False
      # Outside the loop above
      return True
    

    Note: I am assuming no issue with the function player_rect.colliderect and the output of create_hazzard as I'm unsure how the former is implemented. So while the issue above does appear to be a valid bug, if it doesn't solve your problem, I'd look at the player_rect.colliderect function more carefully and check the correct inputs are passed to it.