pythonpygamecollision-detectioncollision

How to detect the collision of circles or balls in Pygame?


I want to make a script in pygame where two balls fly towards each other and when they collide they should bounce off from each other but I don't know how to do this so can you help me?


Solution

  • 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 have to test many collisions between circles you should compare the square of the distances to avoid the time consuming square root calculation necessary to calculate Euclidean distance. With distance_squared_to() there is also a function for this:

    v1 = pygame.math.Vector2(x1, y1)
    v2 = pygame.math.Vector2(x2, y2)
    if v1.distance_squared_to(v2) < (r1 + r2)**2:
        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()