Simulating Bouncing Balls with Collisions in Python using Pygame

In this article, we will walk through a Python program that simulates bouncing balls within a window, using the Pygame library. The code also includes functionality for detecting and resolving collisions between the balls. This example provides a good introduction to both basic physics in programming and how to use Pygame to create dynamic simulations. What You'll Learn Setting up Pygame for creating a simulation. Implementing basic motion of balls with random velocity. Detecting collisions between balls and resolving them according to physical principles. Using object-oriented programming (OOP) in Python to structure the simulation. Setting Up the Environment Before diving into the code, we need to ensure that Pygame is installed. If you haven’t installed Pygame yet, you can do so using pip: pip install pygame Understanding the Code Here’s an overview of how the program works: 1. Pygame Initialization and Display Setup pygame.init() # Set up the display width = 800 height = 600 ball_radius = 20 screen = pygame.display.set_mode((width, height)) clock = pygame.time.Clock() The code begins by initializing Pygame and setting up the display. The window dimensions are 800x600 pixels, and each ball in the simulation has a radius of 20 pixels. 2. Defining Colors white = (255, 255, 255) black = (0, 0, 0) red = (255, 0, 0) green = (0, 255, 0) blue = (0, 0, 255) These are RGB color definitions used to render balls and the background. In this case, we have three colors for the balls (red, green, blue) and two colors for the background (white and black). 3. Ball Class: Managing Individual Balls class Ball: def __init__(self, color): self.x = random.randint(ball_radius, width - ball_radius) self.y = random.randint(ball_radius, height - ball_radius) self.velocity_x = random.randint(1, 3) * 2 self.velocity_y = random.randint(1, 3) * -2 self.color = color The Ball class is central to this program. Each ball object has properties like position (x, y), velocity (velocity_x, velocity_y), and color. The ball's initial position and velocity are set randomly, so each ball starts in a different location and moves at a unique speed. 4. Moving the Balls and Handling Boundary Bounces def move(self): self.x += self.velocity_x self.y += self.velocity_y # Bounce off walls, considering the radius of the ball if self.x = width - ball_radius: self.velocity_x *= -1 if self.x = width - ball_radius: self.x = width - ball_radius if self.y = height - ball_radius: self.velocity_y *= -1 self.y = height - ball_radius The move method updates the ball's position based on its velocity. The program handles wall collisions by reversing the velocity along the respective axis when a wall is hit, making the ball bounce off the walls. 5. Drawing the Balls on the Screen def draw(self): pygame.draw.circle(screen, self.color, (self.x, self.y), ball_radius) The draw method uses Pygame’s pygame.draw.circle() function to render each ball on the screen with its corresponding color and position. 6. Handling Ball-to-Ball Collisions def check_collision(self, other_ball): dx = self.x - other_ball.x dy = self.y - other_ball.y distance = math.sqrt(dx**2 + dy**2) # If the distance between centers is less than twice the radius, a collision occurred if distance 0: return # Calculate the impulse scalar impulse = 2 * velocity_along_normal / (2 * ball_radius) # Apply the impulse to the velocities of both balls self.velocity_x -= impulse * normal_vector.x self.velocity_y -= impulse * normal_vector.y other_ball.velocity_x += impulse * normal_vector.x other_ball.velocity_y += impulse * normal_vector.y # Reposition the balls to avoid overlap overlap = 2 * ball_radius - distance self.x += normal_vector.x * overlap / 2 self.y += normal_vector.y * overlap / 2 other_ball.x -= normal_vector.x * overlap / 2 other_ball.y -= normal_vector.y * overlap / 2 The check_collision method handles the detection of collisions between two balls. It calculates the distance between the centers of two balls. If the distance is less than the sum of their radii (i.e., they overlap), the program applies basic physics principles to resolve the collision by adjusting the balls' velocities and positions. This ensures that the balls bounce off each other in a realistic manner. 7. The Main Game Loop balls = [Ball(red), Ball(green), Ball(blue)] running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Move and check collisions for each ball for ball in balls: ball.move() # C

Mar 21, 2025 - 20:41
 0
Simulating Bouncing Balls with Collisions in Python using Pygame

In this article, we will walk through a Python program that simulates bouncing balls within a window, using the Pygame library. The code also includes functionality for detecting and resolving collisions between the balls. This example provides a good introduction to both basic physics in programming and how to use Pygame to create dynamic simulations.

