Pygame How to Let Balls Collide

Pygame how to let balls collide

To detect if 2 circles (respectively balls) are colliding, you've to test, if the Euclidean distance between the circles center points is less than the sum of the radii. I recommend to use pygame.math.Vector2 / distance_to() for the computation.

In the following the 1 circle is defined by the center point (x1, y1) and the radius r1. The 2nd circle is defined by (x2, y2) and r2:

v1 = pygame.math.Vector2(x1, y1)
v2 = pygame.math.Vector2(x2, y2)
if v1.distance_to(v2) < r1 + r2:
print("hit")

If you want to make the circles bounce, you have to reflect the motion vector of the circle at the normal vector of the intersection like a billiard ball. Use pygame.math.Vector2 / reflect_ip() or reflect() to compute the new direction of the circle.

The movements of the circles are given by (mx1, my1) and (mx2, my2):

nv = v2 - v1
m1 = pygame.math.Vector2(mx1, my1).reflect(nv)
m2 = pygame.math.Vector2(mx2, my2).reflect(nv)
mx1, my1 = m1.x, m1.y
mx2, my2 = m2.x, m2.y

Minimal example: Sample Image repl.it/@Rabbid76/PyGame-CirclesBounceOff

Sample Image

import pygame

pygame.init()

width, height = 400, 400
window = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

x1, y1, r1, mx1, my1 = 200, 200, 50, 2, 0.5
x2, y2, r2, mx2, my2 = 300, 200, 50, -1, -1.5

def move(c, v, r, m):
c += v
if c < r: c, v = r, -v
if c > m-r: c, v = m-r, -v
return c, v

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

x1, mx1 = move(x1, mx1, r1, width)
y1, my1 = move(y1, my1, r1, height)
x2, mx2 = move(x2, mx2, r2, width)
y2, my2 = move(y2, my2, r2, height)

v1 = pygame.math.Vector2(x1, y1)
v2 = pygame.math.Vector2(x2, y2)
if v1.distance_to(v2) < r1 + r2 - 2:
hit_count += 1
print("hit:", hit_count)

nv = v2 - v1
m1 = pygame.math.Vector2(mx1, my1).reflect(nv)
m2 = pygame.math.Vector2(mx2, my2).reflect(nv)
mx1, my1 = m1.x, m1.y
mx2, my2 = m2.x, m2.y

window.fill((127, 127, 127))
pygame.draw.circle(window, (255, 0, 0), (round(x1), round(y1)), r1, 4)
pygame.draw.circle(window, (0, 0, 255), (round(x2), round(y2)), r2, 4)
pygame.display.flip()

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

How to fix collision of a ball with a rect object in pygame

You have to ensure that the pygame.Rect objects leftPaddle and rightPaddleare located at the actual position of the paddles. The easiest way is to use leftPaddle and rightPaddle to draw and move the paddles:

while run:
# [...]

pygame.draw.rect(screen,(225,225,255), leftPaddle)
pygame.draw.rect(screen,(225,225,255), rightPaddle)
pygame.draw.circle(screen,(255,255,255),(round(cir_x), round(cir_y)),radius)

keys=pygame.key.get_pressed()
if keys[K_UP] and rightPaddle.top > vel:
rightPaddle.y -= vel
if keys[K_DOWN] and rightPaddle.bottom < height - vel:
rightPaddle.y += vel
if keys[K_w] and leftPaddle.top > vel:
leftPaddle.y -= vel
if keys[K_s] and leftPaddle.bottom < height - vel:
leftPaddle.y += vel
cir_x+=xvel
cir_y+=yvel

Use pygame.Rect.colliderect to evaluate if a paddel collides with the rectangle that surrounds the ball. Since the velocity of the ball is not 1, the ball can penetrate into the paddle. Restrict the position of the ball to leftPaddle respectively rightPaddle:

while run:
# [...]

ball_rect = pygame.Rect(cir_x-radius, cir_y-radius, 2*radius, 2*radius)

if leftPaddle.colliderect(ball_rect):
cir_x = leftPaddle.right + radius
xvel*=-1

if rightPaddle.colliderect(ball_rect):
cir_x = rightPaddle.left - radius
xvel*=-1

See the complete example:

Sample Image

import pygame
import math
from pygame.locals import *

pygame.init()

width,height=680,450
screen=pygame.display.set_mode((width,height))
pygame.display.set_caption("PONG GAME")
xoffset,yoffset=30,50

vel=7
radius=12

cir_x,cir_y=width/2,height/2
xvel=yvel=5

w=20
p1_x,p1_y=xoffset,height//2-yoffset
p2_x,p2_y=width-w-xoffset,height//2-yoffset
h=2*(yoffset)

leftPaddle=pygame.Rect(p1_x,p1_y,w,h)
rightPaddle=pygame.Rect(p2_x,p2_y,w,h)
clock=pygame.time.Clock()

count=0
run=True
while run:
for i in pygame.event.get():
if i.type==pygame.QUIT:
run=False
screen.fill(1)

pygame.draw.rect(screen,(225,225,255), leftPaddle)
pygame.draw.rect(screen,(225,225,255), rightPaddle)

