ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零学会Pygame:兔子接月饼小游戏源码拆解与开发实战

从零学会Pygame:兔子接月饼小游戏源码拆解与开发实战 简介面向Python及Pygame初学者的节日主题小游戏源码包以“兔子接月饼”为玩法载体完整展示了游戏开发中的初始化窗口、事件循环、精灵创建与碰撞检测等核心开发流程。资源压缩后共16个文件主要包含Python源码、png/jpg静态图片与mp3背景音乐整体大小3.12MB结构清晰目录层次清楚便于直接运行和二次学习。其中游戏主体脚本统一控制游戏初始化、开始、结束与退出精灵模块则定义了月饼、兔子、背景、得分、倒计时、菜单、鼠标共七个继承自pygame.sprite.Sprite的角色类涵盖从元素渲染到状态切换的常见需求。随包附带的素材涵盖兔子、月饼、背景及音效便于理解Pygame的资源加载方式与精灵组管理。目前已有168人学习下载适合作为Python课程设计、兴趣开发或Pygame框架入门学习的参考实践项目。1. 兔子接月饼一个 Pygame 小游戏源码里的完整学习路径带新人的时候我一般会让第二周的项目从控制台程序切到图形界面兔子接月饼这类题材就是从这时候开始高频出现的。一个python pygame 兔子接月饼小游戏源码.zip解压之后通常只有两三百行代码加几张图片却能同时覆盖 Python 基础里的条件判断、循环、随机数、类和键盘事件。这个标题真正值钱的地方不在“免费源码”而在把“游戏循环、精灵对象、碰撞结算、状态切换”这四件事串起来的方式。这篇博客会顺着多数源码包的常见结构把这些机制拆开讲最后把调试好的工程整理成一个可分发的新源码包方便你在本地改、在笔记里记、再发给别人跑。2. 先把窗口转起来Pygame 游戏循环与初始化步骤2.1 环境准备与源码包目录识别拿到 zip 之后不要急着双击运行。先确认 Python 环境干净常见做法是建一个独立虚拟环境避免 pygame 的版本和系统里其他包互相影响python -m venv rabbit_game # Windows 激活 rabbit_game\Scripts\activate # Linux / macOS 激活 source rabbit_game/bin/activate pip install pygame第一行用python -m venv创建虚拟环境激活后 pip 安装的包都会装进这个目录而不是全局环境。pip install pygame会自动拉取当前平台对应的预编译包Windows 和 macOS 一般不需要编译如果安装过程中提示error: failed to build pygame when getting requirements to build wheel多半是 pip 版本太老或者网络源不稳定可以先pip install --upgrade pip再换国内镜像源重试。解压后先看目录结构一个结构正常的 pygame 源码包长这样文件 / 目录作用main.py或game.py入口文件包含初始化与主循环rabbit.png/mooncake.png兔子与月饼的图片资源settings.py或config.py屏幕尺寸、速度、生成间隔等常量notes.md或README.md运行说明与依赖清单requirements.txt依赖列表方便别人一条命令装环境识别入口文件有个小技巧源码包里文件名带main的通常是启动脚本。如果只有game.py就搜索pygame.display.set_mode这行代码所在文件就是入口。2.2 游戏循环四段式事件处理、更新、绘制、刷新pygame 的图形界面不是 React 那样的声明式框架它靠的是一个不结束的 while 循环每秒钟把画面重新画几十遍。最小可运行的主循环是这样的import sys import pygame pygame.init() screen pygame.display.set_mode((720, 960)) pygame.display.set_caption(兔子接月饼) clock pygame.time.Clock() while True: for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() screen.fill((20, 24, 50)) pygame.display.flip() clock.tick(60)pygame.init()统一初始化所有模块set_mode创建窗口并返回一个 Surface 对象之后的绘制都画在这块画布上。事件循环里只处理了QUIT也就是用户点关闭按钮时干净退出sys.exit()用来结束整个进程。screen.fill用深蓝色填充背景模拟夜空display.flip()把后台缓冲一次性推到屏幕上clock.tick(60)把循环限制在每秒 60 帧。这段代码里最容易被忽略的是clock.tick(60)。没有它程序会以 CPU 能跑多快就跑多快的方式刷新不同机器上游戏速度完全不同。锁帧之后后面所有物体的位移量才能按“每帧多少像素”来设计。2.3 帧率与物体移动速度的关系很多人改源码时会犯同一个错误想让兔子跑得快一点把speed从 8 改到 15结果发现速度几乎没变或者在不同电脑上体验不一样。原因在于没有理解“游戏速度 每帧位移 × 每秒帧数”。clock.tick(60)保证约 16.7 毫秒一帧此时每帧移动 8 像素一秒大约移动 480 像素。如果取消帧率限制高速显示器上每秒可能跑 300 帧以上物体快到看不见。所以 pygame 项目里有两个位置不能动帧率设置和位移代码里的固定增量。想做更严谨的帧率无关移动可以把clock.tick(60) / 1000.0得到的秒数作为delta_time乘进位移公式里但一个 60 帧锁定的简单小游戏不需要引入这个复杂度保持固定增量反而更容易调参。3. 兔子和月饼精灵类、键盘控制与掉落逻辑3.1 用精灵类封装游戏对象游戏循环跑起来之后下一步是让屏幕上有东西动。pygame 的sprite.Sprite基类提供了一套对象管理机制所有可移动元素都继承它。看一下兔子对象的常见写法import random import pygame class Rabbit(pygame.sprite.Sprite): def __init__(self, image): super().__init__() self.image image self.rect self.image.get_rect(midbottom(360, 920)) self.speed 9 def update(self): keys pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.rect.x - self.speed if keys[pygame.K_RIGHT]: self.rect.x self.speed self.rect.x max(8, min(self.rect.x, 720 - 8 - self.rect.width)) class Mooncake(pygame.sprite.Sprite): def __init__(self, image): super().__init__() self.image image self.rect self.image.get_rect(center(random.randint(30, 690), -40)) self.speed 5 def update(self): self.rect.y self.speed if self.rect.top 960: self.kill()Sprite子类必须设置self.image和self.rectpygame 的碰撞检测和管理组都依赖这两个属性。get_rect(midbottom(360, 920))用中点底部定位兔子这样它站在画面底部而不是左上角。update方法里用pygame.key.get_pressed()一次性获取所有按键状态比逐个判断KEYDOWN事件更适合连续移动。月饼对象从画布上方进入y坐标每帧加 5掉出底部之后调用kill()把自己从所有 Group 里移除避免无限累积占内存。这里能看出精灵封装的价值每个对象自己管自己的位置和移动逻辑主循环里只需要统一调用一次update()。3.2 键盘控制与移动边界的参数设计key.get_pressed()返回一个长度为几百的序列按下对应键时对应位置为 True。K_LEFT和K_RIGHT是 pygame 预定义的键位常量不需要额外引入键盘库。边界限制的表达式max(8, min(self.rect.x, 720 - 8 - self.rect.width))值得展开说明。外层max(8, ...)保证 rect 的 x 坐标最小为 8防止兔子贴到左边缘内层min(...)保证右边界不超过720 - 8 - 宽度。这 8 像素是留出的缩进视觉上兔子不会撞墙。加速度和手感相关的几个参数可以先按下面的基线起步参数推荐值调节效果兔子速度8 ~ 10 像素/帧低于 6 手感迟钝高于 13 容易穿过月饼初始月饼速度4 ~ 5 像素/帧决定前 30 秒的容错率月饼生成间隔700 ~ 900 毫秒间隔越短同屏数量越多3.3 月饼生成定时器与随机坐标月饼不能一次性全部生成需要在游戏过程中每隔一段时间出现一个。pygame 提供了一套基于事件的定时器机制CREATE_MOONCAKE pygame.USEREVENT 1 pygame.time.set_timer(CREATE_MOONCAKE, 800) mooncakes pygame.sprite.Group() # 主循环的事件处理里 for event in pygame.event.get(): if event.type CREATE_MOONCAKE: mooncakes.add(Mooncake(images[mooncake]))pygame.time.set_timer(event_id, milliseconds)会让 pygame 每隔指定毫秒自动向事件队列投递一个自定义事件。事件号不能直接写pygame.USEREVENT因为同一个事件号可能被多处复用常见做法是pygame.USEREVENT N递增使用。Mooncake构造函数里用random.randint(30, 690)生成横向坐标比固定一个位置掉落更有可玩性。所有月饼实例放进一个Group之后主循环里只需要调用mooncakes.update()每个实例的update会被自动遍历执行。Group 的另一层作用是后面碰撞检测可以直接对整个组做结算不需要手动遍历列表。别忘了在绘制阶段加上mooncakes.draw(screen)否则月饼只存在于逻辑中屏幕上什么都看不到。4. 碰撞检测、计分和难度曲线兔子接月饼的手感从哪来4.1 rect 碰撞、掩码碰撞与 groupcollide 的选择月饼掉下来之后判断“接没接到”是游戏的核心逻辑。pygame 提供了多级碰撞检测手段最常用的是矩形交集判定。两个 Sprite 的rect只要相交colliderect就返回 True。结合 Group 可以一行代码完成所有月饼的判定from pygame.sprite import spritecollide hits spritecollide(rabbit, mooncakes, True) score len(hits)spritecollide(sprite, group, dokill)的第三个参数True表示碰撞后从 Group 中移除该月饼这正是接住月饼后它应该消失的行为。默认用的是rect碰撞也就是两个矩形框相交就算命中。如果兔子和月饼的图片边缘大量留白矩形框之间隔着透明区域也会判定碰撞误判率会高到影响体验。图片素材干净的时候可以换用更精确的掩码碰撞hits spritecollide(rabbit, mooncakes, True, pygame.sprite.collide_mask)pygame.sprite.collide_mask基于图片的 alpha 通道做逐像素判定只把不透明像素的相交视为碰撞。代价是每帧计算量更大月饼数量超过 20 个时可能出现掉帧。4.2 计分、生命值与游戏状态切换有碰撞结果之后后续逻辑要区分两种结果接住加分漏掉扣命。月饼掉出屏幕时已经在Mooncake.update里被kill()了所以要在掉落过程中判断命数而不是等它消失。常见做法是检查月饼的rect.top是否超过屏幕高度GAMEOVER pygame.USEREVENT 2 class State: MENU 0 RUNNING 1 OVER 2 state State.RUNNING score 0 lives 3状态机用三个常量区分菜单、游戏中和结束主循环里根据state决定渲染内容。漏月饼的判断写在Mooncake.update里不太合适因为精灵拿不到游戏全局变量。更常见的是在主循环里遍历 Groupfor mc in mooncakes: if mc.rect.top 960: mc.kill() lives - 1 if lives 0: state State.OVER计分和生命都不能在碰撞检测时直接改全局变量而是先拿回结果再统一结算这样调试时可以很容易加日志确认一轮碰撞的处理次数。pygame 并没有限制全局变量的用法但代码整洁度会直接影响后面扩展难度曲线时的心情。4.3 难度递增的三个参数与推荐取值兔子接月饼的可玩性几乎全部来自难度曲线。难度由三个参数控制月饼下落速度、生成间隔、同屏最大数量。调整规则一般是“速度线性增、间隔指数减、数量阶梯式加”等级月饼速度(像素/帧)生成间隔(毫秒)同屏上限149003267005385007411350951424012调参时不要三个参数同时改否则很难判断手感变化来自哪一个变量。我一般的做法是先固定生成间隔只把速度从 4 往 14 推找到玩家能反应过来的临界值再把间隔从 900 毫秒往下压记录败率明显上升的点最后才设置同屏上限。同屏上限的实现可以放在生成计数器里用一个变量记录当前 Group 长度达到上限后本次定时器事件直接跳过不创建新月饼。等级提升的触发条件用分数阈值即可例如每 100 分升一级。等级变化时用一个字典把等级映射到上面的四元组参数代码里只做查表赋值不写一堆 ifLEVELS { 1: {speed: 4, interval_ms: 900, max_on_screen: 3}, 2: {speed: 6, interval_ms: 700, max_on_screen: 5}, 3: {speed: 8, interval_ms: 500, max_on_screen: 7}, 4: {speed: 11, interval_ms: 350, max_on_screen: 9}, 5: {speed: 14, interval_ms: 240, max_on_screen: 12}, } if level 5 and score level * 100: level 1 params LEVELS[level] pygame.time.set_timer(CREATE_MOONCAKE, params[interval_ms])set_timer可以被重复调用新间隔会覆盖旧设置无需重新初始化模块。这样升级过程中事件源始终是同一个逻辑上不会出现多个生成器叠加。5. 构建产物整理把源码封装成可分发的新 zip5.1 源码 zip 的标准目录与笔记当项目调试完成下一步是把它整理成别人也能直接用的新源码包。这个步骤和下载现成的 zip 正好形成闭环。规范的项目目录应该把配置、精灵和入口分开而不是把几百行代码全塞进一个文件rabbit_catch_mooncake/ ├── main.py ├── settings.py ├── sprites.py ├── images/ │ ├── rabbit.png │ └── mooncake.png ├── notes.md ├── requirements.txt └── build.pyrequirements.txt只需写入pygame2.0不要跟着本机装的版本号锁死否则别人在另一台机器上安装时可能遇到版本不匹配。notes.md里记录运行步骤、按键操作和难度参数表作为源码配套笔记。压缩时选中这个外层目录而不是里面的散文件这样别人解压后直接得到一个文件夹不会散落一地文件。5.2 PyInstaller 打包与资源路径处理源码 zip 适合学习和二次开发但给别人演示时没人愿意先配 Python 环境。用 PyInstaller 把它打成单文件可执行程序是更成熟的交付方式pip install pyinstaller pyinstaller -F -w -n RabbitMooncake main.py-F表示生成单个可执行文件-w表示 Windows 下不弹出控制台窗口-n指定产物名称。执行后 exe 会出现在dist/目录下。需要注意图片资源不会自动打包进 exe运行时相对路径可能失效。解决方法是写一个资源路径转换函数import sys from pathlib import Path def resource_path(relative: str) - Path: if hasattr(sys, _MEIPASS): return Path(sys._MEIPASS) / relative return Path(__file__).parent / relative代码里hasattr(sys, _MEIPASS)判断是否运行在 PyInstaller 解压出的临时目录里是就优先使用该目录查找图片否则使用脚本所在目录。重新打包后把 exe 和 zip 一起发给测试者再让他们重点验证三件事首次打开是否超过 3 秒、连续运行 10 分钟内存是否有明显增长、关闭窗口后进程是否完全退出。本文还有配套的精品资源点击获取
返回列表