Simulating Bouncing Balls with Collisions in Python using Pygame

What You'll Learn

  • Setting up Pygame for creating a simulation.
  • Implementing basic motion of balls with random velocity.
  • Detecting collisions between balls and resolving them according to physical principles.
  • Using object-oriented programming (OOP) in Python to structure the simulation.

Setting Up the Environment

Before diving into the code, we need to ensure that Pygame is installed. If you haven’t installed Pygame yet, you can do so using pip:

pip install pygame

Understanding the Code

Here’s an overview of how the program works:

1. Pygame Initialization and Display Setup

pygame.init()

# Set up the display
width = 800
height = 600
ball_radius = 20
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

The code begins by initializing Pygame and setting up the display. The window dimensions are 800x600 pixels, and each ball in the simulation has a radius of 20 pixels.

2. Defining Colors

white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)

These are RGB color definitions used to render balls and the background. In this case, we have three colors for the balls (red, green, blue) and two colors for the background (white and black).

3. Ball Class: Managing Individual Balls

class Ball:
    def __init__(self, color):
        self.x = random.randint(ball_radius, width - ball_radius)
        self.y = random.randint(ball_radius, height - ball_radius)
        self.velocity_x = random.randint(1, 3) * 2
        self.velocity_y = random.randint(1, 3) * -2
        self.color = color

The Ball class is central to this program. Each ball object has properties like position (x, y), velocity (velocity_x, velocity_y), and color. The ball's initial position and velocity are set randomly, so each ball starts in a different location and moves at a unique speed.

4. Moving the Balls and Handling Boundary Bounces

def move(self):
    self.x += self.velocity_x
    self.y += self.velocity_y

    # Bounce off walls, considering the radius of the ball
    if self.x <= ball_radius or self.x >= width - ball_radius:
        self.velocity_x *= -1
        if self.x <= ball_radius:
            self.x = ball_radius
        elif self.x >= width - ball_radius:
            self.x = width - ball_radius

    if self.y <= ball_radius:
        self.velocity_y *= -1
        self.y = ball_radius
    elif self.y >= height - ball_radius:
        self.velocity_y *= -1
        self.y = height - ball_radius

The move method updates the ball's position based on its velocity. The program handles wall collisions by reversing the velocity along the respective axis when a wall is hit, making the ball bounce off the walls.

5. Drawing the Balls on the Screen

def draw(self):
    pygame.draw.circle(screen, self.color, (self.x, self.y), ball_radius)

The draw method uses Pygame’s pygame.draw.circle() function to render each ball on the screen with its corresponding color and position.

6. Handling Ball-to-Ball Collisions

def check_collision(self, other_ball):
    dx = self.x - other_ball.x
    dy = self.y - other_ball.y
    distance = math.sqrt(dx**2 + dy**2)

    # If the distance between centers is less than twice the radius, a collision occurred
    if distance < 2 * ball_radius:
        # Normalize the vector between the two balls
        normal_vector = pygame.math.Vector2(dx, dy).normalize()

        # Calculate relative velocity
        relative_velocity = pygame.math.Vector2(self.velocity_x, self.velocity_y) - pygame.math.Vector2(other_ball.velocity_x, other_ball.velocity_y)

        # Calculate the velocity along the normal vector (dot product)
        velocity_along_normal = relative_velocity.dot(normal_vector)

        if velocity_along_normal > 0:
            return

        # Calculate the impulse scalar
        impulse = 2 * velocity_along_normal / (2 * ball_radius)

        # Apply the impulse to the velocities of both balls
        self.velocity_x -= impulse * normal_vector.x
        self.velocity_y -= impulse * normal_vector.y
        other_ball.velocity_x += impulse * normal_vector.x
        other_ball.velocity_y += impulse * normal_vector.y

        # Reposition the balls to avoid overlap
        overlap = 2 * ball_radius - distance
        self.x += normal_vector.x * overlap / 2
        self.y += normal_vector.y * overlap / 2
        other_ball.x -= normal_vector.x * overlap / 2
        other_ball.y -= normal_vector.y * overlap / 2

The check_collision method handles the detection of collisions between two balls. It calculates the distance between the centers of two balls. If the distance is less than the sum of their radii (i.e., they overlap), the program applies basic physics principles to resolve the collision by adjusting the balls' velocities and positions. This ensures that the balls bounce off each other in a realistic manner.

