Search code examples
pythonpygamecollision-detection

Pong paddles have some weird bug preventing from letting a player score


I have made a pong game. Now the problem is that when I move the paddle, the ball doesn't go past it but bounces of some invisible wall. Here is my code and run it yourselves and you can see the problem(You have to move the paddles around a little bit)

# Pong

# Importing libraries
import pygame
import random
import time

# Initializing PyGame
pygame.init()

# Creating a font
font = pygame.font.SysFont(None, 30)

# Set the height and width of the screen
window_width = 700
window_height = 500
size = [window_width, window_height]
game_win = pygame.display.set_mode(size)


# Creating a messaging system
def message(sentence, x, y):
    sentence = font.render(sentence, True, white)
    game_win.blit(sentence, [x, y])


# Creating a color
white = (225, 225, 225)
black = (0, 0, 0)

# Setting up ball
ball_size = 25


class Ball:
    """
    Class to keep track of a ball's location and vector.
    """

    def __init__(self):
        self.x = 0
        self.y = 0
        self.change_x = 0
        self.change_y = 0


def make_ball():
    ball = Ball()
    # Starting position of the ball.
    # Take into account the ball size so we don't spawn on the edge.
    ball.x = 350
    ball.y = 250

    # Speed and direction of rectangle
    ball.change_x = 5
    ball.change_y = 5

    return ball


def main():
    # Scores
    left_score = 0
    right_score = 0

    pygame.init()

    pygame.display.set_caption("Ping Pong")

    # Loop until the user clicks the close button.
    done = False

    # Used to manage how fast the screen updates
    clock = pygame.time.Clock()

    ball_list = []

    ball = make_ball()
    ball_list.append(ball)

    # Right paddle coordinates
    y = 200
    y_change = 0
    x = 50
    # Left paddle coordinates
    y1 = 200
    y1_change = 0
    x1 = 650

    while not done:
        # --- Event Processing
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_w:
                    y_change = -5

                elif event.key == pygame.K_s:
                    y_change = 5

                elif event.key == pygame.K_UP:
                    y1_change = -5

                elif event.key == pygame.K_DOWN:
                    y1_change = 5

            elif event.type == pygame.KEYUP:
                y_change = 0
                y1_change = 0

        y += y_change
        y1 += y1_change

        if y > window_height - 100:
            y -= 5
        if y < 50:
            y += 5
        if y1 > window_height - 100:
            y1 -= 5
        if y1 < 50:
            y1 += 5

        message("Left player score: " + str(left_score), 10, 10)
        message("Right player score: " + str(right_score), 490, 10)

        # Drawing a left paddle
        pygame.draw.rect(game_win, white, [x, y, 25, 100])
        # Drawing a right paddle
        pygame.draw.rect(game_win, white, [x1, y1, 25, 100])

        # Updating screen to changes take place
        pygame.display.update()

        # Logic
        for ball in ball_list:
            # Move the ball's center
            ball.x += ball.change_x
            ball.y += ball.change_y

            # Bounce the ball if needed
            if ball.y > 50 - ball_size or ball.y < ball_size:
                ball.change_y *= -1
            if ball.x > window_width - ball_size:
                ball.change_x *= -1
                left_score += 1
            if ball.x < ball_size:
                ball.change_x *= -1
                right_score += 1
            # Here is the part where it all becomes weird and buggy
            if ball.x-ball_size <= x <= ball.x + ball_size:
                ball.change_x *= -1
            if ball.x-ball_size <= x1 <= ball.x + ball_size:
                ball.change_x *= -1
            if ball.y-ball_size <= y <= ball.y + ball_size:
                ball.change_x *= -1
            if ball.y-ball_size <= y1 <= ball.y + ball_size:
                ball.change_x *= -1

            if right_score == 10:
                message('RIGHT PLAYER HAS WON!!', 300, 200)
                time.sleep(10)
                done = True
            elif left_score == 10:
                message("LEFT PLAYER HAS WON!!", 300, 200)
                time.sleep(10)
                done = True

        # Drawing
        # Set the screen background
        game_win.fill(black)

        # Draw the balls
        for ball in ball_list:
            pygame.draw.circle(game_win, white, [ball.x, ball.y], ball_size)

        # Wrap-up
        # Limit to 60 frames per second
        clock.tick(60)

        # Go ahead and update the screen with what we've drawn.
        pygame.display.flip()

    # Close everything down
    pygame.quit()


if __name__ == "__main__":
    main()

When I move the paddle away, the ball can still bounce off some invisible wall. Everything else is good, just the part where I have put # Bounce if needed.


