用pygame打造会唱歌的全屏回忆相册:零基础Python项目

发布时间:2026/9/28 15:09:02
用pygame打造会唱歌的全屏回忆相册:零基础Python项目 直接说结论这是一个零基础也能照着手敲出来的 Python 小项目——用 pygame 做一个全屏播放的回忆相册每张照片配上专属背景音乐按一下空格键一颗心形烟花就在屏幕中间炸开。做完之后还能打包成 exe 发给对方不需要对方装 Python 环境。这是“表白贺卡进阶四”这一期前面几期我们做过静态贺卡、动态文字、粒子爱心这一期直接升级成“会唱歌的回忆相册”——照片、音乐、全屏、动态、烟花全塞在一个项目里。如果你完全是 Python 零基础只需要会装软件、会复制粘贴、能耐心看完这篇文章就能做出来。过程中涉及到的每个概念我都会用大白话解释不用你先啃一本语法书。1. 从需求到方案这个“会唱歌的回忆相册”该怎么落地1.1 拆解需求四个“花哨关键词”背后其实只有三件事很多初学者看到“资产代码”的第一反应是这么多功能肯定要写几百行吧其实只要把需求拆开这个项目比你想象的简单。标题里提到的几个核心词翻译成人话是全屏照片展示时铺满屏幕类似 PPT 放映沉浸感足够。动态音乐照片切换时音乐跟着换每张照片有自己的 BGM。心形烟花一个视觉特效在画面上炸出爱心形状的粒子动画。合在一起这个项目只做三件事照片展示、音乐切换、粒子特效。只要把这三块各自跑通剩下的就是拼装配对问题。1.2 为什么选 Python pygame而不是网页方案你可能想问为什么不用 HTML CSS JS 做个网页相册网页确实能做而且视觉效果上限更高但这里有个关键问题目标用户是零基础学习者不是前端工程师。网页方案涉及浏览器 API、JS 音频权限、页面自适应任何一个环节卡住都得折腾一下午。Python pygame 的优势是很直白的一个.py文件就能跑通全流程不用理解页面标签和语义。pygame 把窗口、图片、键盘事件、音频播放都封装成了简单函数学习曲线平缓。最后用 PyInstaller 打包成 exe对方双击就能跑不需要装任何环境。我把两种方案放在一起对比过真实感受写在表里方案环境依赖全屏与音乐控制打包分发适合谁Python pygame装 Python 并 pip 安装函数调用直接搞定PyInstaller 打包零基础 Python 学习者Web 网页浏览器打开即可需要 JS 处理交互和音频权限部署到服务器或做桌面壳有前端基础的人决定用 pygame 还有一个隐藏理由这个项目是“表白贺卡进阶”系列的一部分系列前面的代码都是 Python 写的延续同一个技术栈你的学习成本不会白白浪费。1.3 项目结构与素材规划先把“施工图纸”画出来写代码之前我强烈建议先规划项目目录不然素材一多就乱了。我的目录长这样heart_album/ ├── photos/ # 放照片支持 jpg / png │ ├── photo_01.jpg │ ├── photo_02.jpg │ └── photo_03.jpg ├── music/ # 放音乐推荐 ogg 格式 │ ├── song_01.ogg │ ├── song_02.ogg │ └── song_03.ogg └── main.py # 主程序照片和音乐怎么配对我建议用“序号对应”这种土办法photo_01.jpg配song_01.oggphoto_02.jpg配song_02.ogg。程序里按索引去拿文件就行不用维护一长串路径对应关系。素材准备有两个经验要说第一照片不要用 4K 原图。pygame 加载大图非常吃亏内存占得高切换时明显卡顿。实操里我都是先把照片压缩到 1920 宽度以内微信发出去也轻松。第二音乐格式优先用 OGG 或 WAV不要直接用 MP3。pygame 对 MP3 的兼容性有些随机同一首歌在不同机器上可能一个正常一个噪音。后面第 4 章我会专门说这个问题。2. 0基础环境搭建Python 安装与依赖部署2.1 安装 Python这一步的关键是勾选 PATH零基础读者请老实按这个流程来打开 Python 官网下载页面找到最新的稳定版点击下载 Windows installer 或 macOS 安装包。安装时有一个很容易忽略的选项Add Python to PATH。一定要勾上。这决定了你在命令行里能不能直接用python命令。安装完成后打开命令行Windows 按Win R输入cmdmacOS 打开终端输入python --version如果输出了类似Python 3.12.x的信息环境就装好了。如果提示python 不是内部或外部命令绝大多数情况是第二步没勾选 PATH。重新运行安装包选择 Modify把 Add Python to PATH 勾上即可。macOS 用户如果系统自带的是 Python 2.x 或者没有 Python建议直接用官网安装包装 3.x不要折腾 Homebrew 版本匹配的问题——零基础阶段越省事越好。2.2 用 pip 安装 pygame一行命令就够Python 装好之后命令行里输入pip install pygame等它下载完会看到类似Successfully installed pygame-2.x.x的提示。装好之后可以顺手验证一下pip show pygame能看到版本信息就说明安装成功。这一步常见的报错是pip 不是内部或外部命令。解决方式和上面一样回退一步重新装 Python 并勾选 PATH。还有同学会遇到网络慢超时换成国内镜像源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple这里的核心建议是不要跳过去装一大堆库。这个项目只要 pygame 一个外部依赖就够了其他全部用 Python 标准库sys、os、math、random。所以你的环境搭建压力非常小。2.3 准备照片、音乐素材格式和命名都有讲究素材准备阶段我踩过的坑值得你避免。照片方面直接用手机相册的照片也行但建议先过一遍压缩。Windows 用户可以用图片编辑工具的“另存为”调整宽度到 1920macOS 用户直接预览导出即可。照片格式 jpg 或 png 都行pygame 对这两种支持最好。不要用 webp 之类的冷门格式加载很容易出问题。音乐方面前面说了我推荐转成 OGG 格式。用什么转搞个 FFmpeg 最省心ffmpeg -i input.mp3 -acodec libvorbis output.ogg不熟悉命令行的朋友也可以下载格式工厂、Audacity 这类图形工具导出 ogg 格式码率选中等就行。为什么非要折腾因为在 pygame 项目里ogg 的兼容性比 mp3 稳得多尤其是把 exe 发给对方电脑之后mp3 解码出各种诡异问题的概率明显更高。命名上严格用photo_01.jpg、song_01.ogg这种格式。顺序编号从 01 开始程序里读取时直接用索引映射不用做字符串解析。3. 核心轮播全屏照片展示与自动翻页3.1 初始化窗口与全屏模式这两行代码是地基照片展示的第一步是把 pygame 的窗口拉起来。先别急全屏开发阶段我建议先用普通窗口调试等所有功能都通了再切全屏。原因后面第 6 章会详细说。先看初始化代码import pygame import sys import math import random pygame.init() screen pygame.display.set_mode((0, 0), pygame.FULLSCREEN) width, height screen.get_size() clock pygame.time.Clock()这里set_mode((0, 0), pygame.FULLSCREEN)就是全屏模式的关键语法。第一个参数(0, 0)表示使用当前屏幕分辨率第二个参数pygame.FULLSCREEN表示全屏。如果开发阶段想用窗口模式把这行替换成screen pygame.display.set_mode((1000, 700))然后后面涉及width、height的代码都能继续用get_size()拿到的就是 1000×700。有了窗口之后还要理解 pygame 的“主循环”机制。不夸张地说没搞懂这个循环pygame 就学了个寂寞。running True while running: clock.tick(60) for event in pygame.event.get(): if event.type pygame.QUIT: running False pygame.display.flip()这个while True循环每秒执行约 60 次由tick(60)控制帧率每次循环做三件事处理用户输入事件、更新画面内容、把画面刷新到屏幕上。想象成做动画每一帧都重新画一张图连续播放就动了。3.2 图片加载与等比缩放关键是“不把脸拉变形”照片显示最核心的难点不在于“加载图片”而在于“放得下还不变形”。回忆一下屏幕是 1920×1080而照片可能是 4032×3024 竖图。直接screen.blit(photo, (0, 0))会只显示照片的左上角一块不是我们想要的效果。正确姿势是计算等比缩放比例def load_scaled_image(path): img pygame.image.load(path) img_w, img_h img.get_size() scale min(width / img_w, height / img_h) new_w int(img_w * scale) new_h int(img_h * scale) return pygame.transform.scale(img, (new_w, new_h))这里的关键是min(width / img_w, height / img_h)。意思很简单把照片同时缩放到屏幕宽和高都放得下取两者中更保守的那个比例。可以这样理解你有一块 1920×1080 的画布照片长宽比各不相同。如果按宽缩放竖图可能超出去如果按高缩放横图可能超出去。min保证两个方向都不超结果就是照片完整出现在屏幕内剩余空间用黑色背景填充。使用的时候photo load_scaled_image(photos/photo_01.jpg) screen.blit(photo, photo.get_rect(center(width // 2, height // 2)))get_rect(center...)是 pygame 特别常用的一个小技巧它返回图片的矩形并自动把中心点放在指定位置。这样不管照片横竖都能稳稳居中。3.3 翻页交互与自动播放把照片串成一部“幻灯片”照片串起来的方式我建议用一个列表来管理。每个元素是一个字典存照片路径和音乐路径slides [ {photo: photos/photo_01.jpg, music: music/song_01.ogg}, {photo: photos/photo_02.jpg, music: music/song_02.ogg}, {photo: photos/photo_03.jpg, music: music/song_03.ogg}, ]当前显示哪张照片用一个索引记录current_index 0 current_photo load_scaled_image(slides[current_index][photo])左右翻页的键盘事件处理写法很固定for event in pygame.event.get(): if event.type pygame.KEYDOWN: if event.key pygame.K_RIGHT: current_index (current_index 1) % len(slides) current_photo load_scaled_image(slides[current_index][photo]) elif event.key pygame.K_LEFT: current_index (current_index - 1) % len(slides) current_photo load_scaled_image(slides[current_index][photo])这里用了% len(slides)取模。作用是到最后一张再按右自动回到第一张到第一张按左自动跳到最后一张。不用写一堆 if 去判断边界。自动播放本质上就是一个倒计时器。用clock.tick(60)返回的毫秒数累加tick dt if tick 8000: tick 0 current_index (current_index 1) % len(slides) current_photo load_scaled_image(slides[current_index][photo])dt是上一帧到这一帧的时间差单位是毫秒。累加到 8000 就表示 8 秒到了自动切下一张。这里强烈建议先不要加音乐等照片轮播跑顺了再进入下一章因为加音乐后排查音频问题会干扰照片切换的调试。4. 让相册真正“唱歌”音乐播放与照片联动4.1 pygame 音乐模块先把这几个函数记牢pygame 对音乐播放封装得比较人性化核心函数就几个函数作用pygame.mixer.music.load(path)加载音频文件支持 ogg、wav、mp3pygame.mixer.music.play(loops0)开始播放loops-1 表示无限循环pygame.mixer.music.stop()停止当前播放pygame.mixer.music.get_busy()返回是否正在播放需要注意一个细节play(-1)里的-1是循环次数不是“第几秒开始”。初学者容易把参数含义搞混。我见过不少项目因为写了play(1)导致音乐放完一遍就没了整个相册后半程是“哑巴”的。4.2 照片与音乐配对把素材绑定到同一套索引在 3.3 节的slides列表里我已经给每张照片预留了music字段。这就意味着照片和音乐的配对关系已经天然建立当前是第 0 张照片就播slides[0][music]对应的音乐切到第 1 张就播第 1 首。这一步的清晰逻辑很重要。早期的做法是把照片和音乐各写一个列表再用同样的下标去取。一旦中间删了一张照片后面的对应关系全乱了。用字典把路径放在一起删除或调整顺序都只动一个列表不易出错。4.3 切换音乐与容错不要让一首坏歌毁掉整个相册照片切换时音乐的切换逻辑是def play_slide_music(index): pygame.mixer.music.stop() pygame.mixer.music.load(slides[index][music]) pygame.mixer.music.play(-1)每次切照片前先stop()当前音乐再加载新的再无限循环播放。顺序不能反不然音乐还没停就 load 新文件会有奇怪的报错。但我还要多说一句生产环境的代码一定要加容错。实际使用中你不可能保证每首音乐都格式完美。如果某首坏掉了pygame.mixer.music.load()会直接抛异常整个程序当场崩溃。加了容错是这样def play_slide_music(index): pygame.mixer.music.stop() try: pygame.mixer.music.load(slides[index][music]) pygame.mixer.music.play(-1) except pygame.error: print(音乐文件加载失败跳过, slides[index][music])我亲测过这个场景做好相册发给对方后对方电脑上有一首音乐因编码问题加载报错如果没有 try 保护当场黑屏退出体验非常糟糕。加三行 try 就能让程序跳过坏歌继续播放照片。启动相册时也要调用一次play_slide_music(0)把第一首音乐播上。别忘了这个初始化。如果想让音乐和照片自动同步得更准我建议在“自动播放”那段里切换照片后同时调用play_slide_music(current_index)。这样 8 秒一到照片和音乐一起换观感是“各就各位”的。5. 心形烟花粒子系统的浪漫落地5.1 先搞懂粒子系统一袋彩色纸屑怎么形成爱心烟花的本质不是一张“爱心图片”在屏幕上移动而是一堆小粒子从中心出发各自飞到一个目标位置最终拼凑成一个心形轮廓。可以这样理解你手里有一杯彩色纸屑往空中使劲一撒纸屑各自有初速度和方向你观察到的就是一堆轨迹。如果让纸屑的终点不是随机散落而是沿着一条爱心曲线分布那它们在屏幕上形成的图案就是一颗心。这个思路实现起来分三步生成心形曲线上的目标点坐标。为每个目标点创建一个小粒子粒子从中心出发向目标点移动。粒子到达目标点后停留一会儿然后逐渐变透明消失。整个过程每帧重绘视觉上就是“心形从中心浮现并消散”。5.2 心形曲线方程数学很好用抄下来就行心形曲线有很多种表达方式pygame 项目里最常用的是这个参数方程x 16 * sin(t)^3 y 13 * cos(t) - 5 * cos(2t) - 2 * cos(3t) - cos(4t)当 t 从 0 变化到 2π一个完整周期x 和 y 的组合点就能勾勒出一个标准的心形。写成 Python 函数def heart_points(cx, cy, scale20, count220): points [] for i in range(count): t i / count * 2 * math.pi x 16 * math.sin(t) ** 3 y 13 * math.cos(t) - 5 * math.cos(2 * t) - 2 * math.cos(3 * t) - math.cos(4 * t) points.append((cx int(x * scale), cy - int(y * scale))) return points这里有个关键细节屏幕坐标系的 y 轴是向下增长的而数学坐标系的 y 轴是向上增长的。如果不做处理生成的心形会上下颠倒。所以代码里是cy - int(y * scale)减号就是把 y 反过来心形才能正着显示。count220表示生成 220 个点点数越多心形轮廓越密烟花效果越饱满。实战中我试过 100 个点看起来稀疏220 个点刚刚好400 个点视觉提升不大性能还下降没必要。5.3 让烟花“放”出来粒子类与发射逻辑有了目标点接下来定义粒子类。每个粒子要有当前位置、目标位置、颜色和生命周期class Particle: def __init__(self, x, y, target_x, target_y, color): self.x x self.y y self.target_x target_x self.target_y target_y self.color color self.progress 0 self.speed random.uniform(0.02, 0.05) self.life 60 def update(self): if self.progress 1: self.progress self.speed self.x (self.target_x - self.x) * 0.12 self.y (self.target_y - self.y) * 0.12 else: self.life - 1 def alive(self): return self.life 0 def draw(self, surface): alpha min(255, self.life * 4) temp pygame.Surface((8, 8), pygame.SRCALPHA) pygame.draw.circle(temp, (*self.color, alpha), (4, 4), 4) surface.blit(temp, (int(self.x) - 4, int(self.y) - 4))update方法里用了一个插值公式粒子位置每次向目标点移动 12% 的剩余距离。视觉效果是粒子先快后慢地“滑”向目标比匀速运动自然得多。life是粒子到达目标后的停留帧数。alive()根据生命值判断粒子是否该移除避免粒子无限堆积导致程序越来越卡。发射烟花时围绕心形曲线生成粒子def spawn_heart_firework(cx, cy): particles [] colors [(255, 80, 80), (255, 150, 80), (255, 220, 120), (255, 255, 255)] for px, py in heart_points(cx, cy, scalerandom.randint(15, 25)): particles.append(Particle(cx, cy, px, py, random.choice(colors))) return particles颜色我用了红、橙、浅黄、白四个色系随机分配。红色是主基调暖色系在黑色背景上非常亮眼。你也可以改成粉色系搭配更有少女感。在主循环里按下空格键就发射烟花elif event.key pygame.K_SPACE: fireworks.extend(spawn_heart_firework(width // 2, height // 2))用width // 2和height // 2把烟花中心放在屏幕正中间。如果你想让烟花跟随鼠标位置绽放可以把这两个值替换成pygame.mouse.get_pos()的坐标。这个扩展做起来很容易。每帧更新所有粒子并过滤掉已经消失的for p in fireworks: p.update() fireworks [p for p in fireworks if p.alive()]6. 打包分发与踩坑实录6.1 用 PyInstaller 打包成 exe一个命令的事做好的相册总不能每次都让对方装 Python、装 pygame那就直接用 PyInstaller 打包。先安装pip install pyinstaller打包命令pyinstaller -F -w main.py参数说清楚-F打包成单文件方便传输。-w不显示控制台窗口让程序看起来完全像一个桌面软件。打包完成后会生成一个dist文件夹里面有main.exe。把photos、music这两个文件夹复制到 exe 同目录下双击 exe 就能跑。这里有个大坑要提醒PyInstaller 不会帮你打包外部资源。很多人第一次打包后运行报错以为代码有问题其实只是 exe 旁边没有photos和music文件夹。所以复制资源文件夹这个步骤我会写进自己的打包清单里。6.2 资源路径与中文字体最容易翻车的两件事路径问题项目目录、照片名、音乐名都不要出现中文。中文路径在 Windows 环境下经常引发各种编码报错别给自己找麻烦。文件夹名用photos这种英文内部照片文件photo_01.jpg这种编号最稳妥。中文字体问题如果后期你给照片加了文字标题比如“我们的毕业旅行”pygame 自带的默认字体不支持中文屏幕上会显示一排方块。解决办法是手动加载一个中文字体文件比如微软雅黑的msyh.ttc或从网上下载开源的中文字体文件放到项目目录里font pygame.font.Font(fonts/msyh.ttc, 40)字体文件不存在时可以先做一个判断如果加载失败就返回默认字体保证程序不崩。6.3 常见问题速查表按我自己的经验把最常踩的坑整理成了表格保存下来能省很多时间现象可能原因解决方案运行报 ModuleNotFoundError: No module named pygame没有安装 pygamepip install pygame窗口一闪而过代码运行时异常程序直接退出先用set_mode((1000, 700))窗口模式调试看控制台报错信息音乐播放是刺耳噪音MP3 编码兼容性问题把音频转成 OGG 或 WAV图片加载失败文件名拼错、路径包含中文检查文件名大小写/后缀改用纯英文路径打包后照片音乐丢失exe 旁边没有资源文件夹把 photos、music 和 exe 放在同一目录中文文字全是方块没有加载中文字体文件手动加载 msyh.ttc 等中文字体照片切换偶尔卡一下图片太大加载耗时将图片压缩到 1920 宽度以内6.4 我私藏的几点避坑技巧最后分享几条不太会写进基础教程里的经验。开发时先用窗口模式不要一上来全屏。全屏模式下print 输出的调试信息要切回桌面才能看到排错效率极低。等所有功能稳定后再换成全屏模式最后 30 秒的事。照片切换时提前加载下一张图。这个项目规模不大一张张加载无所谓。如果照片增加到几十张切换时会有明显卡顿。进阶做法是维护两张图当前图和下一张图在自动播放等待阶段把下一张预先加载到内存。这个优化对零基础来说可以先不做但知道有这回事后面自然用得上。素材命名规范比代码更重要。我做这个项目时素材整理花了半个多小时代码写完反而只要十几分钟。照片和音乐按序号对好整个数据结构的复杂程度直线下降。给相册加一个“惊喜彩蛋”。空格键触发烟花这个功能可以在演示时突然按一下对方的反应会比看照片本身更有趣。如果你想把烟花改成正能量祝福语、爱心图标之类思路完全一样替换目标点生成函数即可。7. 后续可以怎么玩从“回忆相册”到更多可能做完这个项目你已经掌握了 pygame 的窗口管理、图片显示、键盘事件、音乐播放、粒子系统这几块核心能力。把它们组合起来能做的远不止相册。比如把照片换成“倒计时翻页”的日历音乐换成语音提醒就变成一个早安问候程序把心形烟花的目标点换成文字轮廓就变成告白弹幕把全屏相册和 exe 打包流程结合起来还能做一个带密码验证的私密相册。我个人在实际操作中的体会是零基础学 Python最忌讳的是只看书不动手。这个项目里每一个功能都有一个直接可见的反馈照片能全屏显示了音乐会跟着切换了按一下空格烟花炸开了。这些反馈会让你越写越有动力比从变量、数据类型开始啃有趣得多。最后再分享一个小技巧照片、音乐都放在外部文件夹而不是打包进 exe以后想换内容直接替换文件就行不用重新打包。这个细节在送人的时候尤其好用——不用改一行代码爱情故事就能持续更新。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询