pygame.draw.circle(screen,(255,255,255),(round(cir_x), round(cir_y)),radius)

keys=pygame.key.get_pressed()
if keys[K_UP] and rightPaddle.top > vel:
rightPaddle.y -= vel
if keys[K_DOWN] and rightPaddle.bottom < height - vel:
rightPaddle.y += vel
if keys[K_w] and leftPaddle.top > vel:
leftPaddle.y -= vel
if keys[K_s] and leftPaddle.bottom < height - vel:
leftPaddle.y += vel
cir_x+=xvel
cir_y+=yvel

if (cir_x+radius>width or cir_x<radius):
xvel*=-1
if (cir_y+radius>height or cir_y<radius):
yvel*=-1

ball_rect = pygame.Rect(cir_x-radius, cir_y-radius, 2*radius, 2*radius)

if leftPaddle.colliderect(ball_rect):
cir_x = leftPaddle.right + radius
xvel*=-1

if rightPaddle.colliderect(ball_rect):
cir_x = rightPaddle.left - radius
xvel*=-1

clock.tick(50)
pygame.display.update()

pygame.quit()

Python & Pygame: Ball collision with interior of circle

I'm glad you liked my tutorial. I like your variation, it should actually be simpler.

First, I think you need change the test for collision to:

if distance >= circle.size - ball.size:

Because the larger the ball size, the smaller the distance between its centre and the centre of the circle can be. This should make the balls bounce at the right place (inside the circle).

Then I think you just need to swap the signs for the x and y and everything should work.

ball.x += math.sin(angle)
ball.y -= math.cos(angle)

To move the ball by the correct distance you can calculate the overlap:

overlap = math.hypot(dx, dy) - (circle.size - ball.size)

if overlap >= 0:
tangent = math.atan2(dy, dx)
ball.angle = 2 * tangent - ball.angle
ball.speed *= elasticity

angle = 0.5 * math.pi + tangent
ball.x += math.sin(angle)*overlap
ball.y -= math.cos(angle)*overlap

Good luck

Pygame Paddle Collision with Classes

When the Ball hits the Paddle, the direction of the Ball changes. Add a method paddle_left_collision to the Ball class:

class Ball():
# [...]

def paddle_left_collision(self, paddle_rect):
if self.ball_draw.colliderect(paddle_rect):
self.ball_speedx = abs(self.ball_speedx)

Call it in the applicaiton loop

while True:

# handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()

# move objects and detect collisisons
ball.move()
paddle.move()
ball.wall_collision()
ball.paddle_left_collision(paddle.paddle_left)

# draw scene
screen.fill(black)
ball.draw()
paddle.draw()
pygame.display.flip()

clock.tick(FPS)

Note that if you have two paddles, change the method as follows:

def paddles_collision(self, paddle_left_rect, paddle_right_rect):
if self.ball_draw.colliderect(paddle_left_rect):
self.ball_speedx = abs(self.ball_speedx)
if self.ball_draw.colliderect(paddle_right_rect):
self.ball_speedx = -abs(self.ball_speedx)

See also How do I detect collision in pygame? and Sometimes the ball doesn't bounce off the paddle in pong game

I am trying to make a bouncy ball collision effect in python pygame, but my ball keeps going under the line, or limit that I gave

The problem with bouncing balls (and every non-linear physics simulation where you have very drastic changes) is that your simulation might put you below the limit in a single time step, so you have to make sure that you enforce the limit. A simple way to fix that for a game is to force the y position when you hit the floor. Change your move method to this and it works (note that the if self.y <= 400: branch didn't do anything, so I removed it).

    def move(self):
self.velocity += Acs
self.y += self.velocity
if self.y > 400:
self.velocity = -self.velocity + self.bouncy
self.y = 400 # Ensure the limits

For a slightly more realistic bouncing effect, you can use your self.bouncy attribute as an actual coefficient of restitution and change slightly the formula for the bounce. 5% of loss every bounce gave a reasonably good result.

class Ball():
def __init__(self):
self.y = 200
self.velocity = 10
self.bouncy = 0.05 # Now a coefficient of restitution

def draw(self):
pygame.draw.circle(Win, (255, 0, 0), (500, self.y), 15)

def move(self):
self.velocity += Acs
self.y += self.velocity
if self.y > 400: # Note that the loss of velocity is a function of the previous velocity
self.velocity = -self.velocity + self.bouncy*self.velocity
self.y = 400 # Ensure the limits

How to fix these circles' collision in pygame?

What you actually do is to draw the circles, then update there position and finally update the display.

Change the order. Update the position of the circles, then draw them at there current position and finally update the display. So the circles are shown at there actual position.

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

pygame.time.delay(t)

if r:
x1 += v
x2 -= v
v += 1

if x2-x1 <= 50:
r=False

print(x2,x1,(x2-x1),v,t)

dis.fill((255,255,255))
circles()
pygame.display.update()

Furthermore, 1 loop is absolutely sufficient. Just verify if r: in the main application loop.

If you don't want that the circles are intersecting at there final position, then you've to correct the positions:

if x2-x1 <= 50: 
r = False
d = x2-x1
x1 -= (50-d) // 2
x2 += (50-d) // 2


Related Topics



Leave a reply



Submit