- 示例工程
【免费下载链接】python-mini-projects
A collection of simple python mini projects to enhance your python skills
python-mini-projects仓库中的projects/Bouncing_ball_simulator是一个用 Pygame 实现的轻量级物理仿真示例:5 个小球在一个 800×600 的容器内受重力加速下落,并与容器底部、左右墙壁及顶部发生弹性碰撞后持续反弹。本文以该项目的 README.md 为主线,结合 ball_bounce.py 源码逐行拆解其物理模型、碰撞检测逻辑与主循环结构,帮助你快速掌握用 Pygame 编写"重力 + 弹性碰撞"仿真程序的核心套路,并了解如何调整参数获得不同的弹跳效果。
项目概览:容器内的重力弹跳仿真
项目描述(来自 README.md)指出,该脚本展示的是若干小球在重力作用下于一个容器内弹跳的仿真过程,小球不仅受重力影响,还会与容器的底部和侧壁发生碰撞。
在 ball_bounce.py 的源码注释中,作者进一步说明:
- 程序模拟了5 个球在重力加速度作用下的弹跳;
- 伴随与容器墙壁的弹性碰撞(elastic collision);
- 作者将其定位为一个"看起来很有趣"的可视化演示。
从源码结构看,整个程序只有一个类ball和一个主循环,没有引入复杂框架,非常适合作为 Pygame 入门或物理仿真入门的教学示例。项目全部资源文件如下:
| 文件 | 作用 |
|---|---|
| ball_bounce.py | 主程序:仿真逻辑、碰撞检测、渲染循环 |
| background-img.jpg | 容器背景图(1000×666 纯色背景) |
| ball.png | 小球图像资源(32×32) |
| requirements.txt | 依赖声明:pygame==2.0.2 |
运行环境与依赖准备
根据 README.md 的 Prerequisites 部分,运行本项目需要满足两个前提:
- Python 3(脚本基于 Python 3 编写);
- pygame 模块。
依赖版本在 requirements.txt 中固定为:
pygame==2.0.2安装依赖的推荐方式是按 README 给出的命令,直接通过 requirements 文件安装:
pip3 install -r requirements.txt提示:
pip3用于明确指向 Python 3 的包管理器;如果你使用虚拟环境或python3 -m pip,效果等价。固定版本pygame==2.0.2能保证渲染接口行为一致,避免因 pygame 2.x 不同小版本的 API 差异导致运行异常。
快速运行:一条命令启动仿真
README 的 "How to run the script" 部分给出了标准运行流程:
- 进入源码所在目录
projects/Bouncing_ball_simulator/; - 打开终端,执行:
python3 ball_bounce.py运行后会出现标题为"Ball Bounce Simulation"的 800×600 Pygame 窗口,背景为background-img.jpg,5 个橙色小球(ball.png)从随机位置开始运动,在重力作用下下落、与容器壁碰撞并持续弹跳,直到你关闭窗口(触发pygame.QUIT事件)为止。
需要注意:程序通过相对路径加载图像资源(pygame.image.load('background-img.jpg')与pygame.image.load('ball.png')),因此必须保持在projects/Bouncing_ball_simulator/目录下运行,否则会因找不到图片文件而报错。
源码级解析:物理模型与碰撞检测
4.1 窗口初始化与资源加载
import pygame, time, random pygame.init() screen = pygame.display.set_mode((800, 600)) background = pygame.image.load('background-img.jpg') pygame.display.set_caption('Ball Bounce Simulation')pygame.init()初始化所有 pygame 模块;set_mode((800, 600))创建 800×600 像素的仿真容器窗口;load('background-img.jpg')加载容器背景图,主循环中每帧通过screen.blit(background, (0, 0))重绘;set_caption设置窗口标题。
4.2ball类:属性设计与物理常量
class ball: ball_image = pygame.image.load('ball.png') g = 1 def __init__(self): self.velocityX = 4 self.velocityY = 4 self.X = random.randint(0, 768) self.Y = random.randint(0, 350)类属性与实例属性的职责划分:
| 属性 | 类型 | 值/取值范围 | 含义 |
|---|---|---|---|
ball_image | 类属性 | ball.png | 所有小球共享同一张图像资源 |
g | 类属性 | 1 | 每帧施加的重力加速度(像素/帧²) |
velocityX | 实例属性 | 初始4 | 水平方向速度(像素/帧) |
velocityY | 实例属性 | 初始4 | 垂直方向速度(像素/帧),向下为正 |
X | 实例属性 | 随机0~768 | 水平坐标(像素) |
Y | 实例属性 | 随机0~350 | 垂直坐标(像素) |
两个值得注意的细节:
- 初始位置随机化:
X在0~768随机,Y在0~350随机。768 = 800 - 32、350均考虑了小球图像尺寸(32×32)与窗口尺寸,确保小球出生时大致处于容器上半部分且不越出左右边界。 - 重力常数作为类属性:
g被定义为类属性而非实例属性,从设计意图看,它代表作用于所有小球的统一重力场,无需为每个球单独存储。
4.3move_ball():逐帧物理更新与碰撞响应
def move_ball(self): # 重力:每帧给垂直速度叠加向下的加速度 self.velocityY += ball.g # 按速度更新位置 self.X += self.velocityX self.Y += self.velocityY # 左右墙壁碰撞:水平速度取反 if self.X < 0 or self.X > 768: self.velocityX *= -1 # 顶部碰撞:仅在向上运动时取反并夹紧 if self.Y < 0 and self.velocityY < 0: self.velocityY *= -1 self.Y = 0 # 底部碰撞:仅在向下运动时取反并夹紧 if self.Y > 568 and self.velocityY > 0: self.velocityY *= -1 self.Y = 568这段代码是仿真的核心,本质上是离散时间步的欧拉积分(每帧一小步):
- 重力积分:
velocityY += ball.g,每帧垂直速度增加 1 个像素/帧,模拟重力持续加速下落; - 位移更新:
X += velocityX; Y += velocityY,用当前速度更新位置; - 左右墙壁碰撞:当
X < 0或X > 768(即小球越出左右边界,768 = 800 - 32考虑球宽)时,velocityX *= -1使水平速度反向——这就是"弹性碰撞"在代码中的体现:速度大小不变、方向反转; - 顶部碰撞:当
Y < 0且仍在向上运动(velocityY < 0)时,垂直速度取反并将位置夹紧到Y = 0,避免小球穿出顶部; - 底部碰撞:当
Y > 568(568 = 600 - 32,考虑球高)且仍在向下运动(velocityY > 0)时,速度取反并夹紧到Y = 568,小球落在容器底部后反弹。
碰撞条件中带方向判断(velocityY < 0/velocityY > 0)是避免"边界抖动"的关键:只有球正在朝边界运动时才触发反弹,防止球处于边界且已反向运动时被重复取反、卡在边界附近来回振荡。这是 2D 边界碰撞处理中值得借鉴的实践。
4.4 渲染与主循环
def render_ball(self): screen.blit(ball.ball_image, (self.X, self.Y)) Ball_List = [ball(), ball(), ball(), ball(), ball()] running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False time.sleep(0.02) screen.blit(background, (0, 0)) for ball_item in Ball_List: ball_item.render_ball() ball_item.move_ball() pygame.display.update()主循环的固定节奏为:
- 事件处理:轮询
pygame.event.get(),收到pygame.QUIT(点击窗口关闭按钮)时退出循环; - 帧率控制:
time.sleep(0.02),每帧间隔约 20ms,对应约 50 FPS 的刷新节奏,同时也决定了物理仿真的时间步长; - 清屏重绘:先
blit背景图覆盖上一帧画面,再遍历Ball_List依次渲染并更新每个小球; - 画面提交:
pygame.display.update()将绘制结果显示到窗口。
Ball_List一次创建 5 个ball()实例,因此"5 个球"的数量就由这行列表字面量决定——想增加或减少小球数量,直接增删这里的元素即可。
参数调优:改变弹跳效果的关键旋钮
所有物理行为都由少量常量控制,调整它们即可获得截然不同的观感:
| 参数 | 位置 | 调整方向 | 效果 |
|---|---|---|---|
ball.g | 类属性 | 调大 → 下落更快、弹跳更急促;调小 → 失重感、漂浮感 | 控制重力强度 |
velocityX/velocityY | __init__ | 调大 → 运动更激烈;设为 0 → 只做垂直弹跳 | 控制初始发射速度 |
time.sleep(0.02) | 主循环 | 调小 → 仿真更快;调大 → 慢动作回放 | 控制帧率与时间步长 |
set_mode((800, 600)) | 初始化 | 改变容器尺寸时,需同步调整边界常量768、568 | 容器大小 |
Ball_List元素个数 | 主循环前 | 增删ball()实例 | 同时弹跳的小球数量 |
例如,若将窗口改为(1024, 768),则左右边界应相应改为1024 - 32 = 992、底部边界改为768 - 32 = 736,同时建议同步调整Y的随机上限。
从源码结构看可扩展方向
当前实现是"重力 + 墙壁弹性碰撞"的最小可用版本,从源码结构看,以下扩展点都有清晰的落点:
- 小球之间的碰撞:目前
move_ball()只处理球与容器的碰撞,Ball_List中各球彼此穿透。可在主循环中增加两两距离检测,当球心距离小于直径时交换/反转速度分量; - 能量损耗(非完全弹性):将
velocityY *= -1改为velocityY *= -0.9(恢复系数 < 1),即可模拟每次反弹损失能量的阻尼效果,小球最终会静止在底部; - 随机化初始速度:
__init__中的velocityX/velocityY目前固定为 4,可改为random.randint(...),让每次运行的运动轨迹都不同; - 交互控制:在事件循环中监听键盘/鼠标事件,动态调整
ball.g或增加新球。
小结
Bouncing_ball_simulator用不到 60 行代码完整演示了 Pygame 物理仿真的标准骨架:初始化窗口 → 加载资源 → 定义运动对象(速度积分 + 边界碰撞) → 主循环(事件、更新、渲染)。其重力积分、带方向判断的边界碰撞、以及用类属性统一管理物理常量的设计,都是可复用到其他 2D 仿真项目(如弹幕、粒子系统、平台跳跃游戏)的基础模式。按 README.md 的指引安装依赖并运行 ball_bounce.py,再结合本文的参数调优表动手修改几个常量,就能直观感受到每个参数对弹跳效果的影响。
- 示例工程
【免费下载链接】python-mini-projects
A collection of simple python mini projects to enhance your python skills
相关推荐
Dify工作流图片显示排障手册:3条路径,一次搞定
Dify工作流图片显示排障手册:3条路径,一次搞定 跑完一个 Dify 工作流,回复里本该出现图片的位置只剩一个灰色叉,或者图表节点明明执行成功,前端却显示一行
示例工程Bubblezone实战教程:手把手教你实现终端列表项的点击交互效果
Bubblezone实战教程:手把手教你实现终端列表项的点击交互效果 Bubblezone是一款专为BubbleTea打造的实用工具,能够轻松实现终端应用中的鼠
如何将 Sublime Text 3 快速升级为完整 Python IDE:Anaconda 终极指南
如何将 Sublime Text 3 快速升级为完整 Python IDE:Anaconda 终极指南 Anaconda 是一个强大的 Sublime Text
人工智能大模型计算机视觉OCR本地部署AI 技能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考