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()