简介:一套基于pygame开发的Python小游戏源码合集,共收录13款经典休闲游戏,适合Python初学者、游戏开发入门者以及需要课堂或实战案例的师生。资源涵盖滑动拼图、四子棋、记忆配对、俄罗斯方块变体、翻转棋、贪吃蛇、宝石消除等趣味项目,代码整体简洁,便于理解游戏循环、事件处理、碰撞检测和简单AI等核心概念。压缩包内包含73个文件,除13个.py源码外,还配有45张png图片素材、7个wav与4个ogg音效、2个mid背景音乐、1个ttf字体以及关卡配置txt,整体仅1.18MB,轻量易下载。目前已有903人学习使用,且文件按数字前缀分模块组织,每个项目可独立运行,便于按编号逐个体验。下载后既能直接运行体验游戏效果,也能通过修改代码改变玩法、替换素材,无论是自学入门、课程设计还是兴趣仿写,都能获得直接可用的代码参考与完整素材配套。
1. pygame-games 里的 13 个游戏源码,一条线读完
pygame-games 这类仓库不少见,13 个游戏源码攒在一个目录里,贪吃蛇、乒乓球、飞机射击都有。新手面对十几个文件夹不知从哪看起,这篇把 13 个 pygame 游戏反复出现的骨架拆开讲:装环境、跑循环、处理事件、判断碰撞、改参数、打包,一条线走完。
pygame 不是引擎,只是把 SDL 封装成 Python 模块。每个游戏本质上是"初始化窗口 + 死循环 + 刷新画面",所以读源码先找主循环,再看循环里调了哪些函数,最后才是具体逻辑。这套读法练熟,仓库里任何一个游戏都能在半小时内跑通并改出自己的版本。
适合读这篇的人有三类:学 Python 找实战项目的新手,交课程设计的学生,以及要给团队带教 pygame 项目的工程师。文中命令和代码到本机即可复现,不依赖 IDE。
2. pygame 安装与环境验证:先跑通再读源码
2.1 安装 pygame 的三种方式与 wheel 构建失败处理
安装 pygame 最直接的方式是pip install pygame,实际执行时却常卡在两个点上:一是本机装了多个 Python,pip 装的库和编辑器选中的解释器不是同一个;二是 pip 走了源码构建,报出error: failed to build 'pygame' when getting requirements to build wheel。13 个游戏源码目录里相关的 issue 大多属于这两种,先清干净环境再谈读代码。
提示:优先用
python -m pip install pygame而不是裸pip install,保证装进当前python命令对应的解释器。
三种安装方式里,省心的是直接装官方预编译 wheel,Windows 和 macOS 一条命令结束。Linux 上如果 pip 转入源码构建,通常是系统缺 SDL 开发包,常见做法是装好libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev后重试。不想动系统包,就强制只用预编译产物:
python -m pip install --only-binary :all: pygame--only-binary :all:的含义是只接受 PyPI 上现成的二进制 wheel,不触发任何源码构建。连 wheel 都找不到时,多半是 Python 版本过新或过旧,换到 3.10 ~ 3.12 之间最省时间。装好之后用版本号验证:
python -m pygame --version| 安装方式 | 适用场景 | 注意点 |
|---|---|---|
| pip install pygame | Windows/macOS 常规 | 确认解释器对应关系 |
| pip install --only-binary :all: pygame | Linux 缺编译依赖 | Python 版本不宜过新 |
| 源码编译安装 | 需要改 SDL 层行为 | 需要完整编译工具链 |
能打印出版本号,说明导入路径没问题,后面再遇到ModuleNotFoundError: No module named 'pygame',先回头查这一节。
2.2 用最小 20 行代码验证游戏循环可用
装完库别急着打开任意一个游戏,先用最小循环验证环境。跑通了再进源码,出了状况也知道是环境问题还是代码问题。
import sys import pygame pygame.init() # 初始化所有 pygame 模块 screen = pygame.display.set_mode((640, 480)) # 创建 640x480 窗口 pygame.display.set_caption("minimal loop") # 窗口标题 clock = pygame.time.Clock() # 帧率控制对象 while True: for event in pygame.event.get(): # 每帧取一次事件队列 if event.type == pygame.QUIT: pygame.quit() sys.exit() screen.fill((30, 30, 30)) # 纯色刷掉上一帧画面 pygame.display.flip() # 把内存画面整体提交到窗口 clock.tick(60) # 限制帧率 60 FPSpygame.init()会把显示、音频、字体等模块一次性初始化,省去逐个 init 的麻烦。set_mode返回的 Surface 是后续所有绘制的目标;flip()负责双缓冲切换,避免画面闪烁。clock.tick(60)把循环控制在每秒 60 次以内,这个值也是 13 个游戏源码里最常见的帧率设定。
如果报pygame.error: video system not initialized,说明pygame.init()没执行成功,通常是显示驱动或虚拟环境的问题;窗口一闪而过,检查pygame.quit()后面是否跟着sys.exit()。
2.3 帧率、坐标与方向:游戏循环三要素
pygame 没有强制性的更新框架,所有逻辑都在 while 循环里手写。读 13 个游戏源码抓住三个要素就能快速定位一段代码的意图。帧率:clock.tick(fps)的参数决定游戏速度,fps 调高同样的逻辑跑得更快。坐标:pygame 的 (0,0) 在窗口左上角,x 向右增、y 向下增,和数学课的坐标系相反,第一次写移动逻辑最容易在这里把方向搞反。方向:物体移动本质是每帧改变坐标,方向向量 (-1, 0) 表示向左。
x, y = 320, 240 speed_x, speed_y = 3, 3 # 每帧位移量,也就是移动速度 while running: x += speed_x y += speed_y # 碰到左右边界就反向 if x <= 0 or x >= 640 - 40: speed_x = -speed_x这段是乒乓球游戏里球体运动的核心:不考虑加速度,只是每帧坐标加上速度向量,边界判断后取反。13 个游戏里十有八九能搜到相似代码,区别只在边界值和物体尺寸。记住这个模式,后面改速度、改弹跳逻辑都有明确的下手位置。
3. 13 个游戏源码的通用骨架:精灵、事件与碰撞
3.1 pygame.sprite.Sprite 与 Group 的协作方式
打开任意一个游戏目录,最常见结构是一个主文件放游戏循环,一个或多个类文件定义角色。角色类基本都继承pygame.sprite.Sprite,因为 Sprite 提供了与 Group 配合所需的接口。飞机射击类的玩家、子弹、敌机,全都是 Sprite 子类。
import pygame class Player(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image = pygame.Surface((40, 30)) # 先造一个色块代替图片 self.image.fill((0, 128, 255)) self.rect = self.image.get_rect() self.rect.topleft = (x, y) def update(self): # 每帧由 Group 批量调用的方法,写移动逻辑 keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.rect.x -= 5Sprite 子类里两个必须量:self.image决定画什么,self.rect决定画在哪、碰没碰到。update()不是自动执行的,只有把实例加进 Group 后调用group.update()才会批量调用。新手容易漏掉把 Sprite 加进 Group,结果 new 出来的角色在屏幕上完全不可见。
all_sprites = pygame.sprite.Group() player = Player(300, 400) all_sprites.add(player) while running: all_sprites.update() # 批量更新所有精灵的逻辑 all_sprites.draw(screen) # 按各自 rect 把 image 画上屏幕draw(screen)内部遍历每个 Sprite,用各自的rect坐标把imageblit 到屏幕上。Group 不只是容器,还是更新和绘制的调度器;13 个游戏源码里凡是出现all_sprites命名的地方,基本都是这个套路。
3.2 事件循环里那几行不变的代码
游戏循环里除了更新逻辑,还有一段处理输入的事件代码。键盘按下、鼠标移动、窗口关闭这类外部输入被 pygame 排队,pygame.event.get()一次性取回全部事件。
for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: shoot()KEYDOWN适合处理"按一下触发一次"的动作,比如发射子弹;pygame.key.get_pressed()适合"按住不松"的连续动作,比如方向键移动。13 个游戏源码里两种混用很常见,读的时候注意区分:连续移动用 get_pressed,单次跳跃和发射用事件。常用事件按下表对照:
| 事件类型 | 触发时机 | 常见用途 |
|---|---|---|
| pygame.QUIT | 点击窗口关闭 | 退出主循环 |
| pygame.KEYDOWN | 按键按下 | 跳跃、切换、发射 |
| pygame.MOUSEBUTTONDOWN | 鼠标按键 | 拼图、菜单点击 |
| pygame.USEREVENT | 自定义定时事件 | 倒计时、道具生成 |
自定义事件在 13 个游戏里也常见,比如道具每隔 2 秒生成一次,用pygame.time.set_timer(pygame.USEREVENT + 1, 2000)注册定时事件,再在事件循环里判断event.type == pygame.USEREVENT + 1。
3.3 碰撞检测的三种写法和适用场景
pygame 的碰撞检测默认基于rect矩形,对 13 个这类小游戏精度足够。最常用的是pygame.sprite.spritecollide,返回碰撞列表或把碰撞对象从 Group 里移除:
hits = pygame.sprite.spritecollide(player, enemies, True) if hits: score += len(hits)第三个参数dokill=True表示碰撞后从 enemies 组直接移除,子弹打中敌机正好用这个特性,省一段手写列表删除。如果要区分矩形四边的碰撞方向,比如乒乓球要判断球撞到拍子的哪一面,用两个rect做逐边判断:
if ball.rect.colliderect(paddle.rect): if ball.rect.right > paddle.rect.left and ball.rect.left < paddle.rect.right: speed_x = -speed_x更高精度是pygame.mask.from_surface(image)转成掩码后做像素级碰撞,适合三角形、蛋形这类矩形误差大的物体,但每帧计算成本高。13 个游戏源码里用到 mask 的比例很低,多数场景 spritecollide 加 colliderect 就够了;对象是透明背景 PNG 时矩形判定的误差通常可以接受,不必为了精度牺牲帧率。
4. 拆一个游戏改一个游戏:参数、素材与关卡改造
4.1 从 13 个游戏里选第一个改造对象
选改造对象的原则不是看画面,而是看代码行数。贪吃蛇和乒乓球这类交互简单的,主文件通常在 200 行以内,逻辑集中在移动和边界判断,适合第一次练手;飞机射击类多了子弹、敌机刷新和碰撞分组,建议放到第二遍读。选定的另一个判据是看目录里有没有 assets 或 images 文件夹:有素材目录的,说明贴图资源分离,改造空间更大;纯代码画色的,逻辑集中但视觉效果提升有限。
拿到游戏目录后第一步永远是先跑一次原始版本,记录两个数:窗口尺寸和帧率。窗口尺寸决定你后面所有坐标和素材尺寸的参考基准,帧率决定参数改动后速度变化的观感。这两条记下来,改造前后才有对比依据。
4.2 改速度、改颜色、改碰撞范围的参数清单
13 个游戏源码风格不统一,但速度、颜色这类可调参数基本都定义在文件顶部常量区,或者构造函数里。把顶部常量改掉,游戏行为立刻变化。一份通用参数清单,可以直接用于贪吃蛇和乒乓球的改造:
| 常量/参数 | 典型值 | 改大的效果 | 改小的效果 |
|---|---|---|---|
| FPS | 60 | 整体变快、操作更灵敏 | 动作变卡顿 |
| SPEED / VELOCITY | 3~5 | 每帧位移变大、更难操控 | 更好操控、节奏变慢 |
| 蛇身 BLOCK_SIZE | 20 | 蛇更粗、碰撞判定更宽松 | 更细、需要更精确走位 |
| BALL_RADIUS | 10 | 球更大、与拍子碰撞更早 | 球变小、容易从边缘漏过 |
| 初始方向向量 | (-1, 0) | 起始方向改变 | 同上 |
改参数时有一个隐藏联动:碰撞范围由rect决定,而rect由图片或色块尺寸决定。只改 BALL_RADIUS 而不同步改生成图片的代码,会出现"画出来变大但碰撞没变大"。正确做法是把半径作为唯一数据源:
BALL_RADIUS = 15 ball_image = pygame.Surface((BALL_RADIUS * 2, BALL_RADIUS * 2)) ball_rect = ball_image.get_rect()所有逻辑读同一个常量,改一处就行。13 个游戏里不少地方把尺寸写死成 (20, 20),改成这种写法顺带消除了魔数,是改造里最值得做的一步。
关卡改造也是从这里切入:给分数设阈值,每过一个阈值就把 SPEED 往上抬。比如在更新函数里写speed = base_speed + score // 500,分数每涨 500,速度就加一档,关卡难度递进不需要重写逻辑,只是把一个常量变成表达式。
4.3 换素材的路径与格式问题
把色块换成 PNG 图片是改造最直观的一步。pygame 加载图片常用两条 API:pygame.image.load(path)和pygame.transform.scale(image, size)。路径容易踩坑的地方在于相对路径和工作目录有关,从 IDE 双击运行和用命令行python main.py运行时,当前目录可能不一致,导致 FileNotFoundError。13 个游戏源码里如果有 assets 目录,通常硬编码了相对路径,建议改成基于__file__拼路径:
import os import pygame BASE_DIR = os.path.dirname(os.path.abspath(__file__)) img_path = os.path.join(BASE_DIR, "assets", "player.png") player_img = pygame.image.load(img_path).convert_alpha()convert_alpha()把图片转成带 alpha 通道的 Surface,透明背景才能正确显示;不加这行,PNG 的透明区域会变成黑色方块。配合pygame.transform.scale可以把任意分辨率素材缩到目标尺寸,比如把 512x512 的敌人图缩成 40x40,保证原有判断逻辑不用动。
提示:不要用中文文件名和带空格的文件名,pygame 的 loader 对跨平台路径的处理并不都友好,换成 player.png 这种命名能省去大部分路径问题。
5. 帧率与打包:pygame 游戏交付前的几件小事
5.1 用内置手段定位掉帧原因
13 个游戏源码跑起来之后,最常被问的是"为什么我的机器上有点卡"。先别怀疑电脑,在游戏循环里加一个帧率计数器,看真实 FPS 和目标差多少。最直接的方式是用clock.get_fps()配合字体渲染:
font = pygame.font.Font(None, 36) fps_text = font.render(f"FPS: {clock.get_fps():.1f}", True, (255, 255, 255)) screen.blit(fps_text, (10, 10))clock.get_fps()返回最近一秒的平均帧率,比手动计数稳定。如果显示值和目标相差很远,瓶颈通常集中在两类代码:密集的pygame.mask.from_surface像素碰撞,以及每帧都在调pygame.image.load加载素材。前者改成初始化时算好 mask 并复用,后者把加载全部移到循环外,通常立竿见影。13 个这种规模的小游戏,把这两点改掉,帧率回到 60 基本没有悬念。
5.2 PyInstaller 打包 pygame 的注意点
游戏做完要发给别人玩,常见做法是 PyInstaller 打包。基础命令是:
pyinstaller --onefile --windowed --name my_game main.py--windowed去掉运行时黑色控制台窗口,--onefile打出单文件,代价是启动时解压到临时目录稍慢。打包后最常见的报错是找不到图片和字体,原因是BASE_DIR指向了临时解压目录。处理方式是运行时优先用sys._MEIPASS定位资源:
if hasattr(sys, "_MEIPASS"): BASE_DIR = sys._MEIPASS else: BASE_DIR = os.path.dirname(os.path.abspath(__file__))配合--add-data "assets;assets"(Windows 下分号,Linux/macOS 下冒号)把素材目录打进包。打包完的 exe 建议在干净机器上试跑一次,pygame 的 SDL 运行库会随包分发,一般不需要对方装 Python;如果对方机器缺显卡驱动,可能直接闪退,先让对方更新驱动,再排查代码。这一节做扎实,13 个游戏源码就能变成 13 个可分发的小作品,整条流程闭环。
本文还有配套的精品资源,点击获取