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: repl.it/@Rabbid76/PyGame-CirclesBounceOff
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: repl.it/@Rabbid76/PyGame-BallsBounceOff
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 rightPaddle
are 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:
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
Python Requests.Get() Returns Improperly Decoded Text Instead of Utf-8
Python - Activate Conda Env Through Shell Script
How to Show Explosion Image When Collision Happens
Is the Shortcircuit Behaviour of Python's Any/All Explicit
Working with Tiffs (Import, Export) in Python Using Numpy
Is Close() Necessary When Using Iterator on a Python File Object
Link Several Popen Commands with Pipes
Is It Pythonic to Import Inside Functions
Modular Multiplicative Inverse Function in Python
Manifest.In Ignored on "Python Setup.Py Install" - No Data Files Installed
Getting Values from Object Oriented Tkinter
How to Concatenate Two Layers in Keras