Search code examples
pythonpygamecollision-detectionpong

Ping pong clone collision detection


It's my first actual game-making attempt. I know code could be much better with classes or functions but I am trying to make the code simple (and shorter)

i am having problems with the collision detection of the ball and the bar/paddle. The ball sticks to the bar then carries on in the same direction. I tried some pygame collision functions and they didn't work cause i was using the surface method instead of the rect method. My question is:

how do make the ball bounce off the bar/paddle? and/or Which method or function should i be using?

#!\user\bin\ env python
import pygame, sys
from pygame.locals import *
from sys import exit
pygame.init()
#game constants
fps = 60
clock = pygame.time.Clock()
#Colors
BLACK = (  0,   0,   0)
WHITE =  (255, 255, 255)
BLUE =    (  0,   0, 255)
GREEN = (  0, 255,   0)
RED =      (255,   0,   0)

size = window_WIDTH, window_HEIGHT = 800,600
window = pygame.display.set_mode( size , pygame.RESIZABLE)
window.fill(BLACK)
ball = pygame.Surface((25, 25))
ball = ball.convert()
ball.fill(WHITE)
ball_x, ball_y = (window_WIDTH/2 -12), (window_HEIGHT/2 -12 )
ball_speed_x = 7  #ball speed = 20
ball_speed_y = 7

bar = pygame.Surface((15, 90))
bar.fill(WHITE)
bar1 = bar.convert()
bar2 = bar.convert()

bar_speed = 0
bar1_x,bar1_y = (50), (window_HEIGHT/2-60)

bar2_x,bar2_y = (window_WIDTH - 50), (window_HEIGHT/2-60)

middle_line = pygame.Surface((2,window_HEIGHT))
middle_line.fill(WHITE)
middle_line = middle_line.convert()

