How to Make Ball Bounce Off Wall with Pygame

How to make ball bounce off wall with PyGame?

The issue are the multiple nested loops. You have an application loop, so use it.

Continuously move the ball in the loop:

box.y -= box.vel_y        
box.x += box.vel_x

Define a rectangular region for the ball by a pygame.Rect object:

bounds = window.get_rect() # full screen

or

bounds = pygame.Rect(450, 200, 300, 200)  # rectangular region

Change the direction of movement when the ball hits the bounds:

if box.x - box.radius < bounds.left or box.x + box.radius > bounds.right:
box.vel_x *= -1
if box.y - box.radius < bounds.top or box.y + box.radius > bounds.bottom:
box.vel_y *= -1

See the example:

Sample Image

box = Circle(600,300,10)

run = True
start = False
clock = pygame.time.Clock()

while run:
clock.tick(120)

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

keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
start = True

bounds = pygame.Rect(450, 200, 300, 200)
if start:
box.y -= box.vel_y
box.x += box.vel_x

if box.x - box.radius < bounds.left or box.x + box.radius > bounds.right:
box.vel_x *= -1
if box.y - box.radius < bounds.top or box.y + box.radius > bounds.bottom:
box.vel_y *= -1

window.fill((0,0,0))
pygame.draw.rect(window, (255, 0, 0), bounds, 1)
pygame.draw.circle(window, (44,176,55), (box.x, box.y), box.radius)
pygame.display.update()

How to make ball bounce off triangle in pygame?

Interesting task. A triangle can be defined by a simple list:

triangle = [(250, 220), (400, 300), (100, 300)]

The triangle can be drawn by pygame.draw.polygon()

pygame.draw.polygon(WINDOW, RED, triangle, 0)

Use pygame.math.Vector2 to define the position and the motion vector of the ball:

ballvec = pygame.math.Vector2(1, 1)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = 64

Create a function, which does the collision detection. The function has to detect if the ball hits a line. If the line is hit, then the motion vector of the ball is reflected on the line.

The line is represented by 2 points (lp0, lp1), which are pygame.math.Vector2 objects.

The position of the ball (pt) and the motion vector (dir) are pygame.math.Vector2 objects, too:

def isect(lp0, lp1, pt, dir, radius):
# direction vector of the line
l_dir = (lp1 - lp0).normalize()
# normal vector to the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0])
# distance to line
d = (lp0-pt).dot(nv)
# intersection point on endless line
ptX = pt + nv * d
# test if the ball hits the line
if abs(d) > radius or dir.dot(ptX-pt) <= 0:
return dir
if (ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0:
return dir
# reflect the direction vector on the line (like a billiard ball)
r_dir = dir.reflect(nv)
return r_dir

Append the window rectangle and the triangle to a list of lines. Ech line is represented by a tuple of 2 pygame.math.Vector2 objects:

# add screen rect
screen_rect = [(0, 0), (0, 300), (500, 300), (500, 0)]
for i in range(len(screen_rect)):
p0, p1 = screen_rect[i], screen_rect[(i+1) % len(screen_rect)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))

# add red trianlge
triangle = [(250, 220), (400, 300), (100, 300)]
for i in range(len(triangle)):
p0, p1 = triangle[i], triangle[(i+1) % len(triangle)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))

Do the collision detection in a loop, which traverse the lines. If the ball hits a line, then the motion vector is replaced by the reflected motion vector:

for line in line_list:
ballvec = isect(*line, ballpos, ballvec, balldiameter/2)

Finally update the position of the ball an the ball rectangle:

ballpos = ballpos + ballvec
ballRect.x, ballRect.y = ballpos[0]-ballRect.width/2, ballpos[1]-ballRect.height/2

See the example code, where I applied the suggested changes to your original code. My ball image has a size of 64x64. The ball diameter has to be set to this size (balldiameter = 64):


Minimal example

Sample Image

import pygame

