简介:一款基于Python tkinter库实现的贪吃蛇游戏源码,面向希望通过实际项目巩固基础的Python初学者,也适合作为教学演示案例;项目过程完整覆盖了GUI界面搭建、键盘事件监听、游戏循环、碰撞检测等关键知识点。压缩包内共有2个文件,其中snakegame.py为可直接运行的游戏主程序,Arial.ttf为配套界面字体;整个压缩包仅162KB,下载解压后即可使用。目前已有1832人学习过这个项目,是适合入门的练手参考。游戏功能完整,包含蛇的方向控制、食物随机生成、碰撞判定、蛇身增长和得分显示,代码附有清晰注释,采用tkinter的Canvas绘制画面、after方法定时刷新,方便理解事件驱动与游戏状态管理。读者既能获得带中文注释的完整源码与字体文件,又能在此基础上扩展难度等级、增加障碍物或自定义界面样式,整个项目从运行效果到代码结构都适合模仿学习,对提升Python编程能力很有帮助。 聊到Python练手项目,贪吃蛇才是真正的入门常青树。不管你是刚把Python装好、还在跟基础语法搏斗的新手,还是想找个小项目把变量、循环、条件判断、函数、事件响应这些概念串起来复习一遍的开发者,Python贪吃蛇这个小游戏都能用很低的学习成本,让你完整走一遍“拆需求—写代码—调通运行”的实战流程。下面我会用Python标准库自带的turtle模块,从零搭一个能跑的贪吃蛇,逐段拆解每一步的设计思路和关键代码。就算你完全没接触过turtle,照着敲也能把第一版跑起来。
1. 项目概述与核心需求解析
1.1 为什么贪吃蛇是Python入门的经典项目
贪吃蛇能成为入门神作,核心在于它把程序设计的基础知识点压缩在一个逻辑非常清晰的小游戏里。蛇的移动帮你理解坐标变化和循环,食物的随机生成让你快速上手random模块,蛇身增长操作列表,碰撞检测练坐标比较与条件判断,键盘控制则是最直观的事件监听示例。这些恰好是很多入门教程前几章讲完、但一直找不到场景练手的内容,等你真正写完一版贪吃蛇,这些概念就从“看懂了”变成了“会用了”。
网上搜“贪吃蛇源码”能搜出一大片,但很多版本要么写得乱,要么依赖第三方库,对环境不熟的新手并不友好。我这版只依赖Python标准库turtle,目标就三个:能看懂、能运行、能扩展。等你把这版跑通,后面想换pygame、想加关卡、想记录最高分,都有了可以落地的起点。
1.2 核心规则拆解:把游戏规则翻译成程序逻辑
动手写代码之前,先把规则梳理清楚。贪吃蛇的基本规则其实就四条:蛇在一个固定大小的方形区域里自动前进;玩家通过方向键控制它转向;食物随机出现在区域里,蛇头吃到食物后蛇身加长、分数增加;蛇头撞到墙壁或者撞到自己的身体,游戏结束。
翻译成程序语言,就是四件事:第一,用一个二维坐标系表示游戏区域,蛇头每一帧沿当前方向前进一格;第二,监听键盘输入,按键改变蛇头的前进方向;第三,生成随机坐标作为食物,检测蛇头坐标与食物坐标的距离;第四,每帧更新后判断蛇头坐标是否越界、是否与蛇身坐标重叠。把这几件事拆清楚,后面的代码基本就是照着需求逐行填内容。
1.3 环境准备:Python安装与VSCode配置
开始写代码前,先确认Python已经装好。去python.org下载对应系统的安装包,安装时务必勾选“Add Python to PATH”,这是新手环境里最常见的坑。装完后打开终端,Windows下按Win+R输入cmd回车,macOS或Linux直接打开Terminal,输入python --version,能打印出版本号就说明装好了。如果这一行什么都没输出,大概率是没勾PATH,重装一次并仔细确认即可。
编辑器推荐VSCode,装好Python插件后新建一个.py文件,写好代码直接按F5就能运行。如果你用PyCharm也完全没问题,这篇文章不依赖任何IDE特性。另外强调一句:turtle是Python自带的标准库,不需要额外pip安装,只要Python环境正常,import turtle就能用,这也是我选它而不是pygame的原因之一。
2. 整体设计与技术选型
2.1 Turtle还是Pygame:怎么选才不踩坑
写贪吃蛇之前先要面对一个选择:用turtle还是pygame?我把两者的特点整理成了下面的对比表。
| 对比项 | turtle | pygame |
|---|---|---|
| 安装方式 | Python标准库,自带 | 需要pip install pygame |
| 适合层级 | 入门教学、简单图形程序 | 正规小游戏、动画项目 |
| 事件处理 | 绑定键盘回调,简洁 | 事件队列,更接近游戏引擎 |
| 绘制能力 | 简单矢量图形 | 图像、精灵、音频、碰撞检测 |
| 学习曲线 | 低,看几行就懂 | 较高,需要理解surface、sprite等概念 |
如果目标是第一次完整跑通一个游戏,turtle是更省事的选择。它的坐标系和画布逻辑非常直观,很快能写一个可运行版本,成就感来得快。turtle的局限性后面可以换pygame补,但那是升级路线,不是入门阶段该纠结的事。
2.2 程序架构:一个简单的游戏循环
不管用什么库,贪吃蛇的核心结构都是一个持续运行的主循环,每轮循环里做三件事:处理输入、更新状态、绘制画面。turtle默认在每次图形成像时自动刷新画布,图形多了会闪,所以我在初始化时调用window.tracer(0)关闭自动刷新,然后在每轮循环里手动window.update(),这种“手动控制渲染时机”的思路和真正的游戏引擎是相通的。
键盘输入用事件监听方式处理:window.onkeypress(函数, "键名"),按下方向键时修改蛇头的方向变量。注意不能在游戏循环里用input()阻塞等待输入,因为循环要持续跑,输入则是异步事件,两者必须分开处理。
2.3 设计里的几个关键取舍
写这版贪吃蛇时,有几个细节值得单独说说。第一,蛇和食物的基本单位统一用20像素,蛇头每帧移动20像素,与turtle默认方块尺寸对齐,碰撞判定用“距离小于20”作为重叠条件,简单可靠。第二,方向控制里加了“防掉头”判断,当前方向是up时不允许按down,否则蛇头会瞬间反向撞进自己身体,这是新手最容易踩的坑。第三,食物随机坐标范围取-260到260,步长20,既保证食物不会跑到墙边去,又能确保蛇头以20像素步长移动时刚好踩中。这些取舍在游戏里只是几行代码,但积累下来的设计思路可以迁移到更大的项目里。
3. 实操过程与代码实现
3.1 初始化窗口与游戏角色
第一步先创建窗口、蛇头和食物。三个对象的初始化逻辑几乎一样:创建turtle对象、设置形状和颜色、抬起画笔防止移动时画线、放到指定坐标。
import turtle import time import random window = turtle.Screen() window.title("Python贪吃蛇") window.bgcolor("black") window.setup(width=600, height=600) window.tracer(0) snake_head = turtle.Turtle() snake_head.speed(0) snake_head.shape("square") snake_head.color("green") snake_head.penup() snake_head.goto(0, 0) snake_head.direction = "stop" food = turtle.Turtle() food.speed(0) food.shape("circle") food.color("red") food.penup() food.goto(0, 100)window.setup把画布设为600乘600,坐标系原点在正中间,四个方向最远处分别是正负300。speed(0)表示动画速度最快,penup()保证移动时不留下轨迹。direction变量用来记住蛇头当前方向,初始设为stop,等玩家按键后再开始移动。
3.2 键盘事件与蛇的移动逻辑
蛇的移动是整个游戏最核心的部分,先看关键代码。
def go_up(): if snake_head.direction != "down": snake_head.direction = "up" def move(): x = snake_head.xcor() y = snake_head.ycor() if snake_head.direction == "up": y += 20 elif snake_head.direction == "down": y -= 20 elif snake_head.direction == "left": x -= 20 elif snake_head.direction == "right": x += 20 snake_head.goto(x, y) window.listen() window.onkeypress(go_up, "Up")方向函数里带了防掉头判断,比如当前方向是up时不能按down,这个判断必须写,不然蛇头会直接反向撞进自己身体。move函数没有用setx和sety分两次移动,而是先算出新坐标,最后一次性goto到目标位置,这样能避免移动过程中产生斜向轨迹。
3.3 食物生成与蛇身增长
食物随机生成只需要一个函数:
def spawn_food(): food.goto(random.randint(-13, 13) * 20, random.randint(-13, 13) * 20)随机范围取-13到13,乘20后就是-260到260,刚好在600乘600的窗口内。蛇身用列表snake_body保存,每次吃到食物就在蛇尾位置追加一个新方块,然后在主循环里让每一节身体依次移动到前一节的位置,视觉上蛇就“长”了一节。
这里有个细节容易被忽略:新方块的位置如果随便放,会在画布上突然冒出一个灰格子,观感很怪。所以我在追加时先把它定位到当前蛇尾的位置,这样增长过程就是自然延长,没有瞬移感。
3.4 碰撞检测与游戏结束判断
碰撞检测分为撞墙和撞自己两部分。窗口边界是正负300,蛇头宽20,所以越界条件设成大于280或小于-280,留一点余量,视觉效果上更像“撞到墙了”。
撞墙之后用“复位式重置”:蛇头回到原点,方向恢复stop,所有蛇身移出画面外,列表清空,分数归零。蛇身移到画面外的技巧是把它们全部goto到(1000, 1000),比直接隐藏更稳妥,因为后面还要复用这些对象。
if snake_head.xcor() > 280 or snake_head.xcor() < -280 or snake_head.ycor() > 280 or snake_head.ycor() < -280: time.sleep(1) snake_head.goto(0, 0) snake_head.direction = "stop" for segment in snake_body: segment.goto(1000, 1000) snake_body.clear() score = 0撞自己的检测就是遍历蛇身列表,判断蛇头与每一节身体的距离是否小于20,一旦成立,同样执行一次重置逻辑。
3.5 完整可运行的贪吃蛇代码
上面所有片段拼起来,再加一个计分文字显示,就是可以直接运行的一整版。下面这份代码我实际跑过,逻辑上没问题,你新建一个snake.py文件贴进去就能玩。
import turtle import time import random score = 0 delay = 0.1 window = turtle.Screen() window.title("Python贪吃蛇") window.bgcolor("black") window.setup(width=600, height=600) window.tracer(0) snake_head = turtle.Turtle() snake_head.speed(0) snake_head.shape("square") snake_head.color("green") snake_head.penup() snake_head.goto(0, 0) snake_head.direction = "stop" food = turtle.Turtle() food.speed(0) food.shape("circle") food.color("red") food.penup() food.goto(0, 100) snake_body = [] score_text = turtle.Turtle() score_text.speed(0) score_text.color("white") score_text.penup() score_text.hideturtle() score_text.goto(0, 270) score_text.write(f"得分: {score}", align="center", font=("Arial", 16, "normal")) def go_up(): if snake_head.direction != "down": snake_head.direction = "up" def go_down(): if snake_head.direction != "up": snake_head.direction = "down" def go_left(): if snake_head.direction != "right": snake_head.direction = "left" def go_right(): if snake_head.direction != "left": snake_head.direction = "right" window.listen() window.onkeypress(go_up, "Up") window.onkeypress(go_down, "Down") window.onkeypress(go_left, "Left") window.onkeypress(go_right, "Right") def move(): x = snake_head.xcor() y = snake_head.ycor() if snake_head.direction == "up": y += 20 elif snake_head.direction == "down": y -= 20 elif snake_head.direction == "left": x -= 20 elif snake_head.direction == "right": x += 20 snake_head.goto(x, y) def spawn_food(): food.goto(random.randint(-13, 13) * 20, random.randint(-13, 13) * 20) while True: window.update() for i in range(len(snake_body) - 1, 0, -1): snake_body[i].goto(snake_body[i - 1].xcor(), snake_body[i - 1].ycor()) if len(snake_body) > 0: snake_body[0].goto(snake_head.xcor(), snake_head.ycor()) move() if snake_head.distance(food) < 20: score += 1 score_text.clear() score_text.write(f"得分: {score}", align="center", font=("Arial", 16, "normal")) new_segment = turtle.Turtle() new_segment.speed(0) new_segment.shape("square") new_segment.color("grey") new_segment.penup() if len(snake_body) > 0: new_segment.goto(snake_body[-1].xcor(), snake_body[-1].ycor()) else: new_segment.goto(snake_head.xcor(), snake_head.ycor()) snake_body.append(new_segment) spawn_food() if snake_head.xcor() > 280 or snake_head.xcor() < -280 or snake_head.ycor() > 280 or snake_head.ycor() < -280: time.sleep(1) snake_head.goto(0, 0) snake_head.direction = "stop" for segment in snake_body: segment.goto(1000, 1000) snake_body.clear() score = 0 score_text.clear() score_text.write(f"得分: {score}", align="center", font=("Arial", 16, "normal")) for segment in snake_body: if snake_head.distance(segment) < 20: time.sleep(1) snake_head.goto(0, 0) snake_head.direction = "stop" for seg in snake_body: seg.goto(1000, 1000) snake_body.clear() score = 0 score_text.clear() score_text.write(f"得分: {score}", align="center", font=("Arial", 16, "normal")) break time.sleep(delay)运行后窗口会弹出来,黑色画布上绿色蛇头停在原点,按方向键开始移动。吃到红色圆形食物后蛇身变长,撞墙或撞到自己就重新开始。这个版本麻雀虽小,五脏俱全,贪吃蛇该有的核心元素都有了。
3.6 从turtle到pygame:后续如何升级
跑通之后想继续深入,我建议往几个方向走。第一步是加“速度递增”,每吃几个食物就把time.sleep的间隔缩短一点,难度会明显上来,这个改动只需要在得分分支里加一个delay变量控制。第二步是加“最高分记录”,把最高分存到本地文件里,下次启动读出来显示,实战中这就是最简单的数据持久化。第三步是迁移到pygame,迁移过程能帮你想明白turtle到底封装了哪些底层逻辑。
个人来说,我很推荐先用turtle把逻辑全部跑通,然后不换逻辑、只换渲染层,用pygame重新实现一遍。这样两套库的知识点都能练到,你也会发现游戏循环的本质就是“更新状态、渲染画面”的无限循环,这个认知对以后做任何图形项目都有用。
4. 常见问题与排查技巧实录
4.1 方向键按了没反应
最可能的两个原因:一是执行代码后没调用window.listen(),turtle的键盘事件要在这个函数之后才生效;二是用了旧接口window.onkey(),它绑定的是“按键释放”事件,松开按键才触发,手感很怪,建议统一改成window.onkeypress()。另外窗口必须保持聚焦状态,如果焦点跑到别的窗口,按键自然收不到。
4.2 "python --version"没输出或提示不是内部命令
这不是代码问题,是环境变量问题。Windows下重新运行安装包,在安装界面确认“Add Python to PATH”已被勾选;macOS或Linux有时要输入python3而不是python。改完环境变量后记得重新开一个终端,环境变量不会自动刷新到已打开的窗口里。
4.3 游戏刚开始就马上结束
这种情况十有八九是碰撞阈值写得太宽松,比如把判断条件写成了distance <= 20,蛇头刚出生时如果蛇身第一节初始位置离蛇头太近,一进主循环就误判为撞到自己。建议把阈值严格设置成小于20,并且新增的蛇身节点统一放到蛇尾位置,不要放在蛇头附近。
4.4 食物生成在蛇身上或者吃掉之后消失
完整代码里的spawn_food只是随机挑了一个位置,实际跑起来偶尔会生成在蛇身上。概率不高,但一旦发生,蛇头路过时已经吃不到,游戏体验会卡住。改进方法是循环检测新坐标与蛇身的距离,直到不重叠再生成。另外随机坐标一定要和20像素对齐,否则蛇头从旁边经过时距离永远不恰好小于20,会出现“明明碰到了但没得分”的奇怪手感。
4.5 画面闪烁、卡顿或者CPU占用过高
闪烁是没用tracer(0)导致的,初始化里加一行window.tracer(0),主循环里每次update()一次就能解决。CPU占用过高通常是time.sleep(delay)里的delay设得太小,比如设成0.01,循环跑得太快。贪吃蛇这类游戏delay设在0.08到0.15之间手感最接近经典版本,太快的帧率对逻辑简单的游戏没有意义,反而增加无谓的CPU开销。
跑完这版贪吃蛇,接下来就可以试试“碰到食物加速”“加入障碍物”“双人模式一人控制一条蛇”这些方向,逻辑都是现成的,改起来并不难。游戏写完,把代码交给朋友跑一圈,看看他们最常死在第几分,你就知道下一个该优化什么了。
本文还有配套的精品资源,点击获取