Search code examples
pythonpygamecollision-detectioncollision

Ping Pong Pygame collision with side of pad


I am trying to program a ping pong game in python, and I am able to make the ball bounce off the side of the pad. However, I am unable to make the ball bounce off the top and bottom of the pad. I am currently only doing the left pad (pad1). Here is my code:

import sys, pygame, random, math
pygame.init()

width, height = 1200, 675

screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
clock = pygame.time.Clock()


FPS = 120

xmb1 = False
xmf1 = False
ymb1 = False
ymf1 = False

xmb2 = False
xmf2 = False
ymb2 = False
ymf2 = False

squareh = 275
squarew = 35
squares = 3

x1 = 100
y1 = (height / 2) - (squareh / 2)

x2 = width - 100 - squarew
y2 = (height / 2) - (squareh / 2)

BLACK = (0,0,0)
WHITE = (255,255,255)

font = pygame.font.SysFont("arial", 30)

text = font.render("Press Space to start", True, WHITE)
text3 = font.render("3", True, WHITE)
text2 = font.render("2", True, WHITE)
text1 = font.render("1", True, WHITE)
startt = font.render("Start!", True, WHITE)

text3b = False
text2b = False
text1b = False
starttb = False

start = False
startballmove = False

bx = width / 2
by = height / 2
br = 40
bms = 6
bxm = random.randint(-bms, bms)
bym = random.randint(-bms, bms)

btc = by
blc = bx
bbc = by + br + br
brc = bx + br + br

circle=pygame.Surface((br * 2, br  * 2))
circle.fill((0, 0, 0))
pygame.draw.circle(circle, WHITE , (br, br), br, 0)
circle.set_colorkey((0, 0, 0))

pad1 = pygame.Rect(x1, y1, squarew, squareh)
pad2 = pygame.Rect(x2, y2, squarew, squareh)

