Skip to content
Advertisement

noisy bouncing in pygame

I’m cannibalising code from here to make a ball bounce around.

Here is a stripped-down version of the code:

import pygame
import random
 
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
 
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500
BALL_SIZE = 25
SPEED = 10

class Ball:
    """
    Class to keep track of a ball's location and vector.
    """
    def __init__(self):
        self.x = 0
        self.y = 0
        self.change_x = 0
        self.change_y = 0
 
 
def make_ball():
    """
    Function to make a new, random ball.
    """
    ball = Ball()
    # Starting position of the ball.
    # Take into account the ball size so we don't spawn on the edge.
    ball.x = random.randrange(BALL_SIZE, SCREEN_WIDTH - BALL_SIZE)
    ball.y = random.randrange(BALL_SIZE, SCREEN_HEIGHT - BALL_SIZE)
 
    # Speed and direction of rectangle
    ball.change_y = SPEED
    ball.change_x = SPEED

    return ball

def main():
    """
    This is our main program.
    """
    pygame.init()
 
    # Set the height and width of the screen
    size = [SCREEN_WIDTH, SCREEN_HEIGHT]
    screen = pygame.display.set_mode(size)
 
    pygame.display.set_caption("Bouncing Balls")
 
    # Loop until the user clicks the close button.
    done = False
 
    # Used to manage how fast the screen updates
    clock = pygame.time.Clock()
 
    ball_list = []
 
    ball = make_ball()
    ball_list.append(ball)
 
    # -------- Main Program Loop -----------
    while not done:
        # --- Event Processing
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
 
        # --- Logic
        for ball in ball_list:
            # Move the ball's center
            ball.x += ball.change_x
            ball.y += ball.change_y
 
            # Bounce the ball if needed: walls
            if ball.y > SCREEN_HEIGHT - BALL_SIZE or ball.y < BALL_SIZE:
                ball.change_y *= -1
            if ball.x > SCREEN_WIDTH - BALL_SIZE or ball.x < BALL_SIZE:
                ball.change_x *= -1
        
        # --- Drawing
        # Set the screen background
        screen.fill(BLACK)

        # Draw the balls
        for ball in ball_list:
            pygame.draw.circle(screen, WHITE, [ball.x,ball.y], BALL_SIZE)
 
        # --- Wrap-up
        # Limit to 60 frames per second
        clock.tick(60)
 
        # Go ahead and update the screen with what we've drawn.
        pygame.display.flip()
 
    # Close everything down
    pygame.quit()
 
if __name__ == "__main__":
    main()

Right now, the ball always bounces off the walls at a 45 degree angle. I want to introduce some noise into this so that bouncing is slightly more realistic.

I tried to simply add random noise to the bouncing:

if ball.y > SCREEN_HEIGHT - BALL_SIZE or ball.y < BALL_SIZE:
                ball.change_y *= -1*random.uniform(0,1)
if ball.x > SCREEN_WIDTH - BALL_SIZE or ball.x < BALL_SIZE:
                ball.change_x *= -1*random.uniform(0,1)

Supposed to happen: the ball would vary the angles of bouncing off the walls. Actually happens: the ball slows down and then stops.

Advertisement

Answer

When the ball hits the wall, the ball is reflected by the wall depending on the angle of incidence and the normal vector of the wall. This can be computed using the pygame.math module.

move_vector = pygame.Vector2(ball.change_x, ball.change_y)
reflect_vector = move_vector.reflect(normal_vector)

The normal vector is perpendicular to the wall. If you want some randomness, you need to randomly rotate the normal vector:

normal_vector.rotate_ip(random.randint(-10, 10)) # +/- 10 degrees

Compelte example:

import pygame
import random
 
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
 
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500
BALL_SIZE = 25
SPEED = 10

class Ball:
    """
    Class to keep track of a ball's location and vector.
    """
    def __init__(self):
        self.x = 0
        self.y = 0
        self.change_x = 0
        self.change_y = 0
 
 
def make_ball():
    """
    Function to make a new, random ball.
    """
    ball = Ball()
    # Starting position of the ball.
    # Take into account the ball size so we don't spawn on the edge.
    ball.x = random.randrange(BALL_SIZE, SCREEN_WIDTH - BALL_SIZE)
    ball.y = random.randrange(BALL_SIZE, SCREEN_HEIGHT - BALL_SIZE)
 
    # Speed and direction of rectangle
    ball.change_y = SPEED
    ball.change_x = SPEED

    return ball

def main():
    """
    This is our main program.
    """
    pygame.init()
 
    # Set the height and width of the screen
    size = [SCREEN_WIDTH, SCREEN_HEIGHT]
    screen = pygame.display.set_mode(size)
 
    pygame.display.set_caption("Bouncing Balls")
 
    # Loop until the user clicks the close button.
    done = False
 
    # Used to manage how fast the screen updates
    clock = pygame.time.Clock()
 
    ball_list = []
 
    ball = make_ball()
    ball_list.append(ball)
 
    # -------- Main Program Loop -----------
    while not done:
        # --- Event Processing
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
 
        # --- Logic
        for ball in ball_list:
            # Move the ball's center
            ball.x += ball.change_x
            ball.y += ball.change_y
 
            # Bounce the ball if needed: walls
            normal_vector = None
            if ball.y > SCREEN_HEIGHT - BALL_SIZE:
                ball.change_y = abs(ball.change_y)
                normal_vector = pygame.Vector2(0, -1)
            elif ball.y < BALL_SIZE:
                ball.change_y = -abs(ball.change_y)
                normal_vector = pygame.Vector2(0, 1)
            elif ball.x > SCREEN_WIDTH - BALL_SIZE:
                ball.change_x = abs(ball.change_x)
                normal_vector = pygame.Vector2(-1, 0)
            elif ball.x < BALL_SIZE:
                ball.change_x = -abs(ball.change_x)
                normal_vector = pygame.Vector2(1, 0)
            
            if normal_vector:
                normal_vector.rotate_ip(random.randint(-10, 10))
                move_vector = pygame.Vector2(ball.change_x, ball.change_y)
                reflect_vector = move_vector.reflect(normal_vector)
                ball.change_x = reflect_vector.x
                ball.change_y = reflect_vector.y
        
        # --- Drawing
        # Set the screen background
        screen.fill(BLACK)

        # Draw the balls
        for ball in ball_list:
            pygame.draw.circle(screen, WHITE, [round(ball.x), round(ball.y)], BALL_SIZE)
 
        # --- Wrap-up
        # Limit to 60 frames per second
        clock.tick(60)
 
        # Go ahead and update the screen with what we've drawn.
        pygame.display.flip()
 
    # Close everything down
    pygame.quit()
 
if __name__ == "__main__":
    main()
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement