Search code examples
pythonpygamegame-physicspong

Unable to fix broken ball movement in pygame pong


I'm fairly new to programming, game programming especially. I'm trying to make pong using pygame, but have run into a slight issue. Essentially, the ball hits a paddle, stops, and then keeps going once the paddle is out of the way. Obviously I want the ball to bounce back, but I can't figure out why it won't, when I've coded (what I thought was) the appropriate logic for ball-paddle collisions. Here's my code:

# importing stuff
import sys, pygame
from pygame.locals import *

# starting pygame
pygame.init()

# defining basic colours
white = (255, 255, 255)
black = (0, 0, 0)

# set up the clock
clock = pygame.time.Clock()

# text and such
tFont = pygame.font.SysFont("monospace", 15)

# setting window res and setting display
winX, winY = 600, 300
window = pygame.display.set_mode((winX, winY))

# setting the speed for on screen stuff
playSpeed = 5 # player speed (p1 and p2)

# counts points for each player
#         1   2
points = [0, 0]

# tallies number of times FPS is counted and added to toal amount
fpsCount = 0
fpsTotal = 0

class Ball(object):
    def __init__(self, speed):
        # set default ball position in screen centre
        self.ballX = winX / 2
        self.ballY = winY / 2
        self.ballSpeed = speed
    def move(self):
        if points[0] > points[1]: # if p1 has more points than p2
            self.ballX -= self.ballSpeed # ball goes to p1's side
        elif points[0] < points[1]: # only other condition could be p2 having more points
            self.ballX += self.ballSpeed # ball goes to p2's side
        elif points[0] == points[1]: # if points are equal
            self.ballX -= self.ballSpeed # favour player 1 (change later)

        pygame.draw.circle(window, black, (self.ballX, self.ballY), 3)
    def collide(self, paddle): # unsure if 'paddle' necessary
        # if ball hits top of paddle, bounce to top
        # if hits bottom, bounce to bottom 
        # if ball hits midsection, bounce straight (middle could be about 10px?)
        if paddle == playerOne:
            self.ballX
            self.ballX += self.ballSpeed

class Paddle(object):
    # set the player number (1/2) and if it's an AI or real player
    def __init__(self, player, aiornot):
        if player == 1 and aiornot == False:
            self.coords = [[40, 130], [40, 160]]
        elif player == 2 and aiornot == False:
            self.coords = [[560, 130], [560, 160]]

        self.movement = 'stop' # sets default movement

    def move(self):
        if self.movement == 'down' and self.coords[1][1] < 300:
            self.moveDown()

        elif self.movement == 'up' and self.coords[0][1] > 0:
            self.moveUp()

        elif self.movement == 'stop':
            self.stop()

        # draw the paddle in new position
        pygame.draw.line(window, black, self.coords[0], self.coords[1], 10)

    # movement functions, for direction and such
    def moveDown(self):
        self.coords[0][1] += playSpeed
        self.coords[1][1] += playSpeed

    def moveUp(self):
        self.coords[0][1] -= playSpeed
        self.coords[1][1] -= playSpeed

    def stop(self):
        self.coords[0][1] = self.coords[0][1]
        self.coords[1][1] = self.coords[1][1]

ball = Ball(playSpeed)
playerOne = Paddle(1, False)
playerTwo = Paddle(2, False)

