Reputation: 535
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.
Upvotes: 2
Views: 545
Reputation: 88
So it is more a problem of mathematic/physics than python. As far as I understand, change_y
and change_x
represent the speed of the ball. You want to have a constant speed (in magnitude I mean) but a noisy angle.
In terms of code, it means :
# This is the speed of your ball.
math.sqrt( (ball.change_y)**2 + (ball.change_x)**2 ) = constant
So you want to change your code this way :
if ball.y > SCREEN_HEIGHT - BALL_SIZE or ball.y < BALL_SIZE:
ball.change_y *= -1*random.uniform(0,1)
ball.change_x = math.sqrt(2*(SPEED**2) - ball.change_y**2)
if ball.x > SCREEN_WIDTH - BALL_SIZE or ball.x < BALL_SIZE:
ball.change_x *= -1*random.uniform(0,1)
ball.change_y = math.sqrt(2*(SPEED**2) - ball.change_x**2)
Hence, the speed will be constant but the relative x and y components will be variable.
Upvotes: 1
Reputation: 211135
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()
Upvotes: 1