running = True
while running:
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ) or ( event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == K_UP:
                bar_speed -= 20
            if event.key == K_DOWN:
                bar_speed += 20
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_UP:
                bar_speed = 0
            if event.key == pygame.K_DOWN:
                bar_speed = 0

#since i don't know anything about collision, ball hitting bars goes like this.
    #colisions with the walls (veritical walls for bars)
    if bar1_y >= (window_HEIGHT - 90) or bar1_y <= 0:
        bar_speed = 0
    if bar2_y >= (window_HEIGHT - 90) or bar2_y <= 0:
        bar_speed = 0

    # collisions with bars (ball)
    if ball_x <= (bar1_x+15):
        if ball_y >= bar1_y:
            if ball_y <= (bar1_y + 45):  #checks if ball y cord is between bar y cord and bar length
                # To right direction up
                #ball_x += (bar1_x +15)
                #ball_x += ball_speed_x
                #ball_speed_y *= -1
                ball_y *= -1
                ball_x = (bar1_x +15)
                ball_speed_y = -ball_speed_y
                ball_speed_x = ball_speed_x

                # To right direction down
            if ball_y +45 <= (bar1_y + 90):#half down bar make y cord negative
                #ball_speed_y = ball_speed_y
                #ball_x += (bar1_x +15)
                #ball_x += ball_speed_x
                ball_y *= -1
                ball_x = (bar1_x +15)
                ball_speed_y = -ball_speed_y
                ball_speed_x = ball_speed_x

    if (ball_x+25) >= bar2_x:
        if ball_y >= bar2_y:
            if ball_y <= (bar2_y + 44):  #checks if ball y cord is between bar y cord and bar length
                # To right direction up
                #ball_x = (bar1_x )#+15)
                #ball_x -= ball_speed_x
                ball_y *= -1
                ball_x = (bar1_x +15)
                ball_speed_y = -ball_speed_y
                ball_speed_x = -ball_speed_x

            if ball_y +45 <= (bar2_y + 90):#half down bar make y cord negative
                #ball_speed_y = ball_speed_y
                #ball_x = (bar2_x )#+15)
                #ball_x = ball_speed_x
                ball_y *= -1
                ball_x = (bar1_x +15)
                ball_speed_y = -ball_speed_y
                ball_speed_x = -ball_speed_x

    #collisions of ball with up down walls
    if ball_y == 0:
        if ball_x > window_WIDTH/2:
            ball_speed_y = ball_speed_y
            ball_speed_x = ball_speed_x
        if ball_x < window_WIDTH/2:
            ball_speed_y = ball_speed_y
            ball_speed_x *= -1

    if ball_y == window_HEIGHT:
        if ball_x > window_WIDTH/2:
            ball_speed_y *= -1
            ball_speed_x = ball_speed_x
        if ball_x < window_WIDTH/2:
            ball_speed_y *= -1
            ball_speed_x *= -1
    #AI player
    if ball_x >= window_WIDTH/2:
        if not bar2_y == ball_y + 7.5:
            if bar2_y < ball_y + 7.5:
                bar2_y += bar_speed
            if  bar2_y > ball_y - 42.5:
                bar2_y -= bar_speed
        else:
            bar2_y == ball_y + 7.5
    if bar1_y >= 420.: bar1_y = 420.
    elif bar1_y <= 10. : bar1_y = 10.
    if bar2_y >= 420.: bar2_y = 420.
    elif bar2_y <= 10.: bar2_y = 10.

    bar1_y+= bar_speed
    ball_x += ball_speed_x
    ball_y += 1


    window.fill(BLACK)
    window.blit(middle_line,(window_WIDTH/2,0))
    window.blit(bar1,(bar1_x, bar1_y))
    window.blit(bar2,(bar2_x,bar2_y))
    window.blit(ball, (ball_x, ball_y))

    clock.tick(fps)
    pygame.display.update()
pygame.quit()

P.S I want the final game to below 100 lines of code


Solution

  • I suggest you use pygame's Rect, Sprite and Group class for a number if reasons:

    • these make it easy to check for collisions with pygame.sprite.spritecollide
    • it's also easy to use pixel-perfect collision with pygame.sprite.collide_mask
    • checking if the ball bounces of the top or bottom of the screen also becomes dead easy, using Rect.top and Rect.bottom
    • preventing the paddles going out of screen is as simple as calling Rect.clamp_ip

    Here's a basic 2-player pong that should provide a good starting point. Note how simple the code is:

    import pygame
    import math
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    
    # some helpful vector math functions
    def normalize(v):
        vmag = magnitude(v)
        return [v[i]/vmag  for i in range(len(v))]
    def magnitude(v):
        return math.sqrt(sum(v[i]*v[i] for i in range(len(v))))
    def add(u, v):
        return [ u[i]+v[i] for i in range(len(u)) ]
    
    class Paddle(pygame.sprite.Sprite):
        def __init__(self, start_pos, up_key, down_key):
            pygame.sprite.Sprite.__init__(self)
            # the image is just a white rect
            self.image = pygame.surface.Surface((20, 100))
            self.image.fill(pygame.color.Color('White'))
            self.image.set_colorkey(pygame.color.Color('Black'))
            self.rect = self.image.get_rect(topleft=start_pos)
            # using a mask so we can use pixel perfect collision
            self.mask = pygame.mask.from_surface(self.image)
            self.up_key, self.down_key = up_key, down_key
        def update(self, pressed):
            if pressed[self.up_key]:   self.rect.move_ip(0, -3)
            if pressed[self.down_key]: self.rect.move_ip(0,  3)
            # keep the paddle inside the screen
            self.rect.clamp_ip(pygame.display.get_surface().get_rect())
    
    class Ball(pygame.sprite.Sprite):
        def __init__(self, start_pos):
            pygame.sprite.Sprite.__init__(self)
            # the image is just a white ball
            self.image = pygame.surface.Surface((20, 20))
            self.rect = self.image.get_rect(center=start_pos)
            pygame.draw.circle(self.image, pygame.color.Color('White'), self.image.get_rect().center, 10)
            self.image.set_colorkey(pygame.color.Color('Black'))
            # using a mask so we can use pixel perfect collision
            self.mask = pygame.mask.from_surface(self.image)
            # the vector we use to move the ball
            self.move_v = (1, 0.7)
            # store the absolute position in self.pos
            # because a rect can only use integers
            self.pos = self.rect.center
        def update(self, pressed):
            # check if the ball collides with any other sprite
            collide = [s for s in pygame.sprite.spritecollide(self, self.groups()[0], False, pygame.sprite.collide_mask) if s != self]
            if collide:
                # warning: this does not handle the case of the ball hits 
                # the top or bottom of the paddle, only the sides.
                self.move_v = [-self.move_v[0], self.move_v[1]]
    
            # check if the ball would go out of screen
            display = pygame.display.get_surface().get_rect()
            if self.rect.top < display.top and self.move_v[1] < 0 or \
               self.rect.bottom > display.bottom and self.move_v[1] > 0:
                self.move_v = [self.move_v[0], -self.move_v[1]]
    
            # apply a constant speed and update the position
            move_vector = [c * 4 for c in normalize(self.move_v)]
            self.pos = add(self.rect.center, move_vector)
            self.rect.center = map(int, self.pos)
    
    player1 = Paddle((30,  190), pygame.K_w , pygame.K_s)
    player2 = Paddle((590, 190), pygame.K_UP, pygame.K_DOWN)
    ball = Ball(screen.get_rect().center)
    sprites = pygame.sprite.Group(player1, player2, ball)
    
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT: break
        else:
            pressed = pygame.key.get_pressed()
            sprites.update(pressed)
            screen.fill(pygame.color.Color('black'))
            sprites.draw(screen)
            pygame.display.flip()
            clock.tick(60)
            continue
        break
    pygame.quit()
    

    enter image description here