while 1:
    if start and not text1b and not text2b and not text3b and not starttb and not startballmove:
        text3b = True
        pygame.time.set_timer(pygame.USEREVENT, 1000)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.display.quit()
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            # pad 2 check if key is down
            if event.key == pygame.K_UP:
                ymb2 = True
            if event.key == pygame.K_DOWN:
                ymf2 = True
            # pad 1 check if key is down
            if event.key == pygame.K_w:
                ymb1 = True
            if event.key == pygame.K_s:
                ymf1 = True
            if event.key == pygame.K_SPACE:
                start = True
        elif event.type == pygame.KEYUP:
            #pad 2 check if key goes up
            if event.key == pygame.K_UP:
                ymb2 = False
            if event.key == pygame.K_DOWN:
                ymf2 = False
            # pad 1 check if key goes up
            if event.key == pygame.K_w:
                ymb1 = False
            if event.key == pygame.K_s:
                ymf1 = False
        # check if window has been resized
        if event.type == pygame.VIDEORESIZE:
            width = event.dict['size'][0]
            height = event.dict['size'][1]
            screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
        if event.type == pygame.USEREVENT:
            # check if start should be hidden
            if starttb:
                starttb = False
                startballmove = True
            # check if start should be showed
            if text1b and not text2b and not text3b:
                text1b = False
                text2b = False
                text3b = False
                starttb = True
            # check if 1 should be showed
            if text2b and not text3b and not text1b:
                text3b = False
                text2b = False
                text1b = True
            # check if 2 should be showed
            if text3b and not text2b and not text1b:
                text3b = False
                text2b = True
                text1b = False

    # check if pad 1 is out of bounds and move it      
    if ymb1 and not (y1 <= 0): y1 -= squares
    if ymf1 and not (y1 + squareh >= height): y1 += squares
    if y1 > (height - squareh) + 1: y1 -= squares

    # check if pad 2 is out of bounds and move it      
    if ymb2 and not (y2 <= 0): y2 -= squares
    if ymf2 and not (y2 + squareh >= height): y2 += squares
    if y2 > (height - squareh) + 1: y2 -= squares

    # put pads in center if game has not started
    if not start:
        # pad 1
        x1 = 75
        y1 = (height / 2) - (squareh / 2)
        # pad 2
        x2 = width - 75 - squarew
        y2 = (height / 2) - (squareh / 2)
        #ball
        bx = width / 2 - br
        by = height / 2 - br

    # put pads in center in x if game has started
    else:
        # pad 1
        x1 = 75
        # pad 2
        x2 = width - 75 - squarew
    # if ball has not started moving center it
    if not startballmove:
        bx = width / 2 - br
        by = height / 2 - br
    # check if movement variables are 0
    while bxm == 0 or bym == 0:
        if bxm == 0:
            bxm = random.randint(-6, 6)
        if bym == 0:
            bym = random.randint(-6, 6)

    screen.fill(BLACK)
    # draw starting text if game has not started
    if not start:
        screen.blit(text,((width / 2) - text.get_width() // 2, (height / 4) - text.get_height() // 2))
    # put 3 on screen
    if start and text3b:
        screen.blit(text3,((width / 2) - 15, (height / 4) - (text.get_height() / 2)))
    # put 2 on screen
    if start and text2b:
        screen.blit(text2,((width / 2) - 15, (height / 4) - (text.get_height() / 2)))   
    # put 1 on screen
    if start and text1b:
        screen.blit(text1,((width / 2) - 15, (height / 4) - (text.get_height() / 2)))
    # put start on screen
    if start and starttb:
        screen.blit(startt,((width / 2) - (text.get_width() / 8), (height / 4) - (text.get_height() / 2)))   
    # check if ball is out of bounds
    btc = by
    blc = bx
    bbc = by + br + br
    brc = bx + br + br
    if start and startballmove:
        # screen
        if btc <= 0:
            bym = bym * -1
            print("top side")
        if bbc >= height:
            bym = bym * -1
            print("bottom side")
        if blc <= 0:
            bxm = bxm * -1
            print("left side")
        if brc >= width:
            bxm = bxm * -1
            print("right side")
        # left pad
        pad1 = pygame.Rect(x1, y1, squarew, squareh)
        if pad1.collidepoint(int(bx), int(by)):
            bxm = bxm * -1
            bx += abs(bxm)
            print("Collision")
    # move ball
    if start and startballmove:
        bx += bxm
        by += bym
    # draw circle if game start
    if start:
        screen.blit(circle, (int(bx), int(by)))  
    # draw pad 1
    pad1 = pygame.Rect(x1, y1, squarew, squareh)
    pygame.draw.rect(screen, WHITE, pad1, 0)
    # draw pad 2
    pad2 = pygame.Rect(x2, y2, squarew, squareh)
    pygame.draw.rect(screen, WHITE, pad2, 0)

    pygame.display.flip()
    clock.tick(FPS)

Does anyone know how to do this? I have only recently learned to program in python and pygame.


Solution

  • You have to split move and cheking collision on two steps. First move only in X and then you can recognize front collision. Later move in Y and you can recogize side collision.

    Your code is big mess so it may need a lot changes. You should organize code and use better names for variables to make it more readable.

    And you could use elif and nested if so it will be cleaner.

    import sys
    import pygame
    import random
    import math
    
    # --- constants --- (UPPER_CASE_NAMES)
    
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    
    FPS = 60
    
    SQUARE_HEIGHT = 275
    SQUARE_WIDTH  = 35
    SQUARE_SPEED  = 3
    
    BALL_R = 40
    
    # --- main ---
    
    window_width = 1200
    window_height = 675
    
    # - init -
    
    pygame.init()
    
    screen = pygame.display.set_mode((window_width, window_height), pygame.RESIZABLE)
    screen_rect = screen.get_rect()
    
    # - text -
    
    font = pygame.font.SysFont("arial", 30)
    
    # - text - press space -
    
    text_press_space = font.render("Press Space to start", True, WHITE)
    text_press_space_rect = text_press_space.get_rect()
    text_press_space_rect.centerx = screen_rect.centerx
    text_press_space_rect.centery = screen_rect.height // 4
    
    # - text - count down -
    
    text_1 = font.render("1", True, WHITE)
    text_1_rect = text_1.get_rect()
    text_1_rect.centerx = screen_rect.centerx
    text_1_rect.centery = screen_rect.height // 4 
    
    
    text_2 = font.render("2", True, WHITE)
    text_2_rect = text_2.get_rect()
    text_2_rect.centerx = screen_rect.centerx
    text_2_rect.centery = screen_rect.height // 4 
    
    text_3 = font.render("3", True, WHITE)
    text_3_rect = text_3.get_rect()
    text_3_rect.centerx = screen_rect.centerx
    text_3_rect.centery = screen_rect.height // 4 
    
    text_start = font.render("Start!", True, WHITE)
    text_start_rect = text_start.get_rect()
    text_start_rect.centerx = screen_rect.centerx
    text_start_rect.centery = screen_rect.height // 4 
    
    display_text_3 = False
    display_text_2 = False
    display_text_1 = False
    display_text_start = False
    
    # - ball -
    
    ball = pygame.Surface((BALL_R * 2, BALL_R  * 2))
    # circle.fill(BLACK) # not need it because surface is black as default
    ball.set_colorkey(BLACK)
    pygame.draw.circle(ball, WHITE , (BALL_R, BALL_R), BALL_R, 0)
    ball_rect = ball.get_rect()
    ball_rect.center = screen_rect.center
    
    ball_speed = 6
    
    ball_speed_x = ball_speed * random.choice([1, -1])
    ball_speed_y = ball_speed * random.choice([1, -1])
    
    ball_move = False
    
    # - pads -
    
    pad1_rect = pygame.Rect(0, 0, SQUARE_WIDTH, SQUARE_HEIGHT)
    pad1_rect.left = 100
    pad1_rect.centery = screen_rect.centery
    pad1_move_up = False
    pad1_move_down = False
    
    pad2_rect = pygame.Rect(0, 0, SQUARE_WIDTH, SQUARE_HEIGHT)
    pad2_rect.right = screen_rect.right - 100
    pad2_rect.centery = screen_rect.centery
    pad2_move_up = False
    pad2_move_down = False
    
    # --- mainloop ---
    
    play_start = False
    
    clock = pygame.time.Clock()
    
    while True:
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                # pad 2 check if key is down
                if event.key == pygame.K_UP:
                    pad2_move_up = True
                elif event.key == pygame.K_DOWN:
                    pad2_move_down = True
                # pad 1 check if key is down
                elif event.key == pygame.K_w:
                    pad1_move_up = True
                elif event.key == pygame.K_s:
                    pad1_move_down = True
    
                elif event.key == pygame.K_SPACE:
                    if not play_start:
                        play_start = True
                        pygame.time.set_timer(pygame.USEREVENT, 1000)
                        display_text_3 = True
    
            elif event.type == pygame.KEYUP:
                #pad 2 check if key goes up
                if event.key == pygame.K_UP:
                    pad2_move_up = False
                elif event.key == pygame.K_DOWN:
                    pad2_move_down = False
                # pad 1 check if key goes up
                elif event.key == pygame.K_w:
                    pad1_move_up = False
                elif event.key == pygame.K_s:
                    pad1_move_down = False
    
            # check if window has been resized
            if event.type == pygame.VIDEORESIZE:
                window_width = event.dict['size'][0]
                window_height = event.dict['size'][1]
                screen = pygame.display.set_mode((window_width, window_height), pygame.RESIZABLE)
                screen_rect = screen.get_rect()
    
            if event.type == pygame.USEREVENT:
                if display_text_3:
                    display_text_3 = False
                    display_text_2 = True
                elif display_text_2:
                    display_text_2 = False
                    display_text_1 = True
                elif display_text_1:
                    display_text_1 = False
                    display_text_start = True
                elif display_text_start:
                    display_text_start = False
                    ball_move = True
    
        # - moves (without draws) -
    
        # move paddle 1
    
        if pad1_move_up and pad1_rect.top > 0: # > screen_rect.top:
            pad1_rect.y -= SQUARE_SPEED
            if pad1_rect.top < 0:
                pad1_rect.top = 0
    
        if pad1_move_down and pad1_rect.bottom < screen_rect.bottom:
            pad1_rect.y += SQUARE_SPEED
            if pad1_rect.bottom > screen_rect.bottom:
                pad1_rect.bottom =  screen_rect.bottom
    
        # move paddle 2
    
        if pad2_move_up and pad2_rect.top > 0: # > screen_rect.top:
            pad2_rect.y -= SQUARE_SPEED
            if pad2_rect.top < 0:
                pad2_rect.top = 0
    
        if pad2_move_down and pad2_rect.bottom < screen_rect.bottom:
            pad2_rect.y += SQUARE_SPEED
            if pad2_rect.bottom > screen_rect.bottom:
                pad2_rect.bottom =  screen_rect.bottom
    
        # check if ball is out of bounds
        if play_start:
    
            # move ball
            if ball_move:
                ball_rect.x += ball_speed_x
    
                # check FRONT collision with pads
    
                if pad1_rect.colliderect(ball_rect):
                    #if 
                    ball_rect.left = pad1_rect.right 
                    ball_speed_x = -ball_speed_x 
                    print("Front collision right pad")
    
                if pad2_rect.colliderect(ball_rect):
                    ball_rect.right = pad2_rect.left
                    ball_speed_x = -ball_speed_x 
                    print("Front collision left pad")
    
                ball_rect.y += ball_speed_y
    
                # check SIDE collision with pads
    
                if pad1_rect.colliderect(ball_rect):
                    # move from top
                    if ball_speed_y > 0:
                        ball_rect.bottom = pad1_rect.top
                        print("Top collision right pad")
    
                    # move from bottom
                    else: # elif ball_speed_y < 0:
                        ball_rect.top = pad1_rect.bottom
                        print("Bottom collision right pad")
                    # change both or only Y (depends on what effect you need)
                    ball_speed_x = -ball_speed_x
                    ball_speed_y = -ball_speed_y
    
                if pad2_rect.colliderect(ball_rect):
                    # move from top
                    if ball_speed_y > 0:
                        ball_rect.bottom = pad2_rect.top
                        print("Top collision left pad")
    
                    # move from bottom
                    else: # if ball_speed_y < 0:
                        ball_rect.top = pad2_rect.bottom
                        print("Bottom collision left pad")
                    # change both or only Y (depends on what effect you need)
                    ball_speed_x = -ball_speed_x
                    ball_speed_y = -ball_speed_y
    
                # check collision with border
    
                if ball_rect.left <= 0: # <= screen_rect.left:
                    ball_speed_x = -ball_speed_x 
                    print("left side")
                    print("point for left player")
                    # move ball to center 
                    ball_rect.center = screen_rect.center
                    ball_speed_x = ball_speed * random.choice([1, -1])
                    ball_speed_y = ball_speed * random.choice([1, -1])
                if ball_rect.right >= screen_rect.right:
                    ball_speed_x = -ball_speed_x 
                    print("right side")
                    print("point for right player")
                    # move ball to center 
                    ball_rect.center = screen_rect.center
                    ball_speed_x = ball_speed * random.choice([1, -1])
                    ball_speed_y = ball_speed * random.choice([1, -1])
    
    
                if ball_rect.top <= 0: # <= screen_rect.top:
                    ball_speed_y = -ball_speed_y
                    print("top side")
                if ball_rect.bottom >= screen_rect.bottom:
                    ball_speed_y = -ball_speed_y
                    print("bottom side")
    
    
        # - draws (without moves) -
    
        screen.fill(BLACK)
    
        if not play_start:
            screen.blit(text_press_space, text_press_space_rect)
        else: # if play_start:
            if display_text_3:
                screen.blit(text_3, text_3_rect)
            if display_text_2:
                screen.blit(text_2, text_2_rect)   
            if display_text_1:
                screen.blit(text_1, text_1_rect)
            if display_text_start:
                screen.blit(text_start, text_start_rect)
    
            screen.blit(ball, ball_rect)  
    
        # draw pads
        pygame.draw.rect(screen, WHITE, pad1_rect, 0)
        pygame.draw.rect(screen, WHITE, pad2_rect, 0)
    
        pygame.display.flip()
    
        clock.tick(FPS)
    
    # --- end ---
    
    pygame.quit()