Solution

  • I recommend to compute the bounding rectangle of the ball and the paddles and to use pygame.Rect and colliderect() to detect the collision between a ball and a paddle.
    See alos Sometimes the ball doesn't bounce off the paddle in pong game.

    For instance:

    def main():
        # [...]
    
        while not done:
            # [...]
    
            # Logic
            for ball in ball_list:
                # [...]
    
                ball_rect = pygame.Rect(ball.x-ball_size, ball.y-ball_size, ball_size*2, ball_size*2)
                
                left_paddle_rect = pygame.Rect(x, y, 25, 75)
                if ball.change_x < 0 and ball_rect.colliderect(left_paddle_rect):
                    ball.change_x = abs(ball.change_x)
                
                right_paddle_rect = pygame.Rect(x1, y1, 25, 75)
                if ball.change_x > 0 and ball_rect.colliderect(right_paddle_rect):
                    ball.change_x = -abs(ball.change_x)
    

    Furthermore, there the height of the window is 500 rather than 50_

    if ball.y > 50 - ball_size or ball.y < ball_size:

    if ball.y > 500 - ball_size or ball.y < ball_size:
    

    I recommend to remove the multiple calls to pygame.display.flip() respectively pygame.display.update(). Do just one update of the display at the end of the main application loop. See the complete example:

    # Importing libraries
    import pygame
    import random
    import time
    
    # Initializing PyGame
    pygame.init()
    
    # Creating a font
    font = pygame.font.SysFont(None, 30)
    
    # Set the height and width of the screen
    window_width = 700
    window_height = 500
    size = [window_width, window_height]
    game_win = pygame.display.set_mode(size)
    
    
    # Creating a messaging system
    def message(sentence, x, y):
        sentence = font.render(sentence, True, white)
        game_win.blit(sentence, [x, y])
    
    
    # Creating a color
    white = (225, 225, 225)
    black = (0, 0, 0)
    
    # Setting up ball
    ball_size = 25
    
    
    class Ball:
        """
        Class to keep track of a ball's location and vector.
        """
    
        def __init__(self):
            self.x = 0
            self.y = 0
            self.change_x = 0
            self.change_y = 0
    
    
    def make_ball():
        ball = Ball()
        # Starting position of the ball.
        # Take into account the ball size so we don't spawn on the edge.
        ball.x = 350
        ball.y = 250
    
        # Speed and direction of rectangle
        ball.change_x = 5
        ball.change_y = 5
    
        return ball
    
    
    def main():
        # Scores
        left_score = 0
        right_score = 0
    
        pygame.init()
    
        pygame.display.set_caption("Ping Pong")
    
        # Loop until the user clicks the close button.
        done = False
    
        # Used to manage how fast the screen updates
        clock = pygame.time.Clock()
    
        ball_list = []
    
        ball = make_ball()
        ball_list.append(ball)
    
        # Right paddle coordinates
        y = 200
        y_change = 0
        x = 50
        # Left paddle coordinates
        y1 = 200
        y1_change = 0
        x1 = 650
    
        while not done:
            # --- Event Processing
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    done = True
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_w:
                        y_change = -5
    
                    elif event.key == pygame.K_s:
                        y_change = 5
    
                    elif event.key == pygame.K_UP:
                        y1_change = -5
    
                    elif event.key == pygame.K_DOWN:
                        y1_change = 5
    
                elif event.type == pygame.KEYUP:
                    y_change = 0
                    y1_change = 0
    
            y += y_change
            y1 += y1_change
    
            if y > window_height - 100:
                y -= 5
            if y < 50:
                y += 5
            if y1 > window_height - 100:
                y1 -= 5
            if y1 < 50:
                y1 += 5
    
            message("Left player score: " + str(left_score), 10, 10)
            message("Right player score: " + str(right_score), 490, 10)
    
    
            # Logic
            for ball in ball_list:
                # Move the ball's center
                ball.x += ball.change_x
                ball.y += ball.change_y
    
                # Bounce the ball if needed
                if ball.y > 500 - ball_size or ball.y < ball_size:
                    ball.change_y *= -1
                if ball.x > window_width - ball_size:
                    ball.change_x *= -1
                    left_score += 1
                if ball.x < ball_size:
                    ball.change_x *= -1
                    right_score += 1
                
                # Here is the part where it all becomes weird and buggy
                ball_rect = pygame.Rect(ball.x-ball_size, ball.y-ball_size, ball_size*2, ball_size*2)
                
                left_paddle_rect = pygame.Rect(x, y, 25, 75)
                if ball.change_x < 0 and ball_rect.colliderect(left_paddle_rect):
                    ball.change_x = abs(ball.change_x)
                
                right_paddle_rect = pygame.Rect(x1, y1, 25, 75)
                if ball.change_x > 0 and ball_rect.colliderect(right_paddle_rect):
                    ball.change_x = -abs(ball.change_x)
    
                if right_score == 10:
                    message('RIGHT PLAYER HAS WON!!', 300, 200)
                    time.sleep(10)
                    done = True
                elif left_score == 10:
                    message("LEFT PLAYER HAS WON!!", 300, 200)
                    time.sleep(10)
                    done = True
    
            # Drawing
            # Set the screen background
            game_win.fill(black)
    
            # Drawing a left paddle
            pygame.draw.rect(game_win, white, [x, y, 25, 100])
            # Drawing a right paddle
            pygame.draw.rect(game_win, white, [x1, y1, 25, 100])
    
            # Draw the balls
            for ball in ball_list:
                pygame.draw.circle(game_win, white, [ball.x, ball.y], ball_size)
    
            # Go ahead and update the screen with what we've drawn.
            pygame.display.flip()
    
            # Wrap-up
            # Limit to 60 frames per second
            clock.tick(60)
    
        # Close everything down
        pygame.quit()
    
    
    if __name__ == "__main__":
        main()