7. The Main Game Loop

balls = [Ball(red), Ball(green), Ball(blue)]
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Move and check collisions for each ball
    for ball in balls:
        ball.move()

    # Check for collisions between the balls
    for i in range(len(balls)):
        for j in range(i + 1, len(balls)):
            balls[i].check_collision(balls[j])

    # Draw the window
    screen.fill(white)

    # Draw all balls
    for ball in balls:
        ball.draw()

    # Update and render
    pygame.display.flip()
    clock.tick(60)

The main game loop continuously updates the positions of the balls, checks for collisions, and renders the screen. The window is filled with a white background at the start of each loop iteration, and then each ball is drawn on top. The loop runs at 60 frames per second (clock.tick(60)).

8. Exiting the Program

pygame.quit()

When the user closes the window, Pygame shuts down and the program terminates.

The Full Code

Here is the full code for the simulation:

import pygame
import random
import math

# Initialize Pygame
pygame.init()

# Set up the display
width = 800
height = 600
ball_radius = 20

# Define colors
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)

# Create a Pygame display surface
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

# Ball class to manage multiple balls
class Ball:
    def __init__(self, color):
        self.x = random.randint(ball_radius, width - ball_radius)
        self.y = random.randint(ball_radius, height - ball_radius)
        self.velocity_x = random.randint(1, 3) * 2
        self.velocity_y = random.randint(1, 3) * -2
        self.color = color

    def move(self):
        self.x += self.velocity_x
        self.y += self.velocity_y

        # Bounce off walls, considering the radius of the ball
        if self.x <= ball_radius or self.x >= width - ball_radius:
            self.velocity_x *= -1
            if self.x <= ball_radius:
                self.x = ball_radius
            elif self.x >= width - ball_radius:
                self.x = width - ball_radius

        if self.y <= ball_radius:
            self.velocity_y *= -1
            self.y = ball_radius
        elif self.y >= height - ball_radius:
            self.velocity_y *= -1
            self.y = height - ball_radius

    def draw(self):
        pygame.draw.circle(screen, self.color, (self.x, self.y), ball_radius)

    def check_collision(self, other_ball):
        dx = self.x - other_ball.x
        dy = self.y - other_ball.y
        distance = math.sqrt(dx**2 + dy**2)

        # If the distance between centers is less than twice the radius, a collision occurred
        if distance < 2 * ball_radius:
            # Normalize the vector between the two balls
            normal_vector = pygame.math.Vector2(dx, dy).normalize()

            # Calculate relative velocity
            relative_velocity = pygame.math.Vector2(self.velocity_x, self.velocity_y) - pygame.math.Vector2(other_ball.velocity_x, other_ball.velocity_y)

            # Calculate the velocity along the normal vector (dot product)
            velocity_along_normal = relative_velocity.dot(normal_vector)

            if velocity_along_normal > 0:
                return

            # Calculate the impulse scalar
            impulse = 2 * velocity_along_normal / (2 * ball_radius)

            # Apply the impulse to the velocities of both balls
            self.velocity_x -= impulse * normal_vector.x
            self.velocity_y -= impulse * normal_vector.y
            other_ball.velocity_x += impulse * normal_vector.x
            other_ball.velocity_y += impulse * normal_vector.y

            # Reposition the balls to avoid overlap
            overlap = 2 * ball_radius - distance
            self.x += normal_vector.x * overlap / 2
            self.y += normal_vector.y * overlap / 2
            other_ball.x -= normal_vector.x * overlap / 2
            other_ball.y -= normal_vector.y * overlap / 2

# Create 3 balls of different colors
balls = [Ball(red), Ball(green), Ball(blue)]

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

    # Move and check collisions for each ball
    for ball in balls:
        ball.move()

    # Check for collisions between the balls
    for i in range(len(balls)):
        for j in range(i + 1, len(balls)):
            balls[i].check_collision(balls[j])

    # Draw the window
    screen.fill(white)

    # Draw all balls
    for ball in balls:
        ball.draw()

    # Update and render
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Conclusion

This simple simulation demonstrates the basics of object-oriented programming in Python, along with an introduction to collision detection and physics simulations. With Pygame, you can create dynamic and interactive visualizations, making it an excellent choice for creating games and simulations.