拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Flappy Bird 的 Python 实现:用 pygame 从零到打包的完整教程

简介这是一份基于Python的Flappy Bird小游戏完整课程设计资源主要面向Python初学者、游戏开发入门者以及需要完成课程设计的在校生。压缩包内共96个文件包含18个py源码文件、52张png图片素材、10个wav与10个ogg音频文件、2个ico图标、2个md说明文档以及1份docx设计报告整体体积仅1.56MB结构十分紧凑。资源实现了完整的游戏逻辑管道与深绿浅绿交替的地板持续向左滚动模拟小鸟向前飞行的效果点击空格键或↑键让小鸟向上做减速运动松开后自由落体同时具备碰撞检测与游戏结束界面。随附的设计报告详细介绍了实现原理、模块划分与测试过程代码采用模块化组织注释清晰可直接运行也可作为二次开发基础。该资源已有378人学习很适合通过实战项目理解pygame游戏循环、精灵碰撞与状态管理等核心知识点。1. Flappy Bird 的 Python 实现从一句 “pip install pygame” 开始几乎每个学 Python 的人都会在某一天冒出念头把 Flappy Bird 写出来。游戏规则简单到一句话——小鸟受重力下落点一下屏幕就给一个向上的速度穿过管道缝隙就得分。但真正动手才发现难点根本不在语法而在物理手感重力给多少、跳跃速度给多少、管道空隙留多少每一项都直接影响游戏好不好玩。这篇文章用 pygame 把这套逻辑完整拆开从 python 安装检查、主循环、碰撞判定到打包成独立程序给你一份能直接复现的 Python flappybird 方案适合刚学语法的初学者也适合想快速搭小游戏原型验证想法的人。2. 用 pygame 让小鸟动起来环境、主循环与物理参数2.1 先确认 python 安装和 pygame 版本写代码前先确认环境。从 Python 官网下载安装包并安装完成后在终端执行python --version pip install pygame2.6.1 pip list | findstr pygame如果你在 Linux 系统安装 python命令可能是python3和pip3属正常现象。固定版本号只是为了减少 pygame 2.5 到 2.6 之间接口差异带来的困惑实际项目直接装最新稳定版也行。Windows 下用pip list | findstr pygame查看是否安装成功macOS/Linux 把findstr换成grep。下载慢的话可以加国内源地址pip install pygame2.6.1 -i https://pypi.tuna.tsinghua.edu.cn/simple。不太建议往系统 Python 里装一堆库常见做法是先建虚拟环境python -m venv venvWindows 下执行venv\Scripts\activatemacOS/Linux 执行source venv/bin/activate。用 vscode 配置 python 环境时按CtrlShiftP打开命令面板选择 “Python: Select Interpreter” 指向刚才创建的 venv否则解释器用的是系统环境还是虚拟环境很容易搞混。验证安装可以跑python -m pygame.examples.aliens能弹出小飞机游戏窗口就说明环境正常。这一步能提前排除“代码没问题但库没装好”的假象后面每段代码都依赖 pygame 的 Rect、Surface、event 接口。2.2 主循环的三段式结构事件、更新、绘制pygame 项目骨架是一个永不停止的 while 循环每帧做三件事处理输入、更新状态、绘制画面。下面是最小可运行的主循环import pygame import sys SCREEN_WIDTH 400 SCREEN_HEIGHT 640 FPS 60 def main(): pygame.init() screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) clock pygame.time.Clock() running True while running: # 1. 事件处理关闭窗口、按键输入 for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: if event.key pygame.K_SPACE: bird.jump() # 2. 状态更新移动管道、施加物理 bird.update() pipes.update() # 3. 绘制先填背景再画对象最后翻转画布 screen.fill((135, 206, 235)) pipes.draw(screen) bird.draw(screen) pygame.display.flip() clock.tick(FPS) pygame.quit() sys.exit()逻辑说明pygame 的绘制都发生在内存里的 Surface 对象上screen.fill只是填充背景色真正把内容推到窗口需要pygame.display.flip()。clock.tick(FPS)把循环上限锁在每秒 60 帧避免不同性能的电脑上小鸟飞得不一样快。事件里的bird.jump()和更新里的bird.update()分别处理“输入”和“物理”职责分得很清楚。参数说明SCREEN_WIDTH400、SCREEN_HEIGHT640是竖屏手机比例建议沿用这个比例管道坐标计算会更直观。FPS60是 flappybird 类游戏的标准帧率太低动作卡顿太高后续物理参数要成倍调整反而不利于调手感。2.3 重力加速度与跳跃速度的参数设计Flappy Bird 的物理模型只有两个量重力gravity和跳跃初速jump_vel。用 Bird 类封装class Bird: def __init__(self, x, y): self.x x self.y y self.vel 0 self.gravity 0.5 self.jump_vel -8.5 def update(self): # 速度受重力影响先加速度再位移 self.vel self.gravity self.y self.vel def jump(self): # 把当前速度直接重置为跳跃初速 self.vel self.jump_vel逻辑说明update里先让速度加上重力再把速度加到位移上是经典欧拉积分。按下空格时jump()把速度重置为负值表示向上飞下一帧马上被重力拉回来形成一条回落曲线。如果发现小鸟跳得很高但掉得慢多半是重力太小如果按键没反应或滞空感很强先检查事件里KEYDOWN有没有把jump()接到K_SPACE上。参数说明单位是像素/帧。gravity0.5表示每帧速度增加 0.5 像素/帧jump_vel-8.5表示按下瞬间给一个向上 8.5 像素/帧的初速。这两个值决定了跳跃峰值高度和下落弧线是后面全部手感调优的基础。提示pygame 坐标系 y 轴向下所以“向上”是负速度千万别把跳跃写成self.vel abs(self.jump_vel)。2.4 帧率不稳时的 delta time 修正上面的代码要求帧率恒定。如果电脑后台负载高导致掉帧小鸟每帧位移不变现实时间里的速度就变慢了。若要消除这种差异可以引入 delta timedt clock.tick(FPS) / 1000.0 # 单位秒 class Bird: def update(self, dt): self.vel self.gravity * 60 * dt self.y self.vel * 60 * dt逻辑说明把加速度和位移都乘上60 * dt。当帧率降到 30每帧经过的时间是原来的两倍位移也自动变大保证物理速度恒定于 60 帧基准。参数说明dt是 pygame 返回的上一次clock.tick()到现在的毫秒数除以 1000 转成秒。60 * dt是归一化系数帧率正好 60 时该值为 1所以原有参数语义不变。如果你打算后期加多人联机或后台高负载逻辑这个接口建议从第一天就留好但 flappybird 本地演示、课程作业、打包给朋友这类固定帧率场景直接用帧步进写更直观。3. 管道生成、碰撞判定和得分Flappy Bird 的关卡引擎3.1 用 pygame.Rect 做碰撞检测而不是像素级检测小鸟图片四周有透明区域直接用整张图判定会出现“撞空气”。常见做法是给小鸟一个比图片小的判定矩形class Bird: def get_collision_rect(self): rect pygame.Rect(self.x, self.y, 34, 24) return rect逻辑说明pygame.Rect内置colliderect方法用整数矩形做相交判断比逐像素检查透明通道快得多。碰撞面积缩小到 34×24而绘制图片可能是 48×36这样管道缝隙边缘的判定更宽容玩家不会觉得“明明没碰到却死了”。绘制用图片矩形碰撞用判定矩形两张 Rect 分开维护。集中判定写成一个函数def check_collision(bird, pipes, ground_rect): bird_rect bird.get_collision_rect() if bird_rect.colliderect(ground_rect): return True for pipe in pipes: if bird_rect.colliderect(pipe.top_rect) or bird_rect.colliderect(pipe.bottom_rect): return True return False参数说明ground_rect是地面区域矩形Flappy Bird 最容易漏掉的就是地面碰撞很多初学者只判断管道小鸟掉出屏幕底部还能继续飞。pipe.top_rect和pipe.bottom_rect分别是上、下管道矩形。colliderect只需要两个矩形有一点重叠就返回True所以判定矩形不能太大。3.2 管道成对生成随机空隙区间怎么设管道上下成对出现中间留空隙。空隙的中心位置和大小决定难度class Pipe: PIPE_WIDTH 60 GAP 150 def __init__(self, x): self.x x # 空隙中心在屏幕高度范围内随机但不能太靠边 gap_center random.randint(120, SCREEN_HEIGHT - 200) top_rect_height gap_center - self.GAP // 2 bottom_rect_y gap_center self.GAP // 2 bottom_rect_height SCREEN_HEIGHT - bottom_rect_y self.top_rect pygame.Rect(x, 0, self.PIPE_WIDTH, top_rect_height) self.bottom_rect pygame.Rect(x, bottom_rect_y, self.PIPE_WIDTH, bottom_rect_height)逻辑说明先随机出空隙中心点gap_center再往上、下各取GAP / 2算出两根矩形。随机范围两端留了余量上管道至少 120 像素高下管道底部距离地面至少 200 像素否则会出现几乎无法通过的极限关卡。参数说明GAP150对新手比较舒服逐级减小到 120 后难度骤增。PIPE_WIDTH60保持适中管道太宽会让玩家没有转向余地。有些实现会随分数随机空隙宽度我不推荐因为玩家靠肌肉记忆判断跳跃点空隙忽大忽小会让手感变得不可预测。难度递增应该保留 GAP 不变只调管道间距或移动速度。3.3 管道的移动与回收别让列表无限膨胀管道每帧往左移动移出屏幕后要回收。配套的还有生成间隔控制PIPE_SPEED -3 PIPE_INTERVAL 220 def update_pipes(pipes): for pipe in pipes: pipe.x PIPE_SPEED pipe.top_rect.x pipe.x pipe.bottom_rect.x pipe.x # 回收彻底移出屏幕的管道 pipes[:] [p for p in pipes if p.x Pipe.PIPE_WIDTH 0] def spawn_pipe_if_needed(pipes): last_x max(pipe.x for pipe in pipes) if pipes else SCREEN_WIDTH 100 if last_x SCREEN_WIDTH PIPE_INTERVAL: pipes.append(Pipe(SCREEN_WIDTH 100))逻辑说明这里单独维护pipe.x再同步给两个矩形而不是直接改rect.x因为x还要用于 3.4 的得分判断职责分离后不容易乱。列表推导式把已经完全移出屏幕的管道对象丢掉内存不会随时间增长。spawn_pipe_if_needed每帧检查最靠右的管道位置一旦它离屏幕右缘只剩 220 像素就在屏幕外补一根保证两根管道间的水平间隔整体均匀。参数说明PIPE_SPEED-3是每帧左移 3 像素60 帧下每秒移动 180 像素PIPE_INTERVAL220控制两根管道之间的反应时间。如果缩小到 150 以下玩家还没从上一根调整好高度就面临下一根难度会突然上一个台阶。移动速度超过绝对值 5 后矩形每帧跳跃的距离可能跳过判定区出现“看上去没碰到却死了”的视觉错位。3.4 得分判定经过管道而不是碰到管道初学者常把加分放在碰撞里结果小鸟死了还能继续加分。正确的做法是给每根管道一个“是否已结算”标记class Pipe: def __init__(self, x): self.x x self.scored False def update_score(pipes, bird_x, score): for pipe in pipes: if not pipe.scored and pipe.x Pipe.PIPE_WIDTH bird_x: score 1 pipe.scored True return score逻辑说明管道右边缘超过小鸟的 x 坐标说明小鸟已经完全穿过这根管道加一分并置标记。标记跟着 Pipe 对象走不需要额外集合记录“哪根已经得分”。不判断scored会导致同一根管道每帧都满足条件分数疯狂上涨。参数说明判断条件写的是pipe.x Pipe.PIPE_WIDTH bird_x不是pipe.x bird_x。pipe.x是左边缘坐标左边缘刚过小鸟时小鸟才走到管道一半此时计分会显得分数提前。加上PIPE_WIDTH让分数点在管道完全越过小鸟之后更符合“穿过”的直觉。4. 从能玩到像个产品开始、结束画面与资源加载4.1 用状态机管理 READY、PLAYING、GAMEOVER游戏有三个状态待开始、游戏中、结束。如果全用 if-else 堆在主循环里代码很快就乱。状态机是常见做法STATE_READY 0 STATE_PLAYING 1 STATE_GAMEOVER 2 state STATE_READY while running: for event in pygame.event.get(): if event.type pygame.KEYDOWN and event.key pygame.K_SPACE: if state STATE_READY: state STATE_PLAYING elif state STATE_PLAYING: bird.jump() elif state STATE_GAMEOVER: reset_game() state STATE_READY if state STATE_PLAYING: bird.update() update_pipes(pipes) score update_score(pipes, bird.x, score) if check_collision(bird, pipes, ground_rect): state STATE_GAMEOVER逻辑说明同一个空格键在三个状态里有不同含义——READY 开始游戏PLAYING 跳跃GAMEOVER 回到 READY 并复位。状态机的好处是从结构上杜绝了“游戏结束后还能跳跃”的 bug因为 PLAYING 之外根本不会调用bird.jump()。参数说明状态用整数常量而不是字符串避免拼写错误工程变大后可以升级成enum.Enum。绘制阶段也要按状态区分READY 显示标题和操作提示PLAYING 显示正常画面GAMEOVER 显示本次得分和重生提示。状态迁移只发生在这三处赋值点排查逻辑时只需要看这几个分支。4.2 图片与音效加载路径写不好就跑不起来下载来的 zip 工程打开就报错多半是资源路径问题。用相对路径assets/bird.png在 IDE 里能跑换到别的目录就崩。推荐固定写法import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) def load_image(path): full os.path.join(BASE_DIR, assets, path) try: img pygame.image.load(full).convert_alpha() return img except pygame.error as e: raise SystemExit(f资源加载失败: assets/{path}, 错误信息: {e})逻辑说明os.path.abspath(__file__)永远指向当前 .py 文件所在目录再拼上assets子目录打包后的 exe 只要保持 exe 和 assets 的相对关系就不会受“从哪个终端启动”影响。convert_alpha()保留 PNG 的透明通道漏掉的话透明区域会变成黑色方块。try-except的作用是启动时明确提示缺哪张图而不是黑窗闪退。参数说明背景这类不透明大图可以改用.convert()提升加载速度图标、小鸟这类需要透明的图必须用.convert_alpha()。音效在pygame.init()之后才能用pygame.mixer.Sound加载因为 mixer 需要先完成初始化。若设备没有音频输出可以用pygame.mixer.pre_init(44100, -16, 2, 512)预先配置并在异常时不阻塞主逻辑。4.3 死亡后复位统一 reset 函数GAMEOVER 后重新开始不需要重新pygame.init()只需要把所有可变状态恢复到初始值def reset_game(): global bird, pipes, score, state bird Bird(100, SCREEN_HEIGHT // 2) pipes [] score 0 state STATE_READY逻辑说明reset_game负责复位所有参与主循环的变量包括 bird、pipes、score 和 state。用global是因为函数内重新赋值会创建局部变量不加global外部状态不会被修改。也可以把所有状态塞进一个GameState类来减少全局变量但在三百行以内的小游戏里直接全局操作反而更直观。参数说明小鸟出生 x 坐标设在窗口宽度三分之一处y在屏幕垂直居中位置。管道列表重置为空下一帧的生成逻辑会自动拉出第一根管道。分数必须归零否则玩家死一次后带着上一局分数接着跑状态会明显错乱。音效、背景图这类加载一次不再变的资源不需要放在 reset 里重新加载。5. 手感调优、PyInstaller 打包和三个容易踩的坑5.1 参数表重力、跳跃和空隙的推荐区间调参是 flappybird 最花时间的部分给你一张推荐区间表作为起点参数推荐区间手感变化gravity0.40.7太小飘太大沉jump_vel-7-10数值越负跳得越高GAP130180越小需要跳跃越精准PIPE_SPEED-2-4绝对值越大节奏越快这是一组联动参数gravity 和 jump_vel 决定跳跃弧线GAP 和移动速度决定反应窗口。我的习惯是先固定 gravity0.5用 jump_vel 把峰值高度调到“能擦过上一根管道边缘”的观感最后再按死亡原因收紧 GAP。验证方法是连续玩二十局大多数死亡原因是跳早了就把 GAP 调大 10 像素跳不起来把 jump_vel 往负方向调 0.5。5.2 PyInstaller 打包与三个容易踩的坑标题里那个.zip本质就是打包分发。用 PyInstaller 生成可执行文件pip install pyinstaller pyinstaller --onefile --noconsole --add-data assets;assets main.py逻辑说明--onefile打包成单个 exe--noconsole去掉黑色命令行窗口--add-data assets;assets把资源目录一起打进包。Windows 用分号分隔源和目标macOS/Linux 用冒号。打包完成后 exe 在dist目录把它和 assets 一起压缩成 zip就是最常见的发布形态。三个最容易踩的坑convert_alpha()没写透明 PNG 背景变黑小鸟变成方块。碰撞矩形直接用整张图片尺寸透明边缘导致“撞空气”死亡点比视觉位置早。pygame.event.get()被放进条件分支窗口在某个状态下完全无响应看起来像假死它必须每帧无条件执行。提示Windows 下输入python报 “python was not found; run without arguments to install from the Microsoft Store” 时说明 Python 未加入 PATH重装时勾选 “Add python.exe to PATH”否则 pip 和 pyinstaller 都调用不到。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门