1

I'm making a simple Pong game with Python and Pygame, you can get it here. So far I've got a ball which moves around and bounces off the walls of the screen and a paddle which stays within the screen. I've also (sort of) made the ball bounce off of the paddle too - if the ball hits the left or right side of the paddle, it will change direction correctly. However, if the ball hits the top or bottom side of the paddle, it "phases" through the paddle and goes somewhere else entirely, which is not what I want. How can I remove this behavior? Thank you in advance for your replies!

import pygame
from math import sin, cos, radians

screen_width = 960
screen_height = 540
fps = 60

white = (255, 255, 255)
black = (0, 0, 0)

pygame.init()

clock = pygame.time.Clock()
screen = pygame.display.set_mode((screen_width, screen_height))

class Ball(pygame.sprite.Sprite):
    def __init__(self, speed, angle):
        groups = [all_sprites]
        pygame.sprite.Sprite.__init__(self, groups)
        
        self.speed = speed
        self.angle = angle
        
        self.size = 20
        self.colour = white

        self.image = pygame.Surface((self.size, self.size))
        self.image.fill(self.colour)

        self.rect = self.image.get_rect()
        self.rect.x = int(screen_width / 2) - int(self.size / 2)
        self.rect.y = int(screen_height / 2) - int(self.size / 2)

    def update(self):
        self.dx = int(self.speed * sin(radians(self.angle)))
        self.dy = int(self.speed * cos(radians(self.angle)))

        self.rect.x += self.dx
        self.rect.y += self.dy

        if self.rect.x >= screen_width - self.size or self.rect.x <= 0:
            self.angle = 360 - self.angle

        if self.rect.y >= screen_height - self.size or self.rect.y <= 0:
            self.angle = 180 - self.angle

class Paddle(pygame.sprite.Sprite):
    def __init__(self):
        groups = [all_sprites, paddles]
        pygame.sprite.Sprite.__init__(self, groups)

        self.keys = [pygame.K_UP, pygame.K_DOWN]

        self.size = (20, 80)
        self.colour = white

        self.image = pygame.Surface(self.size)
        self.image.fill(self.colour)

        self.rect = self.image.get_rect()
        self.rect.x = 20
        self.rect.y = int(screen_height / 2) - int(self.size[1] / 2)

    def update(self):
        self.dy = 0
        keystates = pygame.key.get_pressed()

        if keystates[self.keys[0]]:
            self.dy = -10
        if keystates[self.keys[1]]:
            self.dy = 10
        
        self.rect.y += self.dy

        if self.rect.y >= screen_height - self.size[1]:
            self.rect.y = screen_height - self.size[1]

        if self.rect.y <= 0:
            self.rect.y = 0

all_sprites = pygame.sprite.Group()
paddles = pygame.sprite.Group()
ball = Ball(10, 45)
paddle = Paddle()

running = True
while running:
    clock.tick(fps)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    all_sprites.update()

    for paddle in paddles:
        if pygame.sprite.collide_rect(paddle, ball):
            ball.angle = 360 - ball.angle

    screen.fill(black)
    all_sprites.draw(screen)

    pygame.display.flip()

pygame.quit()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
hakmad
  • 39
  • 4
  • 1
    Please include the relevant code in the post itself, so as to keep it as self-contained as possible. – AMC Jul 06 '20 at 20:46

4 Answers4

1

The issue is caused by the fact, that the speed of the ball is grater than 1 and the ball doesn't exactly hit the paddle. That may cause multiple collisions and changes of the direction in consecutive frames.
You have to restrict the position of the ball to the borders of the paddel. Is the ball is left of the paddle, then the right of the ball has to be set to the left of the paddle (ball.rect.right = paddle.rect.left) and if the ball is at the right of the paddle, the the left of the ball has to be set to the right of the paddel (ball.rect.left = paddle.rect.right):

while running:
    # [...]

    for paddle in paddles:
        if pygame.sprite.collide_rect(paddle, ball):
            ball.angle = 360 - ball.angle
            if ball.rect.x > paddle.rect.x:
                ball.rect.left = paddle.rect.right
            else:
                ball.rect.right = paddle.rect.left
    # [...]
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
0

Here is one way to do this.

class Ball(pygame.sprite.Sprite):
    ...
    def bounce(self):
        self.speed *= -1
    
    def is_collided_with(self, sprite):
        return self.rect.colliderect(sprite.rect)

In the while loop:

if ball.is_collided_with(paddle):
    ball.bounce()

So in the while loop, it constantly checks whether the paddle has collided the ball. If it did, then the direction should change.

  • I might misunderstood your question, so feel free to correct this code. –  Jul 06 '20 at 20:36
0

Create a rectangle from the last position to the current position and check if it overlaps with the bounding rectangle of your paddle. if you don't do this, and the delta-x is > padde-width, it will phase through.

Lennart Steinke
  • 584
  • 5
  • 11
0

The problem is that if the ball hits the paddle while going right the bouncing will send it into the left wall where it will immediately bounce back in the paddle then to the wall then to the paddle and so on until it exits the paddle.

A simple solution is bouncing with the paddle only if x speed is < 0 (i.e. the ball is going to the left).

for paddle in paddles:
    if (sin(radians(ball.angle)) < 0 and
        pygame.sprite.collide_rect(paddle, ball)):
        ball.angle = 360 - ball.angle

when the ball is moving from left to right it will go through the paddle without bouncing.

6502
  • 112,025
  • 15
  • 165
  • 265