如何用Python实现多小球边界弹跳动画(边界可匀速移动)
实现带移动边界的多小球无碰撞弹跳动画
看起来你已经迈出了很棒的第一步——从单个小球的边界移动开始尝试,这是拆解复杂动画需求的绝佳切入点!我会帮你把这个扩展到多小球无碰撞弹跳+移动高度边界的完整效果,咱们一步步来:
第一步:补全并完善单个小球的移动边界逻辑
先把你代码里没写完的norme函数补全,再添加移动边界的核心逻辑。这里我默认让上边界以恒定速度向下移动,你可以轻松调整为上移或下边界移动,逻辑是通用的:
import matplotlib.pyplot as plt from matplotlib import animation from matplotlib.widgets import Slider import numpy as np # 补全向量模长计算函数 def norme(vecteur): return np.linalg.norm(vecteur) # 初始化参数 fig, ax = plt.subplots() plt.subplots_adjust(bottom=0.25) # 边界参数:初始上下边界、边界移动速度(负数表示向下) y_min = 0 y_max_initial = 10 boundary_speed = -0.02 y_max = y_max_initial # 小球参数 ball_radius = 0.3 ball_pos = np.array([5.0, 5.0]) # 初始位置 ball_vel = np.array([0.5, 0.3]) # 初始速度 # 绘制小球和边界 ball, = ax.plot(ball_pos[0], ball_pos[1], 'o', color='blue', markersize=ball_radius*10) top_boundary, = ax.plot([0, 10], [y_max, y_max], 'r--') bottom_boundary, = ax.plot([0, 10], [y_min, y_min], 'r--') # 设置坐标轴范围 ax.set_xlim(0, 10) ax.set_ylim(y_min - 1, y_max_initial + 1) # 添加边界速度调节滑块 ax_slider = plt.axes([0.25, 0.1, 0.65, 0.03]) speed_slider = Slider(ax=ax_slider, label='边界速度', valmin=-0.1, valmax=0.1, valinit=boundary_speed) def update_boundary_speed(val): global boundary_speed boundary_speed = val speed_slider.on_changed(update_boundary_speed) def update(frame): global ball_pos, ball_vel, y_max # 更新移动边界的位置 y_max += boundary_speed top_boundary.set_ydata([y_max, y_max]) # 更新小球位置 ball_pos += ball_vel # 边界碰撞检测与反弹 # 左右边界反弹 if ball_pos[0] - ball_radius < 0 or ball_pos[0] + ball_radius > 10: ball_vel[0] *= -1 # 上下边界反弹(注意上边界是动态移动的) if ball_pos[1] - ball_radius < y_min: ball_vel[1] *= -1 if ball_pos[1] + ball_radius > y_max: ball_vel[1] *= -1 # 更新小球显示位置 ball.set_xdata(ball_pos[0]) ball.set_ydata(ball_pos[1]) return ball, top_boundary, bottom_boundary # 创建并启动动画 ani = animation.FuncAnimation(fig, update, frames=1000, interval=20, blit=True) plt.show()
第二步:扩展到多小球并添加无碰撞逻辑
要实现多小球互不碰撞,我们需要在每帧更新时检测任意两个小球的距离——如果距离小于两个半径之和,就按照弹性碰撞的规则(动量守恒+动能守恒)更新它们的速度:
import matplotlib.pyplot as plt from matplotlib import animation from matplotlib.widgets import Slider import numpy as np def norme(vecteur): return np.linalg.norm(vecteur) # 初始化参数 fig, ax = plt.subplots() plt.subplots_adjust(bottom=0.25) # 边界参数 y_min = 0 y_max_initial = 10 boundary_speed = -0.02 y_max = y_max_initial # 多小球参数 num_balls = 5 ball_radius = 0.3 # 随机初始化小球位置和速度(避免初始重叠) ball_positions = np.random.uniform(ball_radius, 10 - ball_radius, (num_balls, 2)) ball_velocities = np.random.uniform(-0.5, 0.5, (num_balls, 2)) # 存储所有小球的绘图对象 balls = [] for pos in ball_positions: ball, = ax.plot(pos[0], pos[1], 'o', markersize=ball_radius*10) balls.append(ball) # 绘制边界 top_boundary, = ax.plot([0, 10], [y_max, y_max], 'r--') bottom_boundary, = ax.plot([0, 10], [y_min, y_min], 'r--') ax.set_xlim(0, 10) ax.set_ylim(y_min - 1, y_max_initial + 1) # 边界速度调节滑块 ax_slider = plt.axes([0.25, 0.1, 0.65, 0.03]) speed_slider = Slider(ax=ax_slider, label='边界速度', valmin=-0.1, valmax=0.1, valinit=boundary_speed) def update_boundary_speed(val): global boundary_speed boundary_speed = val speed_slider.on_changed(update_boundary_speed) def handle_ball_collision(i, j): # 计算两个小球的相对位置和速度 pos_diff = ball_positions[i] - ball_positions[j] vel_diff = ball_velocities[i] - ball_velocities[j] distance = norme(pos_diff) # 检测是否发生碰撞(距离小于两倍半径) if distance < 2 * ball_radius: # 计算碰撞法线方向 normal = pos_diff / distance # 提取速度在法线上的分量 vel_normal_i = np.dot(ball_velocities[i], normal) vel_normal_j = np.dot(ball_velocities[j], normal) # 弹性碰撞:交换法向速度分量(假设所有小球质量相同) ball_velocities[i] -= (vel_normal_i - vel_normal_j) * normal ball_velocities[j] -= (vel_normal_j - vel_normal_i) * normal def update(frame): global ball_positions, ball_velocities, y_max # 更新移动边界 y_max += boundary_speed top_boundary.set_ydata([y_max, y_max]) # 更新所有小球的位置 ball_positions += ball_velocities # 边界碰撞检测与反弹 for i in range(num_balls): # 左右边界反弹 if ball_positions[i, 0] - ball_radius < 0 or ball_positions[i, 0] + ball_radius > 10: ball_velocities[i, 0] *= -1 # 上下边界反弹 if ball_positions[i, 1] - ball_radius < y_min: ball_velocities[i, 1] *= -1 if ball_positions[i, 1] + ball_radius > y_max: ball_velocities[i, 1] *= -1 # 小球之间的碰撞检测(只检测i<j的组合,避免重复计算) for i in range(num_balls): for j in range(i + 1, num_balls): handle_ball_collision(i, j) # 更新所有小球的显示位置 for i in range(num_balls): balls[i].set_xdata(ball_positions[i, 0]) balls[i].set_ydata(ball_positions[i, 1]) return *balls, top_boundary, bottom_boundary ani = animation.FuncAnimation(fig, update, frames=1000, interval=20, blit=True) plt.show()
关键细节说明
- 移动边界处理:每帧直接修改上边界的y坐标,同时检测小球是否碰到更新后的边界,碰到就触发反弹逻辑。
- 小球碰撞处理:默认假设所有小球质量相同,弹性碰撞时交换法向速度分量;如果需要不同质量,只需要修改碰撞后的速度计算公式即可。
- 性能优化:碰撞检测时只遍历
i < j的组合,避免重复检测;如果小球数量很多,可以考虑空间分区算法(比如网格划分)减少检测次数。
你可以根据需求自由调整小球数量、初始速度、边界移动方向/速度,甚至添加更多交互控件(比如暂停按钮、小球速度调节滑块等)。
内容的提问来源于stack exchange,提问作者Badr Al-kebsi
相关产品推荐
相关产品推荐