# main loop
while True:
    # event handling for exit
    for event in pygame.event.get():
        if event.type == QUIT:
            # print the average FPS
            print round(fpsTotal / fpsCount, 2), "fps (avg)"
            pygame.quit()
            sys.exit()
        # setting direction upon arrow key press
        elif event.type == KEYDOWN:
            if event.key == K_DOWN:
                playerOne.movement = 'down'
            elif event.key == K_UP:
                playerOne.movement = 'up'
            elif event.key == K_s:
                playerTwo.movement = 'down'
            elif event.key == K_w:
                playerTwo.movement = 'up'
        # when the key is released, stop moving
        elif event.type == KEYUP:
            if event.key == K_DOWN or event.key == K_UP:
                playerOne.movement = 'stop'
            elif event.key == K_s or event.key == K_w:
                playerTwo.movement = 'stop'
            print "player1:", playerOne.coords
            print "player2:", playerTwo.coords

    # this is a mess... if the balls x coords = the paddles x coords, and the balls y
    # coord is somewhere between the start and end point of the paddle, then do the balls
    # collision function on the paddle
    if ball.ballX >= playerOne.coords[0][0] and ball.ballX <= playerOne.coords[1][0]: # or ball.ballX == 40
        if ball.ballY >= playerOne.coords[0][1] and ball.ballY <= playerOne.coords[1][1]:
            ball.collide(playerOne)

    print ball.ballX, ball.ballY

    # fill the window
    window.fill(white)
    # redraw the bat with new position
    playerOne.move()
    playerTwo.move()
    # redraw the ball with new position
    ball.move()
    # set FPS to 60
    clock.tick(60)
    # for working out average FPS
    fpsCount += 1
    fpsTotal += clock.get_fps()
    # set window title
    pygame.display.set_caption("Long Pong")
    # render FPS to text, display text
    text = tFont.render(str(round(clock.get_fps(), 2)), 8, black)
    window.blit(text, (545, 5))
    # update display
    pygame.display.update()

And also a pastebin here if it's easier to look at/copy.

I appreciate any help with this, I've been able to work out any other problem on my own, but I can't tell what I'm missing here.


Solution

  • I hope the below code helps. Although my program was a bit different because every time the ball hit the paddle we had to generate a new ball.

    import random
    from livewires import games, color
    
    games.init(screen_width = 640, screen_height = 480, fps = 50)
    
    class Ball(games.Sprite):
    
        quit_label = games.Text(value = "Press Q to Quit", size = 25, color = color.white, top = 5, right = 130,
                                  is_collideable = False)
        games.screen.add(quit_label)
    
        def update(self):
            if self.right > games.screen.width:
                self.dx = -self.dx
    
            if self.left < 0:
                self.game_over()
    
            if self.bottom > games.screen.height or self.top < 0:
                self.dy = -self.dy
    
            #pressing 'q' quits the game
            if games.keyboard.is_pressed(games.K_q):
                self.game_over()
    
        def bounce(self):
            self.dx = -self.dx
    
    
        def game_over(self):
            """ End the game. """
            end_message = games.Message(value = "Game Over",
                                        size = 90,
                                        color = color.red,
                                        x = games.screen.width/2,
                                        y = games.screen.height/2,
                                        lifetime = 3 * games.screen.fps,
                                        after_death = games.screen.quit,
                                        is_collideable = False)
            games.screen.add(end_message)
            self.destroy()
    
    class Paddle(games.Sprite):
        image = games.load_image("paddle.bmp")
    
        score = games.Text(value = 0, size = 25, color = color.white, top = 15, 
            right = games.screen.width - 10, is_collideable = False)
        games.screen.add(score)
    
        def __init__(self):
            super(Paddle, self).__init__(image = Paddle.image, x = games.mouse.x, bottom = games.screen.height)
    
        def update(self):
            """ Move to mouse y position. """
            self.y = games.mouse.y
    
            if self.left > 0:
                self.left = 10
    
            if self.right > games.screen.height:
                self.right = games.screen.height
    
            self.check_catch()
    
        def check_catch(self):
            for ball in self.overlapping_sprites:
                Paddle.score.value += 1
                ball_image2 = games.load_image("ball.bmp")
                ball2 = Ball(image = ball_image2,
                          x = games.screen.width/2,
                          y = games.screen.height/2,
                          dx = 1,
                          dy = 1)
                games.screen.add(ball2)
                ball.bounce()
    
    
    
    def main():
        wall_image = games.load_image("background.bmp", transparent = False)
        games.screen.background = wall_image
    
        ball_image = games.load_image("ball.bmp")
        the_ball = Ball(image = ball_image,
                          x = games.screen.width/2,
                          y = games.screen.height/2,
                          dx = 1,
                          dy = 1)
        games.screen.add(the_ball)
    
        the_paddle = Paddle()
        games.screen.add(the_paddle)
    
        games.mouse.is_visible = False
    
        games.screen.mainloop()
    
    # kick it off!
    main()