简介:这是一套汇集二十个经典小游戏的 Python pygame 源码合集,所有游戏均亲测可正常运行,面向 Python 初学者、游戏开发入门者以及课堂教学场景,推荐使用 PyCharm 打开运行。游戏类型十分丰富,涵盖射击达人、动物对决、迷宫、打地鼠、2048、贪吃蛇、扫雷、滑雪、飞翔的小鸟、塔防、坦克大战、吃金币、飞机大战、外星人入侵、吃豆人、守护花朵、连连看、计算器、弹球等多个经典玩法,从休闲益智到动作闯关均有涉及。资源包内共 2000 个文件,压缩后约 165.68MB,除 Python 源码和编译文件外,还包含大量图片、动图、音效、字体、地图以及说明文档、配置文件,且目录按游戏模块组织,便于查找与二次开发。已有 1298 人学习下载,适合用来研究 pygame 的事件循环、碰撞检测、动画更新、计分系统与游戏状态管理等核心机制,上手即可运行并拓展。
1. 20 个 pygame 小游戏源码包:先跑起来,再谈好不好用
拿到这套 pygame 小游戏源码包,我做的第一件事不是看代码,而是把 20 个游戏全部启动一遍。20 个独立目录,亲测能正常跑起来——这个前提比功能介绍值钱,因为 pygame 项目最常见的死法是环境没配对,窗口一闪而过,让人误以为代码是坏的。适合刚学完 Python 基础、想在 pygame 里找手感的新手,也适合要快速落地课程设计的在校生。
包里大多是贪吃蛇、俄罗斯方块、飞机大战这类经典玩法,也夹着五子棋、打砖块等轻量项目。每个游戏独立目录,入口和素材分开,结构高度一致,读懂一个,剩下 19 个就能顺同一套路拆。
下文按我复现的顺序写:搭环境、拆源码、列坑、改造。
2. 环境与启动:Python 版本、虚拟环境与 PyCharm 三件事
这套 20 个 pygame 小游戏能不能跑起来,关键不在代码而在环境。源码包里所有游戏只依赖 pygame 一个第三方库,没有任何需要联网的模块,环境搭对之后,启动就是一条命令的事。我按推荐顺序来:先定 Python 版本,再建虚拟环境,最后找入口脚本。无论你用 PyCharm 还是 vscode,虚拟环境这条路完全一样,下文命令通用。
2.1 先把 Python 版本钉在 3.8 到 3.11
pygame 底层封装的是 SDL 库,SDL 对不同 Python 版本的支持节奏有差异。网上流转的免费 python 源码包里,老项目十有八九是用 Python 3.7 或 3.8 写的,函数命名和代码风格都很老派。用新版本 Python 去跑,多数时候能兼容,但偶尔会遇到pygame.mixer初始化失败或者pygame.surfarray报错这类硬问题,定位起来非常费时间。
我一般会把项目解释器直接选成 Python 3.8 到 3.11 之间的版本,搭配 pygame 2.1 之后的版本。这套 20 个小游戏用的全是标准 API,比如display.set_mode、event.get、image.load、mixer.Sound这些,从 2.1 到 2.5 接口基本没变,兼容面很宽。下面这张表是我常用的版本判断参考:
| Python 版本 | 建议 pygame 版本 | 备注 |
|---|---|---|
| 3.8 – 3.10 | 2.1.x – 2.5.x | 最稳妥,老代码基本不用改 |
| 3.11 | 2.3 以上 | 注意 mixer 声音初始化 |
| 3.12 – 3.13 | 2.5.2 以上或 pygame-ce | 老工程可能需要小改 |
这里有个容易混的点:pygame-ce是社区维护的分支,接口上兼容 pygame,但对新 Python 的支持更积极。如果你电脑上只有 Python 3.13 又不想换版本,装 pygame-ce 是更现实的选择,但注意一个项目里不要同时出现 pygame 和 pygame-ce 的混装痕迹,否则 import 出的模块可能不是你预期的那一套。
如果你还没装 Python,先装 3.10 或 3.11,安装向导里记得勾选 "Add Python to PATH"。装完在系统终端执行python --version确认版本,PATH 没配好是 python 安装教程里最高频的问题,表现就是输入 python 提示不是内部或外部命令。
2.2 PyCharm 中创建虚拟环境并安装 pygame
虚拟环境的作用不只是隔离依赖,更重要的是让排查路径变短。这套源码包下载到本地后,我建议放在一个不带中文路径的目录下,比如D:\projects\pygame_games,再在这个目录里建虚拟环境,避免路径编码问题牵连到素材加载。PyCharm 新建项目时可以直接选 Virtualenv 作为解释器类型;如果项目已经存在,进 File > Settings > Project > Python Interpreter,点齿轮选 Add Interpreter,同样能切到 venv。
解释器切好之后,打开 PyCharm 自带的 Terminal,依次执行:
python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate python -m pip install --upgrade pip pip install pygame==2.5.2 python -c "import pygame; print(pygame.version.ver)"最后一行输出类似2.5.2的版本号,就说明安装成功。这里有三个细节值得较真:第一,activate 只改变当前终端的 PATH,如果你又在 PyCharm 的 Python Packages 面板里手动装了一次 pygame,两个位置可能不一致,运行时以终端导入结果为准;第二,如果源码包根目录有requirements.txt,直接pip install -r requirements.txt更省事,没有就只装 pygame,因为 20 个游戏没有其他第三方依赖;第三,装完验证时不要新开一个系统终端,必须在同一个激活了 venv 的终端里验证,否则很容易误判成安装失败。
如果安装速度很慢,换国内镜像源是常见做法:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pygame==2.5.2。这不会改变包的内容,只是下载通道不一样。
注意:venv 目录不要放在带空格的路径里,某些 pygame 素材加载对路径空格很敏感。
2.3 找到入口脚本并启动第一个游戏
依赖装好之后,先别急着改代码。打开源码包根目录,常见的组织方式是每个游戏一个文件夹,文件夹里放入口脚本和素材子目录。入口脚本最常见的名字是main.py,也有叫run.py、game.py的,个别包会把入口写在start.py里。我的习惯是先把整个目录结构扫一遍,记录每个游戏的入口文件名,后面做统一管理时用得上。
启动第一个游戏,命令很直接:
cd 01_snake # 换成你本机实际存在的游戏目录名 python main.py窗口弹出来、能看到游戏画面,说明环境全部打通。如果弹出后立刻闪退,不要反复双击,先看终端里的报错,十有八九是这三类:找不到素材文件、pygame 初始化失败、入口脚本里直接抛了异常。如果终端提示 "python 不是内部或外部命令",说明解释器没进 PATH,回到 2.1 把 PATH 配上;如果提示目录不存在,先ls或dir看一下真实目录名,我见过资源包里的目录名带了版本号后缀,和 README 写的对不上。
提前打个预防针:窗口出现但画面卡死,大概率是主循环里没有clock.tick,这个到第三章拆源码时会看到具体写法。
另外,跑第一个游戏之前,我习惯看一眼入口脚本末尾有没有if __name__ == "__main__":保护。有这个保护,说明作者考虑过模块化导入,后面改造成统一启动器时几乎不用动结构;没有这个保护,游戏一 import 就会直接开窗口,改造时要手动包一层函数。这个细节在第五章会派上用场。
3. 拆源码:pygame 主循环、事件机制与贪吃蛇的实现
把这 20 个游戏挨个打开会发现,所有代码都围着同一个主循环转。这个循环可以浓缩成四步:初始化窗口和时钟、收取事件、更新游戏状态、绘制画面。几乎每个 pygame 小游戏都是这个套路,区别只在更新和绘制两步里做了什么。
3.1 pygame 项目的基本骨架:初始化、事件、更新、绘制
下面这段是我在很多 pygame 源码里都见过的最小骨架,这 20 个游戏基本都能对上号:
import pygame pygame.init() screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("pygame 小游戏") clock = pygame.time.Clock() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: running = False # 更新游戏状态:位置、碰撞、分数 # 绘制画面 screen.fill((0, 0, 0)) pygame.display.flip() clock.tick(60) pygame.quit()逐个拆开看:pygame.init()负责初始化所有模块,包括显示、声音、字体,少量游戏会在 init 之前单独调pygame.mixer.pre_init设置音频参数;set_mode返回一个 Surface,后续所有绘制都画在这块画布上;event.get()必须放在循环里每帧调用,否则窗口的关闭事件排不上队,点关闭按钮没反应;flip()把画布真正推到屏幕上,不调用它画面就只存在于内存里;clock.tick(60)把帧率锁在 60,同时决定了游戏的整体速度。判断一个 pygame 项目写得规不规范,先看这四个位置就行。
3.2 以贪吃蛇为例:网格坐标、方向状态与碰撞判定
20 个游戏里,最适合从头读的是贪吃蛇。它逻辑简单,但又覆盖了 pygame 游戏核心的三个概念:位置表示、输入处理、碰撞判定。常见实现里,蛇身是一个坐标点列表,方向用一个向量表示,每帧把新头部插入列表头部,没吃到食物就删掉尾部,这就是"移动"的本质。核心移动逻辑大概是下面这样:
snake = [(10, 10), (9, 10), (8, 10)] # 蛇身列表,头部在第一位 direction = (1, 0) # 每帧向右移动一格 def move(snake, direction, food): head = snake[0] new_head = (head[0] + direction[0], head[1] + direction[1]) snake.insert(0, new_head) if new_head == food: return True # 吃到食物,蛇变长,不删尾部 snake.pop() # 没吃到,删尾部,蛇保持原长 return False两个容易被忽略的细节:一是方向向量取值只有 (1,0)、(-1,0)、(0,1)、(0,-1) 四种,按键处理时不能允许反向,否则蛇头直接撞进自己身体;二是吃到食物的判断放在插入之后,蛇头占住食物那一格,视觉上就是咬住食物。碰撞判定更简单,新头部出界或者出现在蛇身列表里,游戏结束。
食物坐标一般用random.randrange在网格里取,生成时还要避开蛇身占用的格子,否则食物刷在蛇身上,玩家根本点不到。分数则是每次move返回 True 时加一分。20 个游戏里但凡带"蛇"字或"贪吃"字样的,核心基本都是这套结构,剩下的只是画面和音效包装不同。
3.3 素材加载与路径问题:为什么在 PyCharm 里能跑、双击就崩
20 个游戏里有不少用了图片和音效,素材加载的稳妥写法是下面这种:
import pygame import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) image = pygame.image.load(os.path.join(BASE_DIR, "assets", "snake_head.png")) sound = pygame.mixer.Sound(os.path.join(BASE_DIR, "assets", "eat.wav"))注意:
pygame.image.load用相对路径时,是相对于进程的工作目录,不是脚本所在目录。
这里的关键就在上面这句提示。你在 PyCharm 里点绿色三角运行,PyCharm 会把工作目录设置成项目根目录,素材相对路径因此成立;但如果你在终端里切到别的目录执行python 01_snake/main.py,相对路径就断了,表现是 FileNotFoundError,或者窗口打开后图片全是黑块、画面不渲染。这也是我一直强调从源码包根目录启动的原因,把工作目录这个变量从一开始钉死。
还有一个连带问题:如果游戏里用pygame.font.Font渲染中文文字,而系统字体路径指向中文字体文件,换到没有对应字体的机器上会变方块。拿到新源码包时,全局搜一遍image.load和mixer.Sound,把这两行的路径逻辑确认清楚,能省掉后面一大半的排错时间。
4. 避坑与排查:pygame 小游戏最常见的 5 个翻车现场
下面 5 个坑,几乎都和代码逻辑无关,而是环境和运行方式的问题。每条按现象、原因、解决三步拆,都是我在跑这套 20 个游戏过程中实际遇到过的。
4.1 黑窗口一闪而过,完全看不到报错
现象:双击入口脚本或直接运行,窗口弹出一瞬间就消失,终端也一起关闭,什么信息都没留下。
原因:两种情况最常见。第一种是脚本执行时抛了异常,Windows 上双击运行时终端随进程一起关闭,报错被吞掉;第二种是代码里写了pygame.quit()或sys.exit()提前触发退出,常见于作者调试后遗留的语句。
解决:不要在资源管理器里双击,改用 PyCharm 的 Run 按钮,异常会打印在 Run 面板里。如果必须双击运行,在入口脚本末尾临时加一行input("按回车退出"),让窗口停在报错现场。看到具体异常之后再决定是改环境还是改代码,盲目重装 pygame 是最浪费时间的第一步。
4.2 ModuleNotFoundError: No module named 'pygame'
现象:终端执行python main.py,提示找不到 pygame 模块,但明明刚装过。
原因:当前终端用的 Python 解释器,不是装 pygame 的那一个。最常见是 activate 失败后还在系统 Python 里执行,或者 PyCharm 项目解释器还是旧的。我见过最隐蔽的是电脑里同时装了 Python 3.8 和 3.10,pip 装到了 3.8,终端默认却用 3.10。
解决:先执行python --version确认解释器路径,再用python -m pip install pygame==2.5.2重装,注意用python -m pip而不是裸pip,这样安装目标和运行目标一定是同一个。装完用python -c "import pygame; print(pygame.version.ver)"验证。PyCharm 用户在 Settings 里检查 Project Interpreter 是否指向 venv。
4.3 游戏能开窗,但声音模块报错或直接没声音
现象:窗口正常,终端报pygame.error: audio device not initialized,或者 Linux 下报 ALSA 相关错误;有的游戏不开声音能跑,但一调用mixer.Sound.play()就崩。
原因:SDL 音频子系统初始化依赖系统声卡驱动,pygame 的 mixer 初始化一直是有点玄学的部分。Windows 下常见老代码用pygame.mixer.pre_init(44100, -16, 2, 512)设置了当前声卡不支持的参数;Linux 下常见于无桌面环境或远程机器没有声卡。
解决:不改源码的前提下,在pygame.init()之前插入一行pygame.mixer.pre_init(44100, -16, 2, 512),很多参数不匹配的问题会在初始化阶段暴露而不是运行时崩溃。如果还是没声音,把pygame.mixer.init()包进 try/except,异常就跳过音频继续跑,这是止损最快的方案,毕竟看小游戏代码时声音不是第一优先级。
4.4 游戏速度像开了倍速,或者卡成幻灯片
现象:蛇跑得飞快,一帧走好几格;反过来,某些动画卡顿,操作明显有延迟。
原因:前者是主循环里没有clock.tick(fps),pygame 默认不限制帧率,循环转多快游戏就多快,帧率能冲到几百;后者是 tick 设置太低,或者循环体里有每帧重复的耗时操作,比如每帧都重新加载图片。
解决:在主循环末尾加clock = pygame.time.Clock()并调用clock.tick(60),把帧率钉在 60。如果速度还是不对,就把 tick 里数字调大调小试试,这是 pygame 调游戏速度最直接的手段。比如蛇原本每帧移动一格,tick(60) 下就是每秒 60 格,想调慢就改成 tick(30)。另外养成一个习惯:所有图片、字体、声音的加载都放到循环外的初始化段,循环里只做 blit 和翻转,帧率会稳定很多。
4.5 图片或音效文件找不到,报 FileNotFoundError
现象:报错指向assets/xxx.png找不到,但打开资源管理器,文件明明就在那里。
原因:工作目录和素材相对路径对不上。这个坑在第三章 3.3 说过,本质是pygame.image.load用相对路径定位文件,而相对路径是相对于进程工作目录,不是脚本所在目录。
解决:优先从源码包根目录运行;更稳的做法是把素材加载改成基于__file__的绝对路径,改完之后目录结构怎么挪都不再触发这个错。我每次拿新源码包,会先全局搜一遍image.load和mixer.Sound,把路径确认完才跑游戏,这一步可以说是最省时间的预防措施。
最后补一个自检命令,换电脑、换解释器之后先跑一遍再碰代码:
python -c "import sys, pygame; print(sys.version); print(pygame.version.ver); pygame.init(); print('pygame init ok')"正常输出是 Python 版本、pygame 版本、pygame init ok三行。拆开看:sys.version打印解释器版本,pygame.version.ver打印库版本,pygame.init()验证底层 SDL 能不能在当前机器完成显示和音频初始化。如果 init 这一步就抛错,说明 SDL 底层资源有问题,按 4.1 的方法看完整报错。这个命令把"解释器对不对、库装没装、底层库能不能初始化"三件事一次验完。
5. 进阶玩法:给 20 个游戏套一个统一启动器
20 个游戏每个单独跑没问题,但逐个点入口脚本很烦。我拿到这套资源后做的第一件事,是写一个 pygame 菜单启动器,把 20 个小游戏收进同一个窗口。改造前先确认两个前提:入口脚本有if __name__ == "__main__":保护,且每个游戏能暴露一个统一的入口函数,比如main()。缺保护的游戏,import 进去就会直接开窗口,必须先包一层函数。
方案是用 importlib 从文件路径加载模块,而不是硬编码 import,因为目录名带01_这种序号前缀,不能直接当模块名用:
import importlib.util import os def load_game(folder): path = os.path.join("games", folder, "main.py") # folder 形如 "01_snake" spec = importlib.util.spec_from_file_location(folder, path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod def launch(folder): game = load_game(folder) if hasattr(game, "main"): game.main() else: print(f"{folder} 没有 main() 入口,跳过")用文件路径加载时,模块的__name__是传入的 folder 名,不是__main__,入口脚本里那层 if 保护会跳过。所以真正要调用的是游戏暴露出来的main()函数,hasattr检查是防止个别游戏入口函数名不统一,直接报错中断菜单。
这里最关键的设计是:菜单主循环和游戏主循环不要嵌套。选中游戏后先退出菜单循环,再进入游戏循环,游戏退出后再回到菜单循环,否则两个窗口交替初始化,SDL 的显示上下文会打架,表现就是画面闪烁甚至黑屏。从那以后,我每拿到一套 pygame 源码包,都强制自己按三条走一遍:先搭环境跑通原版,再拆入口脚本确认模块化程度,最后才动代码做改造。这套流程帮我避开了绝大部分"看起来是代码问题、其实全是环境问题"的坑。希望帮到你。
本文还有配套的精品资源,点击获取