用Python写贪吃蛇:从零实现pygame小游戏(含完整代码)

发布时间:2026/8/30 23:59:23
用Python写贪吃蛇:从零实现pygame小游戏(含完整代码) 学完 Python 基础语法之后很多人都会陷入一个非常尴尬的阶段print会写了for循环也会了但一提到“做点东西练手”就不知道该从哪下手。太简单的脚本没有成就感太复杂的项目又啃不动最后只能在教程和视频之间反复横跳。如果你也处在这个阶段我的建议很直接用 Python 写一个贪吃蛇小游戏。这不是一个简单的“玩具项目”而是一个麻雀虽小、五脏俱全的完整练手场景。它能让你把变量、列表、循环、条件判断、函数、事件处理这些基础知识点真正串联起来还能让你第一次体会到“游戏程序是怎么跑起来的”。更关键的是它足够短一个几百行的文件就能跑完整个闭环窗口创建、画面绘制、玩家输入、逻辑更新、碰撞检测、游戏结束。这篇文章会从零开始带你用pygame写出一个可运行的贪吃蛇。代码会放在文中的完整示例里你直接复制就能用同时我也会把每一段逻辑拆开讲清楚重点说那些新手最容易踩的坑比如“蛇怎么移动”“为什么方向键偶尔会失灵”“撞墙和撞自己怎么判断”。读完这篇文章你得到的不仅是一份源码更是一个能举一反三的“游戏开发最小认知框架”。1. 为什么是贪吃蛇这个练手项目真正的价值先给结论贪吃蛇是门槛最低、覆盖最完整的 2D 游戏入门项目没有之一。很多人会觉得现在网页游戏和手机游戏都这么成熟了写一个文字终端里的贪吃蛇是不是太土了其实恰好相反。贪吃蛇的核心玩法决定了它非常适合用来学习游戏开发的基础机制原因有三个。第一游戏逻辑足够简单但足够完整。一个游戏程序无论多复杂本质上都离不开四个环节接收玩家输入、更新游戏状态、检测碰撞与胜负、渲染画面。贪吃蛇把这四件事全包含了但每一项都不需要很深的算法知识。你用列表就能表示蛇的身体用元组就能表示坐标用几十行代码就能完成碰撞检测——这种“简单但不简陋”的平衡是其他小游戏很难比的。第二它能立刻暴露你对 Python 基础理解的漏洞。写贪吃蛇的时候你会被迫去思考几个平时写脚本根本碰不到的问题列表的头部插入和尾部删除怎么做游戏循环里的“每一帧”到底在更新什么为什么键盘按一下就触发一次移动而不是持续移动这些问题一旦想明白你的 Python 基础就不再是“背知识点”而是真正变成了“能解决问题的能力”。第三后续扩展空间极大。贪吃蛇是一个典型的“容易入门、难以精通”的项目。你可以在基础版本上不断加需求加速、穿墙、障碍物、双人对战、AI 自动寻路甚至用网页版HTMLCSSJavaScript重写一遍。同一套逻辑迁移到其他语言或技术栈时你会惊讶地发现核心思路是通用的只是语法不同而已。所以我的判断很明确如果你只想做一个练手项目来巩固 Python 基础贪吃蛇的性价比是最高的。2. 开发前的思路梳理先想清楚再写代码在写代码之前花十分钟把需求想清楚比直接import pygame然后写循环要重要得多。很多新手最终代码跑不通不是语法不会而是逻辑没有理顺。先给这个简化版贪吃蛇定一个需求清单有一个固定大小的游戏窗口窗口里划分成若干网格。蛇由多个连续的格子组成初始为三节。蛇会自动朝当前方向一格一格移动玩家可以用键盘方向键改变方向。随机在空白网格中生成一个食物。蛇吃到食物后身体长度加一分数加一速度略微提升。蛇撞到窗口边界或撞到自己的身体游戏结束。游戏结束后显示最终得分并支持按空格键重新开始。这个清单每一步看起来都很简单但它隐含了一个非常重要的设计决策用网格坐标系而不是像素坐标系来管理游戏。什么叫网格坐标系想象把窗口划分成一个个正方形小格子蛇的每个身体节都恰好占据一个格子。比如窗口宽度是 600 像素每个格子是 20 像素那么横向就有 30 个格子。蛇头的位置就可以用(x, y)这种网格坐标表示比如(15, 15)代表第 15 列第 15 行。渲染的时候再把网格坐标乘以格子大小换算成像素坐标画到屏幕上。这样设计的好处是逻辑计算完全不用关心像素蛇的“移动”就变成了“从一个网格跳到下一个网格”碰撞检测就变成了“新蛇头坐标是否等于食物坐标”或“蛇头坐标是否已经出现在身体列表里”。这套思路比你在像素级别计算位置要简单十倍。接着考虑数据结构。蛇的身体用什么存最合适的就是 Python 列表。列表中的每个元素是一个(x, y)元组索引 0 是蛇头末尾是蛇尾。移动的时候在头部插入新位置如果没有吃到食物就把尾部pop()掉如果吃到了就保留尾部蛇就变长了。这一套操作正好对应 Python 列表的insert(0, new_head)和pop()两个方法。至于游戏的整体流程可以画成这样一个循环接收输入 - 根据方向移动蛇头 - 判断是否吃到食物 - 判断是否死亡 - 绘制画面 - 等待下一帧这个循环每执行一次就是游戏中的“一帧”。帧率越高蛇移动越快。pygame的Clock.tick()方法就是用来控制这个循环的速度的。最后要给游戏定义两个状态运行中和游戏结束。这两个状态对应两套不同的界面和行为。状态管理是游戏开发里非常核心的概念后面你写任何复杂游戏都会用到现在养成“先定义状态再写逻辑”的习惯会少走很多弯路。3. 环境准备与开发工具开始写代码之前先把运行环境准备好。这一步不复杂但容易出问题所以单独拿出来说。3.1 安装 Python贪吃蛇项目使用的是pygame库它支持 Python 3.x所以电脑上只要安装了 Python 3 就可以。多数教程会建议你装 3.8 及以上版本这个建议对pygame完全够用。如何确认是否已安装 Python打开终端Windows 上是 CMD 或 PowerShellmacOS 上是 Terminal输入python --version如果输出类似Python 3.9.7说明已经安装。如果提示命令找不到需要先安装 Python。安装过程比较简单去 Python 官方网站下载对应操作系统的安装包安装时记得勾选“Add Python to PATH”这个选项。这里有一个常见的坑Windows 上安装完 Python 后命令行里运行python可能会打开 Microsoft Store或者提示找不到命令。这通常是因为安装时没有勾选“Add Python to PATH”或者安装的是 Store 版本。解决方法是重新安装并仔细勾选环境变量选项。3.2 安装 pygamepygame是 Python 生态中最经典的 2D 游戏开发库之一它封装了窗口创建、图形绘制、事件处理、声音播放等底层细节非常适合用来学习游戏逻辑。安装命令只需要一行pip install pygame在部分环境下可能需要用到pip3命令比如 macOS 或 Linuxpip3 install pygame如果你的网络环境不太好可以临时切换到国内镜像源安装pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后验证一下是否成功python -m pygame.examples.aliens如果弹出一个外星人小游戏的窗口说明pygame已经安装成功可以正常使用了。关闭窗口即可退出。3.3 开发工具推荐写这个项目其实不需要复杂的 IDE一个支持 Python 语法高亮的编辑器就够用。常见的组合有两种新手优先VS Code Python 插件。启动快调试方便运行代码只需要按一下右上角的运行按钮。极简方案系统自带记事本 命令行运行。好处是让你更清楚 Python 脚本的运行机制坏处是排错效率低容易因为缩进问题找不到错误位置。我的建议是用 VS Code 或者 PyCharm 都行但重点不在于工具多强大而在于你能在代码里自由加中文注释、随时运行验证。工具用顺手了才能真正把精力花在逻辑上。4. 核心概念pygame 中的几个关键机制在动笔写完整代码之前先花一点时间理解pygame的几个核心概念。这些概念不复杂但如果你直接复制代码很可能因为不理解而改不了任何功能。4.1 窗口与 surfacepygame.display.set_mode((宽, 高))会创建一个窗口并返回一个Surface对象。你可以把它理解成一块画布所有绘制操作都在这块画布上进行。绘制完成之后调用pygame.display.flip()画布内容才会真正显示到窗口上。screen pygame.display.set_mode((600, 600))Surface上能做的操作很多填充颜色、画矩形、画圆、加载图片、显示文字。贪吃蛇里我们只需要两个基础操作screen.fill()清屏以及pygame.draw.rect()画格子。4.2 事件系统游戏程序不能像普通脚本那样顺序执行完就结束。它需要反复处理一个问题玩家有没有按键盘鼠标有没有点击窗口有没有被关闭pygame的事件系统帮你把这些“外部输入”收集到一个队列里。每次循环开头你可以调用pygame.event.get()取出所有待处理事件然后逐个判断类型。比如for event in pygame.event.get(): if event.type pygame.QUIT: run False elif event.type pygame.KEYDOWN: if event.key pygame.K_UP: direction (0, -1)这里KEYDOWN表示“有一个键被按下”key是具体按下的是哪个键。方向键在pygame里有对应的常量K_UP、K_DOWN、K_LEFT、K_RIGHT。4.3 游戏循环与时钟游戏循环是整个游戏的心脏。每一轮循环程序依次做这些事情处理事件、更新游戏状态、绘制画面、控制帧率。帧率由Clock对象控制clock pygame.time.Clock()在循环末尾调用clock.tick(10)意思是“让循环每秒钟最多执行 10 次”对应到贪吃蛇里就是蛇每秒最多移动 10 格。这个数字越大游戏速度越快。你以后想实现“随着分数增加而加速”本质上就是动态调整传给tick()的这个数字。4.4 坐标系统pygame的窗口坐标原点在左上角x 轴向右增大y 轴向下增大。这一点和你在数学课上学到的坐标系正好相反。在贪吃蛇里按上方向键蛇头的 y 坐标应该减 1按下方向键y 坐标应该加 1按左x 坐标减 1按右x 坐标加 1。建议你在自己的笔记里写下这个方向向量映射上: (0, -1) 下: (0, 1) 左: (-1, 0) 右: (1, 0)后面写蛇的移动逻辑时就不用反复想坐标方向了。5. 完整代码实现从零写一个可运行的贪吃蛇下面进入正文的核心部分。我会按照“从骨架到血肉”的顺序分步讲解代码。每一步的代码都很短你可以一边读一边在编辑器里敲最后再把所有代码整合成一个文件。先确定文件命名snake.py。5.1 先搭骨架窗口、时钟、游戏循环所有 pygame 游戏的第一步都一样初始化、创建窗口、创建时钟。import pygame import random import sys # 初始化 pygame pygame.init() # 窗口与网格参数 SCREEN_WIDTH 600 SCREEN_HEIGHT 600 GRID_SIZE 20 GRID_WIDTH SCREEN_WIDTH // GRID_SIZE GRID_HEIGHT SCREEN_HEIGHT // GRID_SIZE # 颜色RGB 元组 BLACK (0, 0, 0) WHITE (255, 255, 255) GREEN (0, 255, 0) RED (255, 0, 0) # 创建窗口和时钟 screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(贪吃蛇 - Python 练手项目) clock pygame.time.Clock()这段代码里GRID_SIZE 20意思是每个格子占 20 像素。600 除以 20横向和纵向各有 30 个格子。贪吃蛇的所有位置计算都会基于GRID_WIDTH和GRID_HEIGHT而不是直接写死 30这样以后改窗口大小时只需要改最上面的两个常量。5.2 方向控制与事件处理在这一步我们定义一个“一局游戏”的函数。这个函数会在游戏结束后返回让外部可以决定是重新开始还是退出。事件处理最需要注意的问题是防止 180 度掉头。如果蛇正在向右走玩家按了一下左方向键蛇头会直接撞到自己脖子上。这事在真实游戏里是必须禁止的。怎样禁止判断新方向是否和当前方向完全相反。两个方向相反意味着它们的分量相加等于 0。比如当前方向是(1, 0)向右新方向如果是(-1, 0)向左那么1 (-1) 0且0 0 0就说明是掉头。def play_one_round(): snake [ (GRID_WIDTH // 2 2, GRID_HEIGHT // 2), (GRID_WIDTH // 2 1, GRID_HEIGHT // 2), (GRID_WIDTH // 2 0, GRID_HEIGHT // 2) ] direction (1, 0) next_direction (1, 0) score 0 speed 10 game_over False 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 next_direction ! (0, 1): next_direction (0, -1) elif event.key pygame.K_DOWN and next_direction ! (0, -1): next_direction (0, 1) elif event.key pygame.K_LEFT and next_direction ! (1, 0): next_direction (-1, 0) elif event.key pygame.K_RIGHT and next_direction ! (-1, 0): next_direction (1, 0)这里有三个细节值得说明。第一为什么用next_direction而不是直接修改direction因为一局游戏里每一帧只能移动一次。如果在事件循环里立刻修改direction而这一帧的移动逻辑还没执行就可能导致一帧里方向被改变多次出现各种奇怪行为。用一个“待生效方向”next_direction在移动逻辑执行时统一赋值给direction可以让每一帧的方向保持稳定。第二next_direction ! (0, 1)这个判断实际上是“你将要按的这个方向和当前待生效方向不是相反的”。但这里其实还缺一个更严格的保护如果玩家在一个非常短的时间内连续按压方向键比如先按上再按左而蛇当前正在向右走那么在第一帧里待生效方向先变成(0, -1)随后又变成(-1, 0)最终蛇头会直接向左形成 180 度掉头。严格来说应该在移动逻辑执行前加一道最终检查把direction和next_direction是否相反判断一下。这部分我会在接下来的代码里加上。这也是你以后和别的开发者讨论“防掉头”问题时最容易拿出来聊的细节。第三pygame.QUIT事件代表用户点了窗口右上角的关闭按钮。游戏里必须处理这个事件否则关窗口时会发现程序卡死或者没有任何响应。5.3 蛇的移动逻辑核心中的核心移动逻辑是贪吃蛇最核心的部分也是最容易写错的部分。先看代码# 防掉头保护 if direction[0] next_direction[0] 0 and direction[1] next_direction[1] 0: next_direction direction direction next_direction head snake[0] new_head (head[0] direction[0], head[1] direction[1])一步一步拆解head snake[0]拿到当前蛇头的网格坐标。new_head (head[0] direction[0], head[1] direction[1])根据方向计算出下一步蛇头会移动到的位置。判断new_head是不是撞墙或撞到自身如果撞到了就直接进入游戏结束状态。这里有一点值得注意蛇移动的时候我们并没有真的让每一节身体都“走一步”而是“在头部插入新位置在尾部删除一个位置”。这种数据结构操作对应了蛇在网格上的整体平移。如果你用循环遍历每一节身体去更新坐标代码会复杂很多也容易出 bug。碰撞检测的代码是if ( new_head[0] 0 or new_head[0] GRID_WIDTH or new_head[1] 0 or new_head[1] GRID_HEIGHT or new_head in snake ): game_over True breaknew_head[0] 0表示蛇头跑出了左边界new_head[0] GRID_WIDTH表示跑出了右边界y 方向的判断同理。最后一句new_head in snake是在检测蛇头是否撞到了自己身体的任何一节。这里的in操作对列表来说是线性查找在格子里只有几十个节点时性能完全够用不需要过度优化。5.4 食物生成与吃食物判定食物是游戏里的“目标”。食物需要在空白格子上随机生成所以生成逻辑要排除蛇身占据的坐标。这里用了一个while循环不断随机生成候选坐标直到找到不在蛇身上的位置。def create_food(snake): while True: candidate ( random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1) ) if candidate not in snake: return candidate这个函数看起来简单但有一个隐藏问题如果蛇已经长到几乎占满整个屏幕while循环可能需要尝试很多次才能找到空白位置。极端情况下蛇占满所有格子函数会无限循环。作为练手项目这个风险可以接受但你应该知道它的存在。进阶做法是维护一份“空闲格子列表”每次从列表里随机取一个这个思路在最佳实践部分会展开。吃到食物的判断非常简单就是蛇头新位置是否等于食物坐标if new_head food: score 1 speed min(speed 1, 20) food create_food(snake) else: snake.pop()为什么吃到了食物就不执行pop()因为蛇吃到了食物长度应该增加。没有pop()蛇的列表长度就多了一节相当于蛇变长了一格。如果没吃到食物身体长度保持不变尾部就要删除整条蛇就像在网格上滑行一样前进了。速度提升的逻辑是speed min(speed 1, 20)意思是每吃一个食物帧率加 1但最多加到 20。帧率越高clock.tick(speed)让每帧间隔越短蛇移动就越快。这就是很多“升级版贪吃蛇”里“分数越高速度越快”的实现原理。5.5 分数显示与游戏结束界面pygame 里显示文字要先创建一个字体对象然后把文字“渲染”成一个 Surface再把它贴到主画布上。def show_text(text, color, y, size50): font pygame.font.Font(None, size) surface font.render(text, True, color) rect surface.get_rect(center(SCREEN_WIDTH // 2, y)) screen.blit(surface, rect)pygame.font.Font(None, size)表示使用系统默认字体。font.render(text, True, color)的第二个参数是抗锯齿开关一般传True。get_rect(center...)可以很方便地把文字居中到指定位置。游戏结束后我们要停留在结束界面而不是立刻退出或立刻重开。这里用一个内层while循环来处理“等待玩家操作”的状态while True: screen.fill(BLACK) show_text(游戏结束, RED, SCREEN_HEIGHT // 2 - 30) show_text(f最终得分: {score}, WHITE, SCREEN_HEIGHT // 2 20, 35) show_text(按空格键重新开始, WHITE, SCREEN_HEIGHT // 2 70, 30) pygame.display.flip() for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() elif event.type pygame.KEYDOWN and event.key pygame.K_SPACE: return这个内层循环的作用是只要玩家不按空格键游戏就一直停留在结束画面按下空格键后函数返回外部就可以启动新的一局游戏。这里有一个很容易犯的错误很多新手把pygame.quit()和sys.exit()放在游戏循环里结果一局结束程序就退出了。更合理的结构是程序的主入口是一个外层循环负责“无限开新局”只有玩家真正关闭窗口时才退出。def main(): while True: play_one_round()play_one_round()结束后回到main()main()再次调用它新的一局就开始了。5.6 完整代码总览把上面的所有片段整合成一个完整的snake.py文件。这个版本可以直接运行也可以作为你继续改造的基础。import pygame import random import sys # 初始化 pygame pygame.init() # 窗口与网格参数 SCREEN_WIDTH 600 SCREEN_HEIGHT 600 GRID_SIZE 20 GRID_WIDTH SCREEN_WIDTH // GRID_SIZE GRID_HEIGHT SCREEN_HEIGHT // GRID_SIZE # 颜色RGB 元组 BLACK (0, 0, 0) WHITE (255, 255, 255) GREEN (0, 255, 0) RED (255, 0, 0) # 创建窗口和时钟 screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(贪吃蛇 - Python 练手项目) clock pygame.time.Clock() def draw_cell(color, x, y): 在网格坐标 (x, y) 处绘制一个格子 pygame.draw.rect( screen, color, (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE) ) def show_text(text, color, y, size50): 在窗口中央附近显示一行文字 font pygame.font.Font(None, size) surface font.render(text, True, color) rect surface.get_rect(center(SCREEN_WIDTH // 2, y)) screen.blit(surface, rect) def create_food(snake): 在空白格子中随机生成食物 while True: candidate ( random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1) ) if candidate not in snake: return candidate def play_one_round(): 运行一局游戏游戏结束后返回 # 蛇的身体列表头部是蛇头后面依次是身体和尾巴 snake [ (GRID_WIDTH // 2 2, GRID_HEIGHT // 2), (GRID_WIDTH // 2 1, GRID_HEIGHT // 2), (GRID_WIDTH // 2 0, GRID_HEIGHT // 2) ] # 当前移动方向与下一帧移动方向 direction (1, 0) # 初始向右 next_direction (1, 0) food create_food(snake) score 0 speed 10 # 初始帧率数值越大移动越快 game_over False while not game_over: # 1. 事件处理 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 next_direction ! (0, 1): next_direction (0, -1) elif event.key pygame.K_DOWN and next_direction ! (0, -1): next_direction (0, 1) elif event.key pygame.K_LEFT and next_direction ! (1, 0): next_direction (-1, 0) elif event.key pygame.K_RIGHT and next_direction ! (-1, 0): next_direction (1, 0) # 2. 防掉头保护direction 和 next_direction 不能完全相反 if direction[0] next_direction[0] 0 and direction[1] next_direction[1] 0: next_direction direction # 3. 根据方向计算新蛇头位置 direction next_direction head snake[0] new_head (head[0] direction[0], head[1] direction[1]) # 4. 碰撞检测撞墙或撞到自己 if ( new_head[0] 0 or new_head[0] GRID_WIDTH or new_head[1] 0 or new_head[1] GRID_HEIGHT or new_head in snake ): game_over True break # 5. 蛇头前进 snake.insert(0, new_head) # 6. 判断是否吃到食物 if new_head food: score 1 speed min(speed 1, 20) food create_food(snake) else: snake.pop() # 7. 绘制画面 screen.fill(BLACK) for x, y in snake: draw_cell(GREEN, x, y) draw_cell(RED, food[0], food[1]) show_text(f得分: {score}, WHITE, 30, 30) pygame.display.flip() clock.tick(speed) # 游戏结束显示结算界面等待玩家选择 while True: screen.fill(BLACK) show_text(游戏结束, RED, SCREEN_HEIGHT // 2 - 30) show_text(f最终得分: {score}, WHITE, SCREEN_HEIGHT // 2 20, 35) show_text(按空格键重新开始, WHITE, SCREEN_HEIGHT // 2 70, 30) pygame.display.flip() for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() elif event.type pygame.KEYDOWN and event.key pygame.K_SPACE: return def main(): # 反复进入新的一局 while True: play_one_round() if __name__ __main__: main()这个文件里draw_cell把网格坐标转换成像素坐标并绘制show_text用来统一显示文字create_food负责生成不落在蛇身上的食物play_one_round封装了一整局游戏的状态和逻辑main函数用无限循环实现“每局结束后重新开始”。整份代码只有 150 行左右但已经完整实现了“重新开始按钮”“分数越高速度越快”这些常见升级版需求的核心逻辑。这也是它适合作为练手项目的原因代码量小但每个部分都值得你反复阅读。6. 如何运行与验证效果代码写完后运行非常简单。在snake.py所在目录打开终端执行python snake.py如果一切正常你会看到一个 600x600 的黑色窗口窗口里有一条绿色的三节蛇以及一个红色的食物。蛇会自动向右移动你可以通过方向键控制它的方向。建议按以下清单验证游戏功能是否正常按上、下、左、右方向键蛇的朝向是否随之改变。蛇吃到食物后身体长度是否增加一节。分数是否加 1蛇的移动速度是否变快。蛇撞到窗口边界后是否弹出“游戏结束”界面。游戏结束后按空格键是否能重新开始。点窗口右上角关闭按钮程序是否正常退出。其中第 4 项验证比较容易忽略。很多新手在测试时只关心“蛇能不能吃食物”却忘了测试“撞墙后的表现”导致代码里碰撞检测一直有 bug 却没有发现。建议把这六项当成一份回归测试清单每次改完代码都手动过一遍。如果运行后窗口一闪而过或者报错信息很快消失可以在终端里重新执行python snake.py错误信息会保留在终端里。先看报错的最后几行通常能直接定位到问题。7. 常见问题与排查思路写贪吃蛇时新手遇到的问题虽然千奇百怪但归纳起来就那么几类。这里列出一份排查表供你对照使用。问题现象可能原因排查方式解决方案运行报错ModuleNotFoundError: No module named pygamepygame 未安装或安装到了其他 Python 环境在终端执行pip show pygame查看是否安装重新执行pip install pygame检查是否使用了正确的 pip 命令窗口一闪而过立即退出程序启动后立即发生异常或者主循环逻辑有误在终端中运行脚本观察报错输出检查代码缩进、括号是否匹配把报错信息复制到搜索引擎查找方向键不响应没有处理KEYDOWN事件或事件判断逻辑写错确认事件循环里是否包含pygame.KEYDOWN分支检查键值常量名称是否正确比如K_UP是K_UP不是K_UP的拼写差异蛇可以 180 度掉头并死亡没有做方向反向检测在事件处理和移动逻辑中都加入“禁止反向”判断使用direction next_direction (0, 0)的防掉头保护吃到食物后蛇没有变长没有正确使用insert和pop检查吃到食物时是否跳过pop()吃到食物只insert不pop没吃到食物insert后要pop食物生成在蛇身上create_food没有排除蛇身坐标查看食物生成函数是否判断candidate not in snake在create_food中加入if candidate not in snake判断蛇移动速度过快或过慢clock.tick(speed)参数设置不合适确认初始 speed 值以及加速逻辑是否合理调整初始speed把加速上限限制在一定范围内窗口标题中文乱码控制系统字体或 pygame 字体渲染兼容问题换成英文标题测试可以先用英文标题等以后引入中文字体资源再替换游戏结束后按空格不能重开结束界面的事件循环里没有处理空格键查看play_one_round结束后的while True循环在事件循环里判断event.key pygame.K_SPACE然后return这张表里最值得你重点关注的是第 4 行和第 5 行。它们分别对应了贪吃蛇两个最容易出 bug 的核心机制方向和增长。把这俩问题想透彻你基本上就掌握了整个项目的精髓。8. 最佳实践与进阶改造方向基础版本能跑通之后建议你不要急着换下一个项目。把代码再读一遍试着按下面的思路改造每改一个功能你对 Python 和游戏开发的理解都会加深一层。8.1 用面向对象重构现在代码是函数式的全局变量比如screen、clock散落各处。你可以把蛇、食物、游戏逻辑分别封装成类。比如定义一个Snake类内部维护body列表、direction、next_direction提供move()、grow()、check_collision()等方法。这样主循环会变得非常简洁snake Snake(...) while True: snake.update() snake.draw()这个改造对代码行数来说可能反而更长了但它能锻炼你划分职责的能力也是从“写脚本”过渡到“写程序”的重要一步。8.2 实现方向队列前面提到事件系统里用next_direction够用但还不算完美。真正的做法是实现一个方向队列玩家每次按键都把方向压入队列每帧移动时从队列里取一个方向。这样可以解决“在极短时间内连续按两个方向键后一个方向被吞掉”的问题。实现方式也不难用collections.deque即可from collections import deque directions deque(maxlen3) # 每帧移动前如果队列非空取出队首方向作为新方向这个知识点是游戏开发中输入系统的一个经典话题很适合作为进阶练习。8.3 增加障碍物和穿墙模式想增加可玩性可以给游戏加两个模式障碍物模式和穿墙模式。障碍物模式在地图上随机生成一些灰色格子蛇头撞到障碍物同样游戏结束。你需要维护一个障碍物坐标集合并在碰撞检测里把它加进去。穿墙模式蛇头碰到边界后从对面出现。实现方式是取模运算new_head ((head[0] direction[0]) % GRID_WIDTH, (head[1] direction[1]) % GRID_HEIGHT)一个改动就能带来完全不同的玩法体验。8.4 加入 AI 自动寻路比障碍物更进阶的玩法是“AI 贪吃蛇”输入一个目标点蛇自动去追。实现思路可以很朴素每次移动前比较蛇头坐标和食物坐标在 x、y 方向上的差值优先朝着差值更大的方向移动。这个算法不会最优但会让蛇“看起来会思考”。如果继续深入研究搜索算法可以尝试 BFS广度优先搜索来寻找最短路径。到这一步你的贪吃蛇项目已经远远超出“练手”范畴变成算法学习的载体了。8.5 打包成 exe 分享代码写完能不能让没装 Python 的朋友也玩上可以用pyinstaller打包pip install pyinstaller pyinstaller -F snake.py打包完成后dist目录下会生成一个独立可执行的snake.exeWindows或可执行文件。这样你就可以把成果发到班级群、公司群里别人双击就能玩。8.6 用其他技术栈重写如果你想换个口味可以试试用 HTMLCSSJavaScript 重写这个游戏。你会发现游戏逻辑完全不用变仍然是网格坐标、蛇身列表、方向向量、碰撞检测。只是渲染层从 pygame 变成了 Canvas事件处理从pygame.event变成了keydown事件。这种跨语言迁移是最好的检验方式如果换一种语言你还能把同样的逻辑写出来说明你是真的理解了而不是背代码。9. 总结贪吃蛇这个项目代码量不大但已经覆盖了游戏开发最核心的闭环创建