I'm attempting to model the bouncing of balls in a pattern in python with pygame. Something is causing the physics to be incorrect - the balls GAIN energy, i.e. they bounce fractionally higher over time. (I have included a 'speed factor' which can be increase to see the effect I describe.)
Here is my code:
import pygame
import math
# Window dimensions
WIDTH = 800
HEIGHT = 600
# Circle properties
RADIUS = 5
NUM_BALLS = 20
GRAVITY = 9.81 # Gravitational acceleration in m/s²
SPEED_FACTOR = 10 # Speed multiplier for animation
# Circle class
class Circle:
def __init__(self, x, y, vx, vy, color):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.color = color
def update(self, dt):
# Update positions
self.x += self.vx * dt
self.y += self.vy * dt
# Apply gravity
self.vy += GRAVITY * dt
# Bounce off walls
if self.x - RADIUS < 0 or self.x + RADIUS > WIDTH:
self.vx *= -1
self.x = max(RADIUS, min(WIDTH - RADIUS, self.x)) # Clamp x within bounds
if self.y - RADIUS < 0 or self.y + RADIUS > HEIGHT:
self.vy *= -1
self.y = max(RADIUS, min(HEIGHT - RADIUS, self.y)) # Clamp y within bounds
def draw(self, screen):
pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), RADIUS)
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
circles = []
# Calculate circle arrangement
circle_radius = RADIUS * 2 # Diameter of an individual ball
circle_diameter = NUM_BALLS * circle_radius # Diameter of the circle arrangement
circle_center_x = WIDTH // 2
circle_center_y = HEIGHT // 2
angle_increment = 2 * math.pi / NUM_BALLS
for i in range(NUM_BALLS):
angle = i * angle_increment
x = circle_center_x + math.cos(angle) * circle_diameter / 2
y = circle_center_y + math.sin(angle) * circle_diameter / 2
vx = 0
vy = 0
hue = i * (360 // NUM_BALLS) # Calculate hue value based on the number of balls
color = pygame.Color(0)
color.hsla = (hue, 100, 50, 100) # Set color using HSL color space
circles.append(Circle(x, y, vx, vy, color))
# Game loop
running = True
clock = pygame.time.Clock()
prev_time = pygame.time.get_ticks() # Previous frame time
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
current_time = pygame.time.get_ticks()
dt = (current_time - prev_time) / 1000.0 # Time elapsed in seconds since the last frame
dt *= SPEED_FACTOR # Multiply dt by speed factor
prev_time = current_time # Update previous frame time
screen.fill((0, 0, 0)) # Clear the screen
for circle in circles:
circle.update(dt)
circle.draw(screen)
pygame.display.flip() # Update the screen
pygame.quit()
I don't fully understand how the gravity factor works, but assume it's just an acceleration. Where is the extra energy coming in to the system?
When you 'clamp' the ball to the position on the edge, you're moving it up slightly. Theoretically, a perfect ball bouncing like this would have the same magnitude of velocity going up and down for the same y-coordinate, but you change the position for the same velocity. On the way back up, the ball gets a head-start, and can go ever so slightly higher.
To solve this, you could do some sort of calculation to determine how much velocity would be lost to gravity in that small distance and diminish your
vy
accordingly, but there's probably a better way to do itEDIT
another solution would be to leave the y coordinate as-is and simply change the shape of the ball into an appropriately sized ellipse so that it falls within the boundary. Then change it back into a circle when it no longer intersects a boundary. you would need to have sufficiently short time steps, and a limit on velocity or else your ellipses might become very weird.