Sometimes the ball doesn't bounce off the paddle in pong game

I have a simple pong game that mostly works well. But sometimes it occurs the the ball doesn't bounce of the paddle. The ball wobbles and slides along the paddle and the paddle seems to magnetically pull the ball as shown in the animation:

Every time when the rectangle which surrounds the ball, collides the the paddle rectangle, the the direction of the ball is changed:

if ball.colliderect(paddleLeft):
    move_x *=-1
if ball.colliderect(paddleRight):
    move_x *=-1

What causes the behavior?

The issue can be reproduced with the following complete, minimal and verifiable example. The position of the ball is set so that the wrong behavior occurs immediately if the right paddle is not moved:

import pygame

pygame.init()
width, height = 600, 400
window = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
radius, move_x, move_y = 10, 3, 3
ball = pygame.Rect(width//2+125, 20, radius*2, radius)
paddleHeight = 80
paddleLeft = pygame.Rect(20, (height-paddleHeight)//2, 10, paddleHeight)
paddleRight = pygame.Rect(width-30, (height-paddleHeight)//2, 10, paddleHeight)

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT: run = False

    keys = pygame.key.get_pressed()
    if keys[pygame.K_w] and paddleLeft.top > 0: paddleLeft.y -= 5
    if keys[pygame.K_s] and paddleLeft.bottom < height: paddleLeft.y += 5
    if keys[pygame.K_UP] and paddleRight.top > 0: paddleRight.y -= 5
    if keys[pygame.K_DOWN] and paddleRight.bottom < height: paddleRight.y += 5
    ball.x += move_x
    ball.y += move_y
    if ball.left <= 0 or ball.right >= width: move_x *=-1
    if ball.top <= 0 or ball.bottom >= height: move_y *=-1

    if ball.colliderect(paddleLeft): move_x *=-1
    if ball.colliderect(paddleRight): move_x *=-1

    window.fill(0)
    pygame.draw.rect(window, (255, 255, 255), paddleLeft)
    pygame.draw.rect(window, (255, 255, 255), paddleRight)
    pygame.draw.circle(window, (255, 255, 255), ball.center, radius)
    pygame.display.flip()

Solution 1:

The behavior occurs, when the ball doesn't hit the paddle at the front, but at the top or bottom. Actually the collision between the paddle and the ball is detected and the direction is changed. But the ball penetrated so deep into the paddle that the ball cannot leave the collision area with the paddle with it's next step. This causes that a collision is detected again in the next frame and the direction of the ball is changed again. Now the ball moves in the same direction as before the first collision. This process continues until the ball leaves the paddle at the bottom. This causes a zig zag movement along the front side of the paddle.

There are different solution. One option is not to reverse the direction, but to set the direction to the left when the right paddle is hit a nd to set the direction to the right when the left paddle is hit:

if ball.colliderect(paddleLeft):
    move_x = abs(move_x)
if ball.colliderect(paddleRight):
    move_x = -abs(move_x) 

Another option is to adjust the position to the ball. If the right paddle is hit, the right side of the ball must be placed to the left of the paddle. If the left paddle is hit, then the left side of the ball must be placed to the right of the paddle:

if ball.colliderect(paddleLeft):
    move_x *= -1
    ball.left = paddleLeft.right
if ball.colliderect(paddleRight):
    move_x *= -1
    ball.right = paddleRight.left