尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零实现贪吃蛇:Python+pygame游戏开发与碰撞检测实战

从零实现贪吃蛇:Python+pygame游戏开发与碰撞检测实战 之前在课程设计里让学生练手写小游戏第一个想到的往往就是贪吃蛇。看起来简单但真正要写得顺手、玩得流畅里面涉及窗口渲染、事件监听、碰撞检测、数据结构更新一系列知识点。这篇文章就把整个制作过程拆开讲清楚从环境准备到完整可运行的源码再到常见报错和扩展思路新手可以直接照着做有基础的开发者也能快速拿到一套可以二次改造的代码。本文会用 Python 加上 pygame 库来完成一个经典版贪吃蛇方向键控制移动、吃到食物蛇身变长、撞墙或撞到自己游戏结束、支持重新开始。最终代码是一个单文件 snake.py复制就能运行。1. 为什么用 Python 写贪吃蛇1.1 贪吃蛇游戏的核心机制贪吃蛇的玩法可以拆成几个非常明确的规则蛇在固定尺寸的矩形区域内移动每次移动一格。玩家通过方向键改变蛇的前进方向。蛇头碰到食物时食物消失蛇身长度增加一格并随机生成新食物。蛇头撞到墙壁或者撞到自己的身体时游戏结束。这套规则非常适合用二维坐标来表达。整个游戏场地可以看成一个网格棋盘每一个格子都有唯一的坐标。蛇就是一串连续坐标组成的列表食物则是其中一个坐标点。理解了这一点写代码的思路就非常清晰了我们不是让蛇“画出一条线”而是维护一个坐标列表每次都移动蛇头的位置然后根据是否吃到食物决定要不要保留尾部。1.2 为什么选择 pygame 库pygame 是 Python 生态里最常用的游戏开发库之一它的定位是“2D 游戏开发工具包”。对于贪吃蛇这种界面简单、逻辑清晰的小游戏pygame 比 tkinter 更适合也比直接写控制台程序更接近真实游戏的样子。这里简单对比一下方案优势劣势控制台文本版实现最简单没有实时画面交互体验差tkinter 图形版无需额外安装动画刷新效率低游戏循环写起来别扭pygame适合 2D 游戏事件系统和动画循环完善需要额外安装依赖包对于想入门游戏开发的读者从 pygame 入手是最推荐的路线。它对窗口、键盘事件、图形绘制、帧率控制都有现成的封装能让你把主要精力放在游戏逻辑本身。1.3 学完你能掌握什么通过今天这个案例你能掌握以下内容pygame 窗口的创建与关闭。键盘事件的监听与方向控制。使用列表存储和管理蛇身坐标。碰撞检测的两种常见场景撞墙、撞自己。食物随机生成时的防重叠处理。游戏结束界面与重新开始机制。这些知识点不只适用于贪吃蛇后续做俄罗斯方块、推箱子、打砖块等小游戏时思路都是相通的。2. 环境准备与版本说明在写代码之前先把 Python 环境和 pygame 安装好。很多新手卡在第一步安装环节这里把步骤拆细一点。2.1 检查 Python 环境打开命令行工具Windows 下按Win R输入cmd回车。macOS 下打开“终端”。Linux 下打开终端。输入以下命令python --version如果显示类似Python 3.12.x这样的版本号说明 Python 已经安装好了。如果提示“找不到命令”可以试试python3 --version。本文示例代码基于 Python 3 编写只要是 Python 3.8 以上版本基本都没有问题。如果还没有安装 Python 环境可以按照网上“Python 安装教程”里的步骤从官网下载对应操作系统的安装包记得在安装界面勾选“Add Python to PATH”否则命令行里无法直接使用 python 命令。2.2 安装 pygame 库安装 pygame 非常简单使用 pip 命令即可pip install pygame如果你电脑上同时存在 Python 2 和 Python 3 环境可能需要使用pip3 install pygame安装过程中如果出现网络问题可以换成国内镜像源例如清华源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 验证安装是否成功安装完成后在命令行输入以下命令验证python -m pygame.examples.audiocmd不过这个示例会打开窗口并播放音频如果只是想快速验证库是否能正常导入可以执行python -c import pygame; print(pygame.ver)如果能输出 pygame 的版本号比如2.5.2说明安装成功。需要注意pygame 的版本更新比较频繁但本文用到的基础 API 在 2.0 以上版本中保持稳定不需要纠结具体版本号。2.4 开发工具选择写代码可以用任意文本编辑器新手用 VS Code 或者 PyCharm 都可以。VS Code 更轻量PyCharm 对项目管理和调试更友好。本教程不依赖 IDE 的特殊功能所以打开一个编辑器新建 Python 文件就能开始。3. 贪吃蛇游戏的核心设计思路正式写代码之前先把游戏逻辑拆成几个独立模块这对新手来说尤其重要。不要一上来就贴一大段代码我们先搞清楚每一部分在做什么后面写代码时才能看懂每一行为什么存在。3.1 游戏中的两个核心对象贪吃蛇游戏本质上只有两个核心对象蛇和食物。蛇可以用一个列表表示列表中的每个元素是一个坐标元组(x, y)。例如snake [(100, 100), (80, 100), (60, 100)]这段代码表示蛇头在(100, 100)肚子在它左边总共有三个格子。列表第一个元素永远是蛇头后面的元素依次是蛇身。食物也用一个坐标元组表示。为了保证随机生成的食物不在墙上、不在蛇身上需要在一个合法的坐标范围内生成并检查是否与蛇重叠。3.2 移动的本质是坐标变换蛇的移动很容易理解成“整个身体平移”但在代码实现里更高效的做法是计算出新蛇头的位置。把新蛇头插入到列表最前面。如果没吃到食物就把列表最后一个元素删除。这样就实现了“蛇向前走一格”的效果。方向用方向向量表示direction (CELL_SIZE, 0) # 向右 direction (-CELL_SIZE, 0) # 向左 direction (0, CELL_SIZE) # 向下 direction (0, -CELL_SIZE) # 向上注意这里方向向量直接使用格子大小作为步长这样每次移动都恰好是一格不会出现蛇头停在两个格子中间的情况。3.3 碰撞检测的三种情况碰撞检测是游戏里最容易出错的地方贪吃蛇一共要处理三种碰撞第一种是撞墙。蛇头的 x 坐标小于 0 或大于等于窗口宽度或者 y 坐标超出上下边界都算撞墙。第二种是撞自己。新蛇头坐标出现在蛇身列表里说明蛇头钻进了身体。第三种是吃到食物。新蛇头坐标与食物坐标完全相等此时食物被吃掉蛇变长然后重新生成一个食物。代码实现时撞墙和撞自己可以放在一起判断游戏结束的条件是它们中的任意一个成立if new_head_x 0 or new_head_x WINDOW_WIDTH or new_head_y 0 or new_head_y WINDOW_HEIGHT: game_over True elif new_head in snake: game_over True3.4 游戏循环的作用pygame 程序的运行不是“从上到下执行一次就结束”而是需要一个持续运行的循环。每一轮循环做四件事处理用户输入事件。更新游戏状态比如蛇的位置。绘制画面。控制帧率也就是每秒循环多少次。贪吃蛇的帧率通常设置为 10 到 15也就是说蛇每秒移动 10 到 15 格。帧率太高蛇会跑得太快新手操作不过来帧率太低游戏又显得卡顿。4. 完整源码实战下面进入真正写代码的阶段。我按步骤拆解每一段代码都有说明最后给出完整的单文件版本。4.1 创建项目文件建议新建一个文件夹例如snake_game在里面创建snake.py文件snake_game/ └── snake.py所有代码都写在snake.py中。保持单文件结构有一个好处复制到任意电脑只要装了 Python 和 pygame 就能直接运行非常适合分享和演示。4.2 初始化 pygame 并定义常量先导入需要的库并完成 pygame 初始化import pygame import random import sys pygame.init()导入 random 是为了随机生成食物位置导入 sys 是为了在窗口关闭时安全退出程序。接下来定义游戏常量。常量全部使用大写命名这是 Python 社区推荐的风格表示“这些变量不应该在运行过程中被修改”。WINDOW_WIDTH 640 WINDOW_HEIGHT 480 CELL_SIZE 20 FPS 10WINDOW_WIDTH和WINDOW_HEIGHT游戏窗口大小单位是像素。CELL_SIZE格子边长。蛇身、食物、移动步长都基于这个值。FPS刷新率控制游戏速度。还需要定义颜色。pygame 中使用 RGB 三元组来表示颜色BLACK (0, 0, 0) GREEN (0, 200, 0) DARK_GREEN (0, 150, 0) RED (200, 0, 0) WHITE (255, 255, 255)接下来创建窗口并设置标题screen pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(Python 贪吃蛇小游戏)set_mode返回值是当前窗口对应的 Surface 对象后面所有绘制操作都通过它完成。4.3 字体与得分显示在游戏界面显示得分需要使用 pygame 的字体模块。中文字体在不同系统上存在差异Windows 一般有 simheimacOS 有 pingfangscLinux 可能安装了 notosanscjk。为了让代码跨平台更稳定推荐写一个查找字体的辅助函数def get_font(size): font_names [simhei, microsoftyahei, pingfangsc, notosanscjk] for name in font_names: try: return pygame.font.SysFont(name, size) except Exception: continue return pygame.font.Font(None, size) font get_font(28)这段代码先尝试常见中文字体都不行就退回 pygame 默认字体。这样在 Linux 等环境上即使中文显示不了程序也不会崩溃。4.4 蛇的初始化与移动蛇的初始位置放在窗口中部附近初始长度为 3snake [(100, 100), (80, 100), (60, 100)] direction (CELL_SIZE, 0)direction初始向右。后面每次按键时需要更新这个方向变量。移动逻辑写在游戏循环里核心代码是head_x, head_y snake[0] new_head (head_x direction[0], head_y direction[1]) snake.insert(0, new_head) if new_head food: score 1 food create_food() else: snake.pop()这里的关键是snake.insert(0, new_head)之后如果没吃到食物就立即snake.pop()移除尾部。这样蛇在视觉上保持长度不变地前进。吃到食物时不移除尾部蛇身就会长一格。4.5 食物的随机生成食物不能出现在蛇身上否则会造成视觉重叠。用 while 循环不断生成随机坐标直到找到一个不在蛇上的位置def create_food(): while True: x random.randint(0, (WINDOW_WIDTH - CELL_SIZE) // CELL_SIZE) * CELL_SIZE y random.randint(0, (WINDOW_HEIGHT - CELL_SIZE) // CELL_SIZE) * CELL_SIZE if (x, y) not in snake: return (x, y)random.randint(0, (WINDOW_WIDTH - CELL_SIZE) // CELL_SIZE)生成的是格子索引再乘以CELL_SIZE得到像素坐标。这样可以确保食物对齐到网格上不会出现在两个格子之间。4.6 事件处理与方向控制pygame 会把键盘按键等外部事件放入事件队列游戏循环里通过pygame.event.get()获取事件列表。对于贪吃蛇来说最关键的是防止蛇“掉头反向”。比如当前方向向右玩家按左键蛇头会直接进入自己的身体游戏即刻结束。惯用的解决方案是在按键时检查反向if event.key pygame.K_UP and direction ! (0, CELL_SIZE): direction (0, -CELL_SIZE) elif event.key pygame.K_DOWN and direction ! (0, -CELL_SIZE): direction (0, CELL_SIZE) elif event.key pygame.K_LEFT and direction ! (CELL_SIZE, 0): direction (-CELL_SIZE, 0) elif event.key pygame.K_RIGHT and direction ! (-CELL_SIZE, 0): direction (CELL_SIZE, 0)注意判断条件中不能只判断“方向相反”还要把当前方向与目标方向比较。比如当前向下时按上键就是反向当前向左时按右键就是反向。另外窗口关闭按钮对应pygame.QUIT事件必须有处理逻辑否则点击关闭按钮时程序可能无法正常退出。4.7 游戏画面绘制绘制顺序是先填充背景色再画食物再画蛇最后画得分文本。画蛇时可以通过索引区分蛇头和身体蛇头用深绿色让玩家更容易辨认for i, block in enumerate(snake): color DARK_GREEN if i 0 else GREEN pygame.draw.rect(screen, color, (block[0], block[1], CELL_SIZE, CELL_SIZE))pygame.draw.rect接收四个参数目标 Surface、颜色、矩形区域x、y、宽、高。食物同样用矩形绘制颜色用红色。4.8 游戏结束与重新开始当 game_over 状态为 True 时主循环退出进入单独的结束处理流程。为了支持按 R 键重新开始需要把整个游戏包在一个外层循环中。结构如下外层 while 负责控制“是否运行整个游戏程序”。内层 while 负责控制“单局游戏是否结束”。游戏结束后显示结算画面等待玩家按键。重新开始时需要重置蛇、方向、得分和食物。这部分逻辑推荐封装成函数def reset_game(): global snake, direction, score, food, game_over snake [(100, 100), (80, 100), (60, 100)] direction (CELL_SIZE, 0) score 0 food create_food() game_over False有了这个函数按 R 键时直接调用即可。4.9 完整代码把上面的模块整合成完整的单文件代码复制到snake.py中即可运行import pygame import random import sys pygame.init() WINDOW_WIDTH 640 WINDOW_HEIGHT 480 CELL_SIZE 20 FPS 10 BLACK (0, 0, 0) GREEN (0, 200, 0) DARK_GREEN (0, 150, 0) RED (200, 0, 0) WHITE (255, 255, 255) screen pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(Python 贪吃蛇小游戏) def get_font(size): font_names [simhei, microsoftyahei, pingfangsc, notosanscjk] for name in font_names: try: return pygame.font.SysFont(name, size) except Exception: continue return pygame.font.Font(None, size) font get_font(28) def create_food(): while True: x random.randint(0, (WINDOW_WIDTH - CELL_SIZE) // CELL_SIZE) * CELL_SIZE y random.randint(0, (WINDOW_HEIGHT - CELL_SIZE) // CELL_SIZE) * CELL_SIZE if (x, y) not in snake: return (x, y) def reset_game(): global snake, direction, score, food, game_over snake [(100, 100), (80, 100), (60, 100)] direction (CELL_SIZE, 0) score 0 food create_food() game_over False reset_game() clock pygame.time.Clock() running True while running: while not game_over: for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() elif event.type pygame.KEYDOWN: if event.key pygame.K_UP and direction ! (0, CELL_SIZE): direction (0, -CELL_SIZE) elif event.key pygame.K_DOWN and direction ! (0, -CELL_SIZE): direction (0, CELL_SIZE) elif event.key pygame.K_LEFT and direction ! (CELL_SIZE, 0): direction (-CELL_SIZE, 0) elif event.key pygame.K_RIGHT and direction ! (-CELL_SIZE, 0): direction (CELL_SIZE, 0) head_x, head_y snake[0] new_head (head_x direction[0], head_y direction[1]) if ( new_head[0] 0 or new_head[0] WINDOW_WIDTH or new_head[1] 0 or new_head[1] WINDOW_HEIGHT or new_head in snake ): game_over True break snake.insert(0, new_head) if new_head food: score 1 food create_food() else: snake.pop() screen.fill(BLACK) pygame.draw.rect(screen, RED, (food[0], food[1], CELL_SIZE, CELL_SIZE)) for i, block in enumerate(snake): color DARK_GREEN if i 0 else GREEN pygame.draw.rect(screen, color, (block[0], block[1], CELL_SIZE, CELL_SIZE)) score_text font.render(f得分: {score}, True, WHITE) screen.blit(score_text, (10, 10)) pygame.display.flip() clock.tick(FPS) for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() elif event.type pygame.KEYDOWN: if event.key pygame.K_r: reset_game() elif event.key pygame.K_q: pygame.quit() sys.exit() screen.fill(BLACK) over_text font.render(游戏结束按 R 重新开始按 Q 退出, True, WHITE) final_text font.render(f最终得分: {score}, True, WHITE) screen.blit( over_text, (WINDOW_WIDTH // 2 - over_text.get_width() // 2, WINDOW_HEIGHT // 2 - 40), ) screen.blit( final_text, (WINDOW_WIDTH // 2 - final_text.get_width() // 2, WINDOW_HEIGHT // 2), ) pygame.display.flip() clock.tick(FPS)这份代码已经可以完整运行。逻辑上做了三处关键保护按键反向检测、食物与蛇重叠检查、碰撞后退出内层循环。5. 运行与验证5.1 运行命令确保当前目录是snake_game在命令行执行python snake.py如果你的环境默认是 Python 3但用 python 命令启动失败可以尝试python3 snake.py窗口打开后游戏立刻开始不需要额外点击开始按钮。5.2 操作方式按方向键上、下、左、右控制蛇头方向。蛇吃到红色食物后会变长得分加 1。撞墙或撞自己后画面中央显示最终得分。按 R 重新开始按 Q 退出游戏。5.3 预期效果程序启动后你会看到一个黑色背景的窗口窗口左上角有白色字体显示得分右侧是一条由三个绿色方格组成的蛇红色方块是食物。按下方向键蛇会按你设置的方向移动蛇头碰到食物后食物消失在新的随机位置生成另一个食物蛇身长度增加一节。这个版本的游戏循环为 10 帧每秒移动节奏偏慢适合新手练习。熟悉后可以把 FPS 改成 12 或 15感受变化。6. 常见问题与排查6.1 报错 ModuleNotFoundError: No module named pygame原因pygame 没有安装或者安装在另一个 Python 环境中。排查方式pip list | grep pygame如果没有输出说明没装上。确认当前使用的 Python 和 pip 是同一个版本比如python -m pip install pygame比pip install pygame更可靠。6.2 窗口一闪而过或退出后出现异常原因事件循环中缺少pygame.QUIT的处理或者代码缩进错误导致主循环只执行了一次。建议先检查while running外层循环和while not game_over内层循环是否正确。如果还有问题打开命令行直接运行python snake.py可以看到具体报错堆栈。6.3 蛇无法转向或反向掉头原因方向判断条件写错。方向条件必须同时检查“当前方向”与“目标方向”是否相反。再检查一下代码中direction ! (0, CELL_SIZE)这类比较方向时是否把数值写反。CELL_SIZE是常量方向向量的分量必须和格子边长一致否则蛇移动时会错位。6.4 食物出现在蛇身上原因生成食物时没有检查与蛇重叠。检查你用的随机坐标范围是否正确。坐标必须是CELL_SIZE的整数倍保证格子对齐。另外生成函数中while True有可能因为蛇占满整个屏幕而陷入死循环实际游戏里这种情况几乎不会出现但可以作为扩展点处理。6.5 中文字体显示异常或程序报错原因不同操作系统内置字体不同pygame.font.SysFont在找不到字体时行为不稳定。代码中已经通过get_font函数回退到默认字体。如果中文仍然显示为方块可以临时把提示文字改成英文或者手动指定一个你系统上存在的字体文件路径。6.6 问题排查清单问题现象常见原因解决思路启动提示缺少 pygame依赖未安装执行python -m pip install pygame窗口打开后马上关闭事件循环未正确建立检查 while 循环和 QUIT 事件处理蛇只能走一格就停止循环内没有调用 clock.tick在循环末尾加上clock.tick(FPS)蛇方向不受控制事件监听代码不在内层循环中把 KEYDOWN 处理放到游戏运行循环内部得分不增加食物碰撞判断使用了错误坐标确保 new_head 与 food 是同一个坐标系食物生成位置不对坐标没有对齐网格坐标使用CELL_SIZE的整数倍7. 扩展方向与工程建议到这里你已经拥有一个可以运行的贪吃蛇游戏了。但作为开发练习最好别止步于“能跑”。以下扩展方向由易到难你可以根据自己的水平选择。7.1 功能扩展建议第一档增加难度曲线。让分数每增加 5 分FPS 加 1蛇移动越来越快。实现时可以在clock.tick(FPS score // 5)中动态调整。第二档增加墙体障碍。在地图中随机生成若干不可穿越的障碍物让蛇绕过障碍移动。这一改动需要把障碍物坐标也纳入碰撞检测。第三档增加游戏开始界面和暂停功能。按空格键暂停再按一次继续。暂停时要冻结时钟和移动逻辑但不退出窗口。第四档增加最高分记录。用文件或 SQLite 保存历史最高分每次游戏结束后读取并比较。第五档双人模式。一个玩家用方向键控制另一个用 WASD 控制选最长的蛇作为获胜者。这个改动涉及事件监听合并和独立蛇对象适合作为进阶练习。7.2 代码结构优化当前单文件版本适合入门分享但如果你想把它当成一个正经项目来维护建议拆分成多文件结构snake_game/ ├── main.py ├── settings.py ├── snake.py ├── food.py └── utils.pysettings.py放所有常量和颜色。snake.py定义 Snake 类负责移动、碰撞、绘制。food.py定义 Food 类负责随机生成和绘制。utils.py放字体查找等公共函数。main.py负责主循环和游戏状态切换。这种做法方便后续增加功能时不影响已有代码也更容易写单元测试。7.3 将游戏打包成独立可执行程序如果你想把这个小游戏分享给没有安装 Python 的朋友可以使用 PyInstaller 打包pip install pyinstaller pyinstaller -F -w snake.py-F表示打包成单个 exe 文件。-w表示运行时不显示命令行窗口。打包产物会出现在dist目录下。需要提醒的是PyInstaller 打包后的文件体积通常比较大这是正常现象因为它把 Python 解释器和依赖库都打进去了。7.4 背后的学习路径做完贪吃蛇之后你的下一步可以这样安排学习 pygame 的精灵类和碰撞组理解游戏对象的生命周期管理。学习用time模块或事件定时器控制游戏节奏而不是单纯依赖 FPS。尝试实现俄罗斯方块进一步理解“方块旋转”“行消除”等更复杂的逻辑。学习面向对象重构把游戏中的蛇、食物、场景分别建模。如果你在运行代码过程中遇到和文中不同的报错建议先读一下报错信息里的“最后一行”再定位对应的代码行。Python 的报错信息通常已经告诉了你具体是哪个文件哪一行出了问题这也是调试能力提升最快的方式。本文字体处理、方向判断和碰撞检测这三块代码是新手最容易忽略但实际最容易出问题的地方。运行前如果哪一步卡住可以回到对应的章节核对一遍。代码写完之后多玩几局感受一下帧率对操作手感的影响你就能明白游戏开发里“手感”到底是怎么通过参数调出来的。
返回列表