我正在尝试使用 pygame 在 python 中以某种模式对球的弹跳进行建模。某些因素导致物理学不正确——球获得能量,即随着时间的推移,它们的弹跳会稍微更高。 (我添加了一个“速度系数”,可以增加该系数来查看我描述的效果。)
这是我的代码:
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()
我不完全理解重力因子是如何工作的,但假设它只是一个加速度。额外的能量从哪里进入系统?
当您将球“夹”到边缘位置时,您会稍微向上移动它。理论上,像这样弹跳的完美球对于相同的 y 坐标将具有相同大小的上下速度,但您可以更改相同速度的位置。在返回的过程中,球取得了领先,并且可以稍微更高。
为了解决这个问题,您可以进行某种计算来确定在这么短的距离内重力会损失多少速度,并相应地减少您的
vy
,但可能有更好的方法来做到这一点
编辑
另一种解决方案是保持 y 坐标不变,只需将球的形状更改为适当大小的椭圆形,使其落在边界内。当它不再与边界相交时,将其变回圆形。你需要有足够短的时间步长,并且限制速度,否则你的椭圆可能会变得非常奇怪。
alex_danielssen 的答案涉及模拟获得能量的原因
当您将球“夹”到边缘位置时,您会稍微向上移动它。
考虑在地板上触发反弹时会发生什么(这适用于任何墙壁,但现在我们只考虑地板):
假设当因为
self.y - RADIUS = -1
(即< 0
)触发反弹时,我们有self.vy = -10
。此时,self.y
是RADIUS - 1
。处理反弹后,您设置 self.vy = 10
和 self.y = RADIUS
。球被提升到高于其先前位置 1
单位,因此它具有更多的势能,但它仍然具有与较低时相同的动能。这是你神奇的能量增益。
要解决这个问题,您只需根据能量守恒正确计算其速度即可。
if self.y - RADIUS <= 0 or self.y + RADIUS >= HEIGHT:
# Energy = potential. + kinetic ( per unit mass )
energy = GRAVITY * self.y + 0.5 * self.vy**2
# Update location
self.y = max(RADIUS, min(HEIGHT - RADIUS, self.y)) # Clamp y within bounds
# Direction of vy (1 = up, -1 = down)
vy_direction = self.vy / abs(self.vy)
# Recalculate velocity from energy and new location, flip sign
self.vy = -vy_direction * (2 * (energy - GRAVITY * self.y))**0.5
我发现在碰撞检查之前应用一半的重力,在碰撞检查之后应用一半的重力基本上解决了我的问题。
def update(self, dt):
# Update positions
self.x += self.vx * dt
self.y += self.vy * dt
# Apply gravity step 1
self.vy += GRAVITY/2 * 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
# Apply gravity step 2
self.vy += GRAVITY/2 * dt
我认为你的问题在于你正在进行完美弹性碰撞。我的意思是,当你击中边缘时,你的垂直速度只是乘以-1。这样做的作用是球永远不会失去垂直速度。
尝试将 vy 代码更改为:
if self.y - RADIUS < 0 or self.y + RADIUS >= HEIGHT: # You don't want it to equal the height. It should be strictly less.
self.vy *= -0.95 # Instead of a factor of 1. This will dampen the ball thereby reducing the max height it can go.
self.y = max(RADIUS, min(HEIGHT - RADIUS, self.y)) # Clamp y within bounds