资讯详情

Pygame实战:兔子接月饼游戏开发全流程解析

📅 2026/9/16 8:15:56 | 华诺云谱 👁 阅读
Pygame实战:兔子接月饼游戏开发全流程解析
简介面向Python初学者的pygame趣味小游戏完整源码包以中秋兔子接月饼为主题演示如何用pygame搭建一个完整且可交互的游戏循环。包内不仅包含游戏主体逻辑和精灵类定义还提供了配套图片、背景音乐等全部素材共计16个文件其中10张图片用于角色和界面展示3张图片作为场景背景1个音频文件负责背景配乐另有2个Python脚本整个压缩包只有3.12MB下载和使用都很轻量。项目共实现了月饼、兔子、背景、得分、倒计时、菜单、鼠标七个精灵所有精灵都继承自pygame的Sprite基类这样设计的好处是能够清晰体会面向对象编程与pygame精灵组机制的结合。通过阅读代码可以学习到窗口初始化、游戏事件循环、碰撞检测、文字渲染、倒计时控制以及音频播放等多个核心模块的写法。目前已有168人学习下载代码结构清晰、注释到位既能够直接运行体验游戏效果也适合作为Python课程设计或pygame入门练手的参考实例。1. 兔子接月饼Pygame 实战里最值得抄的第一份完整代码有一类小游戏的源码在技术社区里被反复翻出来看规则一眼就懂但里面塞满了游戏开发的常用套路。兔子接月饼就是这样——玩家控制一只兔子左右移动去接住从天上不断掉落的月饼接得越多分越高漏接三次游戏结束。很多第一次完整跑通 Pygame 的人都死磕过这份代码的某个细节为什么兔子用的是rect.x而不是坐标赋值、为什么月饼掉落的y值要不停加上speed、为什么碰撞检测放在event循环外面。Pygame 写小游戏本质就是在做三件事初始化窗口和时钟、循环里处理用户输入、更新物体的坐标和碰撞状态。这份兔子接月饼源码把这三件事压缩在一个主循环里肉眼能跟完调试起来也不绕。对新手来说它是从「照着官方文档画一个红色矩形」到「能拿得出手、能给别人玩」之间最近的一步对有几年经验的工程师来说它的价值在另一面看别人如何把一个游戏的逻辑状态用最直接的方式表达出来回头重构起来心里会特别有数。2. Pygame 环境跑通与游戏主循环的最小骨架先让窗口和时钟转起来2.1 安装 Pygame 前先确认 Python 版本macOS 和 Windows 的做法不一样兔子接月饼源码依赖 Pygame而 Pygame 的安装坑大半出在 Python 版本和虚拟环境上。现在多数发行版 Pygame 已经跟得上最新的 Python 3.12、3.13但仍然建议项目单独建虚拟环境不要直接装到系统环境里——后期你要 pip freeze 导出依赖或者换机器跑源码时虚拟环境能少一半麻烦。python3 -m venv mooncake_env source mooncake_env/bin/activate # Windows 下是 mooncake_env\Scripts\activate pip install --upgrade pip pip install pygame提示如果pip install pygame报error: failed to build pygame when getting requirements to build wheel多数不是 Pygame 本身的问题而是 pip 版本过旧或系统 Python 缺少头文件。先pip install --upgrade pip再装绝大多数情况直接解决。验证能不能用一条命令进 Python 交互式环境import pygame pygame.init() print(pygame.version.ver)能打出版本号说明环境通了。切记这一步不要省——很多源码跑不起来的第一个原因不是代码写错是 Pygame 的某些依赖在系统里没装完全。2.2 主循环的四段式结构pygame 游戏跑起来全靠这一套Python 的 Pygame 小游戏不论兔子接月饼还是俄罗斯方块主循环长相都差不多初始化、事件处理、更新状态、绘制画面。这四段式是 Pygame 的固定套路也是这份源码最容易让人看得一头雾水的地方。import pygame def main(): pygame.init() screen pygame.display.set_mode((800, 600)) pygame.display.set_caption(Mooncake Catcher) 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_ESCAPE: running False # 2. 更新游戏逻辑兔子移动、月饼下落 # 3. 绘制背景和物体 screen.fill((30, 30, 60)) pygame.draw.rect(screen, (255, 240, 200), (400, 500, 80, 50)) # 4. 刷新画面 pygame.display.flip() clock.tick(60) pygame.quit() if __name__ __main__: main()代码里第 1 段pygame.event.get()是事件队列鼠标、键盘的输入全在这里必须每帧取一次不取就堆积窗口也会卡成未响应。第 2 段和第 3 段的顺序是一开始最容易搞反的点先更新坐标再绘制画面才是连续的反过来就会看到物体拖着残影或者慢半拍。第 4 段clock.tick(60)是核心中的核心它把循环锁在每秒 60 帧保证不同电脑上游戏速度一致。这段骨架能跑通兔子接月饼的框架就算立住了。后面的兔子贴图、月饼下落、碰撞检测全都是在这个循环里加代码块而已。3. 兔子对象与月饼对象的建模思路pygame 里的 Rect 和 Surface 是关键3.1 Surface 代表可见物体Rect 决定位置兔子接月饼里每个角色都是这两个的组合Pygame 里没有「角色」这个概念所有看得见摸得着的东西都是Surface——就是一块矩形内存区域用来装像素。Rect则是这块区域的几何信息或者理解成一个不透明的碰撞盒子。兔子是一张图片加载成的 Surface月饼也是一张图片加载成的 Surface区别只在位置坐标和尺寸。rabbit pygame.image.load(rabbit.png).convert_alpha() rabbit_rect rabbit.get_rect() rabbit_rect.x 350 rabbit_rect.y 500 mooncake pygame.image.load(mooncake.png).convert_alpha() mooncake_rect mooncake.get_rect() mooncake_rect.x 200 mooncake_rect.y 0convert_alpha()常见做法是加载 PNG 之后立刻调用作用是把图片像素格式转换成 Pygame 内部最擅长绘制的格式同时保留透明通道。如果你在源码里见过有人不写这一句画面上往往会有黑色方块背景——那就是 alpha 通道没处理好。Rect的几个常用属性在兔子接月饼里全都能用上x、y是左上角坐标centerx、centery是中心点left、right、top、bottom是四边。碰撞检测时用两个rect.colliderect()直接判断比手动比较 x、y 值快得多也准得多。提示鼠标控制兔子时不要直接rabbit_rect.x mouse_x否则鼠标跳到屏幕另一端时兔子也瞬移过去体验极差。常见做法是取鼠标 x 坐标后让rabbit_rect.centerx跟随并限制在窗口宽度范围内。3.2 月饼下落的速度和加速度参数抄源码时最需要改的就是这三个值月饼掉落的实现逻辑极其朴实每帧让mooncake_rect.y加一个固定值。至于加多少直接决定游戏难度。看别人写的兔子接月饼源码最常改的就是这三行mooncake_speed 3 # 初始下落速度单位是像素/帧 mooncake_accel 1 # 每接住一个月饼后速度增量 mooncake_max_speed 12 # 速度上限防止游戏后期快得完全没法玩这三个参数放在while循环外面初始化每接住一个就加一次速。为什么这样设计因为如果从第一帧到最后都用一个速度游戏曲线太平玩家三分钟就腻了如果无限加速后期谁都防不住。减速带和上限是让游戏「越玩越快但永远差一点」的关键。在更新月的块下落逻辑的代码里实际长这样if mooncake_rect.y 600: mooncake_rect.y mooncake_speed else: # 月饼掉出屏幕底部视为漏接 lives - 1 mooncake_rect.y -50 mooncake_rect.x random.randint(0, 700)月饼从y -50开始往下掉超出窗口底部就重置位置重新从顶上丢下来。这里的随机 x 用了random.randint所以每次掉落的位置不可预测——这是小游戏保证「每局都不一样」的最小手段。源码里一般不会做月饼的花样轨迹抛物线、左右飘动都是进阶玩家的二次开发了。3.3 碰撞检测用 colliderect 还是 maskpickle 一个细节图片透明区域的碰撞假阳性兔子接月饼的碰撞逻辑大多数源码直接用colliderect代码三行搞定if rabbit_rect.colliderect(mooncake_rect): score 10 mooncake_rect.y -50 mooncake_rect.x random.randint(0, 700)这行代码配合图片后有一个隐蔽的问题如果月亮糕图片是圆形的但它的 Rect 是正方形兔子碰到月饼图片的四角位置时肉眼上还没接触分数却已经加了。透明 PNG 图片越大这四个角的误差越明显。解决办法是靠pygame.mask做像素级碰撞。把兔子和月饼各做一份 Mask再调用pygame.sprite.collide_maskfrom pygame.sprite import collide_mask rabbit_mask pygame.mask.from_surface(rabbit) mooncake_mask pygame.mask.from_surface(mooncake) if rabbit_mask.overlap(mooncake_mask, (mooncake_rect.x - rabbit_rect.x, mooncake_rect.y - rabbit_rect.y)): score 10但mask碰撞需要额外分配内存和计算点阵对兔子接月饼这种物体数量只有两个的小游戏来说完全无所谓如果一份源码里同时有几十个月饼在飞mask 碰撞的成本就要掂量下了。新手改源码时建议先一致用colliderect跑顺了再升级成 mask两者并存时反而容易出 bug。4. pygame 的渲染循环与分数/生命值 HUD把兔子接月饼从「能动」做到「能玩」4.1 每帧填充背景、绘制图片与文字的固定顺序pygame 的渲染顺序就是遮挡顺序Pygame 没有 z-index 概念后画的东西永远盖在先画的东西上面。兔子接月饼的渲染顺序通常是背景色、月饼、兔子、HUD 文字、生命值。这个顺序看着随机实际上决定了游戏的视觉正确性。screen.fill((20, 20, 50)) # 1. 背景清空上一帧残留 screen.blit(mooncake, mooncake_rect) # 2. 月饼最早渲染 screen.blit(rabbit, rabbit_rect) # 3. 兔子压在月饼上面 score_surface font.render(fScore: {score}, True, (255, 255, 255)) screen.blit(score_surface, (10, 10)) # 4. HUD 最上层 pygame.display.flip()screen.fill不写的话上一帧的画不会消失而是会全体残留在屏幕上形成拖影。flip()把整块后台缓冲推到前台显示器上——这是双缓冲机制每帧先在后台画完再一次展示避免闪烁和撕裂感。文字显示有两个坑是新手最容易踩的。第一个是字体要用pygame.font.SysFont(simhei, 28)这种中文字体名称用默认字体画中文出来的是一堆方框。第二个是字体尺寸固定的情况下把分数数字画上去后如果数字位数变了需要用render返回的surface.get_width()计算宽度才不会覆盖到别的东西。4.2 用精灵组 Sprite 统一管理多个月饼pygame 的 group 机制比裸 Rect 列表抗造基础版本里一个月饼接完重置位置自己管代码还好写。但到了有二三十个月饼满天飞的地步手写列表管理坐标和状态会让代码变成一堆for和if。Pygame 的精灵组机制就是为这个场景准备的。import pygame import random class Mooncake(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image pygame.image.load(mooncake.png).convert_alpha() self.rect self.image.get_rect() self.rect.x random.randint(0, 700) self.rect.y -30 self.speed random.randint(2, 6) def update(self): self.rect.y self.speed class Rabbit(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image pygame.image.load(rabbit.png).convert_alpha() self.rect self.image.get_rect(topleft(x, y))Sprite子类里image和rect两个名字是约定俗成pygame.sprite.Group的绘制和碰撞方法都认这两个属性。主循环里的调用就变得异常简洁all_sprites pygame.sprite.Group() mooncakes pygame.sprite.Group() rabbit Rabbit(350, 500) all_sprites.add(rabbit) for _ in range(10): m Mooncake() all_sprites.add(m) mooncakes.add(m) while running: all_sprites.update() hit_list pygame.sprite.spritecollide(rabbit, mooncakes, False) for m in hit_list: score 10 m.rect.y -30 m.rect.x random.randint(0, 700) all_sprites.draw(screen) pygame.display.flip()spritecollide第三个参数传True的话被碰到的月饼会被从 group 里移除并消失传False则保留。兔子接月饼一般传False因为月饼不应该被接一次就永久消失而是回顶部重新掉。这里要注意如果你的某份源码里就是传True它的设计意图可能是「接住即吃掉、月饼数量有限」两种都是合理的玩法设计看游戏想要什么效果。4.3 生命值和游戏结束状态的过渡逻辑避免一张 Game Over 画面卡死循环兔子接月饼的第三个核心循环状态是游戏的结束。漏接三个月饼或者按源码里设定的生命值归零游戏要停止接住逻辑展示结算画面并且等玩家按键重开。常见做法是加一个if game_over分支lives 3 score 0 game_over False while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False if game_over and event.type pygame.KEYDOWN and event.key pygame.K_r: # 重启所有变量 lives 3 score 0 game_over False if not game_over: # 原有的移动检测、碰撞检测 # 当 lives 0 时 game_over True else: screen.blit(game_over_bg, (0, 0)) restart_hint font.render(Press R to restart, True, (255, 255, 255)) screen.blit(restart_hint, (250, 400)) pygame.display.flip()这套结构最忌讳的是把 game over 检测直接放在pygame.QUIT分支里然后pygame.quit()退出那玩家想重玩就得重新执行整个脚本。重开变量时记得全部重置——包括分数、生命值、月饼的 y 坐标缺一个就等于开挂或卡死。有些源码直接把main()函数又递归调用一次也能跑但 python 的递归深度有限玩个几十局就有崩的风险不建议在游戏主循环里递归。5. Pygame 源码常见报错与三个必调的参数把复用性做对才敢拿去改5.1 Image not found 与 font 报错的排查方向别急着改代码逻辑跑兔子接月饼源码时九成报错集中在资源加载上。pygame.error: Couldnt open rabbit.png这类问题不是图片不存在就是路径不对。源码里的rabbit.png是相对路径意味着工作目录必须和图片文件在同一层级。排查命令要会用ls -la # 确认当前目录文件 pwd # 看当前工作目录如果你用 vs code 打开整个文件夹再运行当前目录通常会变成文件所在目录如果你只在 vs code 里打开了一个main.py再运行当前目录可能是项目根目录图片相对路径照样失效。最稳妥的是用绝对路径但要考虑换机器后路径变化——折中方案是用os.path.join(os.path.dirname(__file__), rabbit.png)构造路径这会永远以代码文件所在目录为基准。字体报错最常见的是pygame.error: no system font不让指定simhei这时换成pygame.font.get_default_font()看能不能出字或者用pygame.font.match_font找一个系统中文字体。如果要在没有中文字体的服务器上跑干脆所有显示文本都用英文省时省力。5.2 分数计算与速度递增的平衡中央的tick(60)与 delta time 真的该用了主流源码里clock.tick(60)是硬编码帧率月饼下落速度也是按「像素/帧」写的。因此在高刷新率显示器120Hz、144Hz上如果代码写成tick(120)或者忘写tick月饼会以两倍速度落下游戏难度直接翻倍。差分时间delta time就是把速度从「像素/帧」改为「像素/秒」的标准做法。dt clock.tick(60) / 1000.0 # 转成秒dt 表示上一帧耗时 mooncake_rect.y mooncake_speed * dt * 60这里dt是上一帧到当前帧的秒数乘回 60 是保持原来参数数值不变。如果不想让成品移植到不同刷新率的机器上难度突变这个改动比加任何游戏功能都值得。没引入 delta time 的源码在笔记本和台式机上玩的难度会有肉眼可见的差别。6. 打包成独立可执行文件并保留游戏节奏的验证方法最后收官再说三个实用技巧6.1 用 PyInstaller 打成 exe依赖内部资源路径的小技巧源码最终要发给别人玩不能要求对方装 Python 和 Pygame。打包工具常见用的是 PyInstaller命令简单但有两个细节pip install pyinstaller pyinstaller -F -w mooncake.py --add-data rabbit.png:. --add-data mooncake.png:.-F打成单文件-w去掉命令行黑窗Windows 下是--noconsole--add-data把图片资源打进包内。但单文件模式的运行时会把资源解压到临时目录os.path.dirname(__file__)这时拿到的是解压路径反而不对。标准做法是运行时判断是否被打包import sys, os if getattr(sys, frozen, False): base_path sys._MEIPASS else: base_path os.path.dirname(__file__) rabbit_img_path os.path.join(base_path, rabbit.png)sys._MEIPASS是 PyInstaller 单文件模式的专属属性——已打包时base_path用它开发时用脚本目录。这样同一个代码既能本地python mooncake.py跑也能打包后正常运行。6.2 测试游戏难度是否合理的两个指标帧率稳定性比分数数字更能说明问题游戏写完不是能跑就完事还要验证难度曲线。接住率就是实际接到的月饼数量除以月饼总数这个指标在玩两局之后看最直观——第一局能接到 60% 以上说明前期太简单到第五分钟还能维持在 40% 以上说明速度递增太慢。调试速度参数时先调mooncake_speed初始值 1 像素/帧、微调mooncake_accel增量不要同时动三个参数否则一次改完根本说不清是哪个变化影响了手感。6.3 隐藏资源的两个字pygame 的 event 循环里加上暂停功能很多成品源码没有暂停功能被打断时只能眼睁睁看兔子漏接。加暂停很简单在事件循环里拦截一个p键进入一个内层等待循环paused False while running: for event in pygame.event.get(): if event.type pygame.KEYDOWN and event.key pygame.K_p: paused not paused if paused: pause_text font.render(Paused, True, (255, 255, 255)) screen.blit(pause_text, (350, 280)) pygame.display.flip() continue # 原有的更新和绘制逻辑continue的效果是跳过当帧的移动计算和碰撞检测只保留事件处理和画面冻结。这样做的代价很小但绝大多数网上流传的兔子接月饼源码都缺这一步补上以后可玩性会明显提升。真正高手拿到别人的 Pygame 作品第一件事也不是看代码逻辑而是先把这类手感细节捋一遍。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。