pygame Get the balls to bounce off each other
I'm trying to get the balls to bounce off each other. I tried to do it using the reflect()
method, but it doesn't work for some reason.
To detect the balls, I used groupcollide
, since I couldn't think of a better way, maybe this is wrong?
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))
if self.rect.right >= 700:
self.reflect((-1, 0))
if self.rect.top <= 0:
self.reflect((0, 1))
if self.rect.bottom >= 700:
self.reflect((0, -1))
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 = (200, 200), 10, (random.random(), random.random())
ball_2 = Ball(start, velocity, direction)
all_balls.add(ball_1, ball_2)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
all_balls.update()
hits = pygame.sprite.groupcollide(all_balls, all_balls, False, False)
for _, hit_sprites in hits.items():
if len(hit_sprites) > 1:
sprite_1 = hit_sprites[0]
sprite_2 = hit_sprites[1]
sprite_1.reflect((0, 1)) # not working
print("hit!")
window.fill(0)
pygame.draw.rect(window, (255, 0, 0), (0, 0, 700, 700), 1)
all_balls.draw(window)
pygame.display.flip()
Solution 1:
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()