pygame.init()
window = pygame.display.set_mode((500, 300))

try:
ball = pygame.image.load("Ball64.png")
except:
ball = pygame.Surface((64, 64), pygame.SRCALPHA)
pygame.draw.circle(ball, (255, 255, 0), (32, 32), 32)
ballvec = pygame.math.Vector2(1.5, 1.5)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = ball.get_width()

def reflect_circle_on_line(lp0, lp1, pt, dir, radius):
l_dir = (lp1 - lp0).normalize() # direction vector of the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0]) # normal vector to the line
d = (lp0-pt).dot(nv) # distance to line
ptX = pt + nv * d # intersection point on endless line
if (abs(d) > radius or dir.dot(ptX-pt) <= 0 or # test if the ball hits the line
(ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0):
return dir
r_dir = dir.reflect(nv) # reflect the direction vector on the line (like a billiard ball)
return r_dir

triangle1 = [(250, 220), (400, 300), (100, 300)]
triangle2 = [(250, 80), (400, 0), (100, 0)]
screen_rect = [(0, 0), (0, window.get_height()), window.get_size(), (window.get_width(), 0)]

line_list = []
for p0, p1 in zip(triangle1, triangle1[1:] + triangle1[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(triangle2, triangle2[1:] + triangle2[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(screen_rect, screen_rect[1:] + screen_rect[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))

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

for line in line_list:
ballvec = reflect_circle_on_line(*line, ballpos, ballvec, balldiameter/2)
ballpos = ballpos + ballvec

window.fill((64, 64, 64))
pygame.draw.polygon(window, (255, 0, 0), triangle1, 0)
pygame.draw.polygon(window, (0, 0, 255), triangle2, 0)
window.blit(ball, (round(ballpos[0]-balldiameter/2), round(ballpos[1]-balldiameter/2)))
pygame.display.flip()

pygame.quit()

pygame Get the balls to bounce off each other

Define a circle by the ball Sprites and use the algorithm from the answer to Pygame how to let balls collide for a function, that can compute reflection of bouncing balls:

def reflectBalls(ball_1, ball_2):
v1 = pygame.math.Vector2(ball_1.rect.center)
v2 = pygame.math.Vector2(ball_2.rect.center)
r1 = ball_1.rect.width // 2
r2 = ball_2.rect.width // 2
if v1.distance_to(v2) < r1 + r2 - 2:
nv = v2 - v1
if nv.length() > 0:
ball_1.dir = ball_1.dir.reflect(nv)
ball_2.dir = ball_2.dir.reflect(nv)

Ensure that the balls have different initial positions:

start, velocity, direction = (200, 200), 10, (random.random(), random.random())
ball_1 = Ball(start, velocity, direction)

start, velocity, direction = (300, 300), 10, (random.random(), random.random())
ball_2 = Ball(start, velocity, direction)

Alternatively you can avoid that the balls are sticking together by improving the bounce algorithm. Only bounce the balls, if the next positions of the balls are closer then the current positions:

def reflectBalls(ball_1, ball_2):
v1 = pygame.math.Vector2(ball_1.rect.center)
v2 = pygame.math.Vector2(ball_2.rect.center)
r1 = ball_1.rect.width // 2
r2 = ball_2.rect.width // 2
d = v1.distance_to(v2)
if d < r1 + r2 - 2:
dnext = (v1 + ball_1.dir).distance_to(v2 + ball_2.dir)
nv = v2 - v1
if dnext < d and nv.length() > 0:
ball_1.dir = ball_1.dir.reflect(nv)
ball_2.dir = ball_2.dir.reflect(nv)

Test if each ball collides with any other ball:

ball_list = all_balls.sprites()
for i, b1 in enumerate(ball_list):
for b2 in ball_list[i+1:]:
reflectBalls(b1, b2)

Complete example: Sample Image repl.it/@Rabbid76/PyGame-BallsBounceOff

Sample Image

import pygame
import random

class Ball(pygame.sprite.Sprite):

def __init__(self, startpos, velocity, startdir):
super().__init__()
self.pos = pygame.math.Vector2(startpos)
self.velocity = velocity
self.dir = pygame.math.Vector2(startdir).normalize()
self.image = pygame.image.load("small_ball.png").convert_alpha()
self.rect = self.image.get_rect(center = (round(self.pos.x), round(self.pos.y)))

def reflect(self, NV):
self.dir = self.dir.reflect(pygame.math.Vector2(NV))

def update(self):
self.pos += self.dir * 10
self.rect.center = round(self.pos.x), round(self.pos.y)

if self.rect.left <= 0:
self.reflect((1, 0))
self.rect.left = 0
if self.rect.right >= 700:
self.reflect((-1, 0))
self.rect.right = 700
if self.rect.top <= 0:
self.reflect((0, 1))
self.rect.top = 0
if self.rect.bottom >= 700:
self.reflect((0, -1))
self.rect.bottom = 700

pygame.init()
window = pygame.display.set_mode((700, 700))
pygame.display.set_caption('noname')
clock = pygame.time.Clock()

all_balls = pygame.sprite.Group()

start, velocity, direction = (200, 200), 10, (random.random(), random.random())
ball_1 = Ball(start, velocity, direction)

start, velocity, direction = (300, 300), 10, (random.random(), random.random())
ball_2 = Ball(start, velocity, direction)

all_balls.add(ball_1, ball_2)

def reflectBalls(ball_1, ball_2):
v1 = pygame.math.Vector2(ball_1.rect.center)
v2 = pygame.math.Vector2(ball_2.rect.center)
r1 = ball_1.rect.width // 2
r2 = ball_2.rect.width // 2
d = v1.distance_to(v2)
if d < r1 + r2 - 2:
dnext = (v1 + ball_1.dir).distance_to(v2 + ball_2.dir)
nv = v2 - v1
if dnext < d and nv.length() > 0:
ball_1.reflect(nv)
ball_2.reflect(nv)

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

all_balls.update()

ball_list = all_balls.sprites()
for i, b1 in enumerate(ball_list):
for b2 in ball_list[i+1:]:
reflectBalls(b1, b2)

window.fill(0)
pygame.draw.rect(window, (255, 0, 0), (0, 0, 700, 700), 1)
all_balls.draw(window)
pygame.display.flip()

Ball doesn't bounce of screen edges in PyGame

You need to change your move method a bit, you need to remove the else blocks as they mess up ball's movement, just always move the ball once when calling move. You can also combine checking whether ball is on edge for an axis in one line (using or):

def move(self):

if self.top <= 0 or self.bottom >= HEIGHT:
self.speedY *= -1

if self.left <= 0 or self.right >= WIDTH:
self.speedX *= -1

self.y += self.speedY
self.x += self.speedX

How to make ball bounce off paddle

This should work:

# Inside the main loop.
if ball_rect.collidelist([blue_rect, yellow_rect]) > -1:
ball_x_speed = -ball_x_speed

Hope it helps!

How to make a bouncy ball in PyGame, Python

I'll work through this explanation thoroughly so don't be surprised if you find some parts obvious.

Now, as I mentioned in my comment, to get a true understanding of whats going on it can be helpful to try and write the logic without any GUI. This places emphasis on your conceptual understanding.

Concept one: x & y coordinates

Alright, lets begin. When it comes to 2D graphics, in any 2D environment we know that an item's position can be represented by two variables: an x and a y coordinate. There is no requirement that these variables are specifically x and y, its just convention, but what is important is the idea of having these two variables.

By changing these two values in any combination, we can position an item anywhere in that 2D world. The same applies to a number line for example, we only need one variable to position an item anywhere in that 1D world.

In this image we can see that the big cross has been positioned in the 2D world (the "2D world" being the graph in this case). The combination of the two variables x and y being (200, 100) positions the cross at that specific place, and no other combination of these two variables can place it exactly there.

Cross at 200, 100

This is the first important concept to understand: the idea of a coordinate system. Being able to position items in a 2D space by changing two values. Or you could see it as that each position in a 2D space has its own x and y coordinates, both are accurate.

Again, just as using the letters x and y is convention, its also convention to picture that the x value describes the horizontal position and the y value describes the vertical position. You would typically see that a bigger x value is 'more right', and a bigger y value is 'more up'. This places the coordinates (0, 0) at the bottom left of such a 2D world.

Not always though, as with this graph and Pygame for example! Pygame sees a bigger y value as 'more down', so Pygame's coordinates (0, 0) are in fact at the top left of its 2D world (in this case the "2D world" is Pygame's graphical window).

Concept two: velocity

Its all good and well to be able to position items in a 2D world, but its pretty useless if we can't move these items! Now something which is quite relevant here is a basic knowledge of frames. The Pygame window updates itself many times every second, because - well if it didn't then the picture wouldn't change! Every time it updates, its called a frame. This is where the term frames per second (fps) comes from, essentially telling you how many times it updates every second.

So, lets say we want to move an item right (aka increasing the x value) in our 2D world (again, this "2D world" in Pygame is the window). How would we go about doing this? If the item is at coordinate (200, 100), to move it right all we need to do is increase the x value right? Okay then, lets see what it would look like if we increased the big cross' x value by 100 (so its moving right).

Moving a big cross right

But if we did this all in just one frame, then it would essentially just teleport 100 to the right! And I wouldn't call that 'moving'. If I think of something moving, then its more of a gradual shift from one position to another.

Hmm, okay.. so how about instead of moving it 100 all in one go, we move it by 5, twenty times. 20 * 5 is still 100, so by the end it will still have moved 100, but in this case, because we're spreading out the movement into twenty blocks of 5, it appears more gradual to the human eye.

More specifically, we can move the item by 5 every frame, for twenty frames. Since the window updates a certain number of frames every second (typically this can default to 30 or 60 frames per second), there is a small time gap between each frame, and by only moving by 5 each frame, we end up spreading out the movement of 100 from an instant to just under a second.

All this time, we've been talking about moving this item by 5 every frame, but to be honest it could be anything! It could move by 1 every frame, it could move by -5 every frame (so backwards), it could move by 2.5 every frame (so 5 in two frames). This value, this amount moved per frame, can be called the velocity. The higher the velocity (so the more distance moved every frame), the faster the item appears to be moving, since its covered more distance in the same amount of time.

Now we've only thought about moving the item right (and technically left too by moving right a negative amount), therefore we can call this amount moved horizontally per frame the x velocity. The same also applies to moving the item up and down, so the amount moved vertically per frame being the y velocity.

x = 200
y = 100

x_velocity = 5
y_velocity = 0

while True:
# each loop can be thought of as one frame
x += x_velocity
y += y_velocity

# ... other pygame code stuff ...

In the above code, each frame the item's x value increases by 5, so we can say that the x velocity is 5. As the y value does not change, the y velocity is 0. The x and y velocities are in essence the horizontal and vertical speed of the item. The greater the value, the faster it moves in that axis, and if its negative then it moves in the opposite direction.

This is the second important concept to understand: the idea of velocity. The ability to move an item's position by changing its coordinates every frame, and the amount it moves (or "changes by") is known as the velocity.

Concept three: bouncing

At the moment, so far we can move an item gradually across the screen, by setting x and y velocities and changing the x and y values by these velocities each frame. So what happens when we hit a wall? Well the item should bounce right? In the current state the item would simply continue off the edge window.

Well lets take a specific example shall we? If the ball has an x velocity of -5, and y velocity of 0 (so its moving left 5 per frame), and it hits the left wall, what should happen? Well since we know we need to make the ball move backwards, we change its direction by negating the velocity (turning it from -5 to 5). The ball hit the left wall, so it should bounce horizontally, so the x velocity should be negated (so its now moving right).

Okay, so we know how to make it bounce horizontally, but how do we know when it needs to bounce?

Empty graph

As soon as the item moves left of the left wall (so we know it needs to bounce), we can see that its x value is negative (aka <0). So, I think it would be pretty safe to deduce the following rule:

if x < 0:
x_velocity = abs(x_velocity)

The abs() function makes the number you give it positive. So abs(-5) -> 5, but also abs(5) -> 5. So what the rule above actually means is: "when the item's x value is less than 0 (so its passed the left wall), make its x velocity positive (make it move right, causing it to 'bounce' )".

Following the same logic, we can apply the same to the y value:

if y < 0:
y_velocity = abs(y_velocity)

The next question to naturally arise is about the bottom and right walls. Since how can we know when the item has passed those? Looking at the picture below might give us some insight:

Ball passed right wall

We can visually see that the right wall is at x 500, and the bottom at y 400. In this case the window size is 500x400. Since the left wall is at x value 0, we know that the right wall must be at the x value of the window's width. And the same for the bottom wall too, since the top wall is at y value 0, the bottom wall must be at the y value of the window's height. Therefore we can complete the bouncing code for all 4 walls to make this:

# left wall
if x < 0:
x_velocity = abs(x_velocity)

# top wall
if y < 0:
y_velocity = abs(y_velocity)

# right wall
if x > width:
x_velocity = -abs(x_velocity)

# bottom wall
if y > height:
y_velocity = -abs(y_velocity)

The reason for -abs() is because at the right and bottom walls, we need to make the ball bounce left/up respectively, and in each of these cases the corresponding x or y velocities are both always negative. We first make them always positive with abs(), and then negate that with - to make -abs() be always negative.

We can combine this with our earlier velocity code to make the pseudo-python-code for a ball bouncing inside 4 walls:

x = 200
y = 100

x_velocity = 5
y_velocity = 5

while True:
x += x_velocity
y += y_velocity

# left wall
if x < 0:
x_velocity = abs(x_velocity)

# top wall
if y < 0:
y_velocity = abs(y_velocity)

# right wall
if x > width:
x_velocity = -abs(x_velocity)

# bottom wall
if y > height:
y_velocity = -abs(y_velocity)

Being able to understand how to manipulate an item's velocity to simulate bouncing is the trickiest part so far, but just as important for the final product.

Concept four: paddles

So far we've just implemented some generic ball bouncing rules, so how about we start on something specifically pong-like? The next thing missing is the paddles, so lets think about how we can make those work.

Below I've pictured the Pygame window, and with the red lines being where the paddles could potentially be.

Window with paddles

First off, lets use our new knowledge to figure a few things out about these paddles. Say, for example how we're going to be keeping track of where they are! We know that we can store an item's position using two values, one of which we'll be using here. The y value tells us the vertical position (or "distance from the top") for an item.

So, looking at the picture above, we can see that the left paddle is right at the top. Its "distance from the top" is zero!, sooo that means that we could say that the left paddle has a y value of 0. And the right paddle, its 100 away from the top, so it has a y value of 100. Simple stuff!

But, thats not all. Before when we were talking about a single point, it didn't have any height like the paddles do! And in this case the paddles look to be 200 tall. But here's a question: what's the maximum y value these paddles can have? So, if the paddles are 200 tall, and the height of the window is 400, then 200 above 400 means 400 - 200 which means the max is 200!

Okay, so we've figured out how to store the paddles vertical position (the y value), and the maximum y value it can have! (If there was no maximum, then it would disappear off the bottom!) The next thing to do is how to move it. Lets say that as long as we hold the W key, the left paddle should move up by 5 (so -5 from y), and holding the S key should move the paddle down by 5 (so +5 to y).

We're going to want to check which keys are pressed every frame, and then move the paddle by 5 in the direction. Remember from before how the amount moved every frame is known as the velocity? Well the same applies here! The only difference is that the paddle only moves when the key is pressed, but it still applies here: since the paddle moves by 5 each frame (when a key is pressed), the paddle's velocity will be 5! Obviously this can be anything, but 5 sounds good to me. Lets take a look at some pseudo-python-code:

# start the paddle at the top
paddle_y = 0
paddle_velocity = 5

# each loop is one frame
while True:
if key_pressed('w'):
# we are subtracting so it moves upwards
paddle_y -= paddle_velocity
if key_pressed('S'):
paddle_y += paddle_velocity

Oh wait!! I totally forgot about the limits! Right now with this code the paddles can move off the screen.. whiiich sucks. Okay, well we know that the distance from the top can't be less than zero, so the range is 0 to window_height - paddle_height. We just have to make sure it doesn't reach outside those values! If the y value ever does go beyond, then we'll just move it back to the limit. Take a look at the updated code which handles this:

# start the paddle at the top
paddle_y = 0
paddle_velocity = 5
paddle_height = 200

# each loop is one frame
while True:
if key_pressed('w'):
# we are subtracting so it moves upwards
paddle_y -= paddle_velocity
if key_pressed('S'):
paddle_y += paddle_velocity

# limits the minimum y to 0
if paddle_y < 0:
paddle_y = 0
# limit the maximum y to keep it on the screen
if paddle_y > (height - paddle_height):
paddle_y = (height - paddle_height)

The function key_pressed() is just one I made up for demonstration purposes, but there should be something similar in Pygame.

Cool beans, we've got it moving! The next part of paddles is making the ball bounce off them. Looking at the picture, we can see that the paddles don't really have a width.. if anything they're sort of part of the wall in way. In normal pong the side walls are out-of-bounds, so the paddles could be seen as the part of the wall which aren't out of bounds. A small, moving portion of the wall which is bounces the ball and is not out-of-bounds.

Lets do a few things to the code we've got so far. First off lets combine the ball and paddle code, we'll change the left and right walls to be out-of-bounds, and we'll add controls for a second, right paddle with O and L for controls:

ball_x = 200
ball_y = 100
ball_x_velocity = 5
ball_y_velocity = 5

left_paddle_y = 0
right_paddle_y = 0
paddle_velocity = 5
paddle_height = 200

# each loop is one frame
while True:

# BALL
ball_x += ball_x_velocity
ball_y += ball_y_velocity

# left wall
if x < 0:
out_of_bounds()

# top wall
if y < 0:
ball_y_velocity = abs(ball_y_velocity)

# right wall
if x > width:
out_of_bounds()

# bottom wall
if y > height:
ball_y_velocity = -abs(ball_y_velocity)

# PADDLES
if key_pressed('w'):
left_paddle_y -= paddle_velocity
if key_pressed('S'):
left_paddle_y += paddle_velocity
if key_pressed('O'):
right_paddle_y -= paddle_velocity
if key_pressed('L'):
right_paddle_y += paddle_velocity

if left_paddle_y < 0:
left_paddle_y = 0
if left_paddle_y > (height - paddle_height):
left_paddle_y = (height - paddle_height)
if right_paddle_y < 0:
right_paddle_y = 0
if right_paddle_y > (height - paddle_height):
right_paddle_y = (height - paddle_height)

Nice! The rundown of of the next step is: if the ball hits either left or right wall, then only bounce the ball if its inside the paddle. The next natural question is "how do we know if the ball is inside the paddle?".

Window with paddles

Looking at this picture again, specifically at the right paddle, for the ball to bounce off of the right paddle it needs to be between its top and bottom point. So we need to compare the y values for the ball, and the paddle's ends. The paddle's top is just its y value, and the paddle's bottom is its y value + its height. So for the ball to be inside the right paddle, the following must be true:



Related Topics



Leave a reply



Submit