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 <= 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.
Top comments (0)