从零到EXE-探索用Python开发贪吃蛇单机小游戏全过程及避坑指南
前言:一个初学者的探索之旅
作为Python游戏开发的新手,在完成贪吃蛇游戏开发并打包成可执行文件的过程中,经历了从环境搭建到打包发布的完整闭环。本文将系统性地呈现开发全流程,并重点分享7个关键踩坑点及解决方案。
一、开发环境搭建
1.1 基础环境配置
- 操作系统:Windows 10 教育版 22H2
- Python环境:使用Miniconda创建3.12虚拟环境
conda create -n snake_env python=3.12.9 conda activate snake_env
- 核心工具:
- VS Code 1.98.0(安装Python扩展包)
- PyInstaller 6.12.0(打包工具)
- Pillow 11.1.1(图像处理库)
1.2 依赖安装
# 游戏开发核心库
pip install pygame==2.15.1
# 图标处理依赖(解决PIL模块缺失问题)
pip install pillow==11.1.1
1.3 项目结构
project/
│
├── snake.py # 主程序文件
├── snake.ico # 程序图标
├── pack.spec # PyInstaller打包配置
│
├── resources/ # 资源文件
│ └── sound.wav # 游戏音效
│
└── dist/ # 打包输出目录
└── snake.exe # 生成的可执行文件
三、实战全记录:从代码到可执行文件
3.1 AI辅助开发(DeepSeek实战)
通过智能对话完成核心代码迭代:
- 生成基础移动逻辑框架
- 添加计分系统与碰撞检测
- 优化方向控制(关键代码):
# 禁止180度转向
if (event.key == pygame.K_UP and current_dir != "DOWN") or
(event.key == pygame.K_DOWN and current_dir != "UP"):
direction = new_dir
完整py代码见文章最后
3.2 生成SPEC配置文件
pyi-makespec --onefile --noconsole --icon=snake.ico snake.py
修改生成的spec文件:
# ---------- 基础配置 ----------
block_cipher = None # 必须位于文件开头:ml-citation{ref="2,3" data="citationList"}
# ---------- Analysis模块 ----------
a = Analysis(
['snake.py'], # 主程序路径
binaries=[],
datas=[],
hiddenimports=['turtle'], # 显式声明隐藏依赖
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
# ---------- PYZ模块定义(必须大写) ----------
pyz = PYZ( # 变量名必须为pyz:ml-citation{ref="3" data="citationList"}
a.pure,
a.zipped_data,
cipher=block_cipher
)
# ---------- EXE配置 ----------
exe = EXE(
pyz, # 此处引用上方定义的模块
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[], # 空列表占位符
name='SnakeGame',
debug=False,
bootloader_ignore_signals=False,
upx=True,
console=False,
icon='snake.ico',
icon_ignore_ext=False,
onefile=True # 关键参数控制单文件模式:ml-citation{ref="1,2" data="citationList"}
)
3.3 图标获取与处理
- 访问Flaticon下载256x256 PNG图标
- 使用在线转换工具生成标准ICO格式
3.4 打包执行全流程
# 激活虚拟环境
conda activate snake_game
# 执行打包命令
pyinstaller snake.spec
# 生成文件路径
dist/snake.exe
3.5 成果验证
- 双击exe文件无黑框启动
- 游戏音效与图标正常加载
- 任务管理器显示"SnakeGame"进程
四、新手必看:6大避坑指南
4.1 环境配置类
问题1:ModuleNotFoundError: No module named ‘PIL’
✅ 解决方案:
pip uninstall PIL # 如存在旧版本
pip install pillow
问题2:打包后文件体积过大(>500MB)
✅ 优化方案:
conda create --name slim_env --clone base
conda install --name slim_env python=3.12 --no-deps
4.2 打包配置类
问题3:图标显示异常
✅ 处理步骤:
- 验证图标尺寸为256x256
- 在spec文件中添加:
exe = EXE(..., icon='snake.ico', icon_ignore_ext=False)
问题4:资源文件丢失
✅ 正确引用方式:
# 获取资源绝对路径
base_path = sys._MEIPASS if hasattr(sys, '_MEIPASS') else os.path.abspath(".")
sound_path = os.path.join(base_path, 'sounds/bgm.wav')
4.3 运行时异常
问题5:闪退无报错
✅ 调试方案:
- 临时启用控制台:
pyinstaller --noconsole snake.spec → 改为 --console
- 添加异常捕获:
try:
main()
except Exception as e:
with open('error.log', 'w') as f:
f.write(str(e))
问题6:杀毒软件误报
✅ 应对策略:
- 使用UPX压缩:
exe = EXE(..., upx=True)
- 添加数字签名(推荐使用开源工具osslsigncode)
五、总结与进阶建议
5.1 项目收获
- 掌握PyGame基础开发流程
- 理解虚拟环境的重要性
- 熟悉PyInstaller打包机制
5.2 优化方向
- 性能提升:
# 启用双缓冲
screen = pygame.display.set_mode((800,600), pygame.DOUBLEBUF)
- 功能扩展:
- 添加关卡系统
- 实现网络排行榜
5.3 学习资源推荐
开发心得:保持环境纯净、规范资源路径、善用AI辅助,是新手高效开发的三法宝!🚀
import turtle
import random
# ---------- 全局状态 ----------
paused = False
game_active = True
score = 0
direction = "right"
new_direction = direction
move_speed = 200 # 新增速度控制变量(初始200ms)
snake = [] # 蛇身列表
head = None # 独立声明蛇头
# ---------- 窗口设置 ----------
screen = turtle.Screen()
screen.title("贪吃蛇 v2.0")
screen.bgcolor("black")
screen.setup(width=600, height=600)
screen.cv._rootwindow.resizable(False, False)
screen.tracer(0)
# ---------- 初始化函数 ----------
def init_snake():
global head, snake
# 清理旧蛇身
for seg in snake:
seg.hideturtle()
snake.clear()
# 创建新蛇头
head = turtle.Turtle()
head.shape("square")
head.color("green")
head.penup()
head.goto(0, 0)
snake.append(head)
# 添加初始身体
for i in range(1, 3):
segment = turtle.Turtle()
segment.shape("square")
segment.color("green")
segment.penup()
segment.goto(-20 * i, 0)
snake.append(segment)
# ---------- 食物生成 ----------
food = turtle.Turtle()
food.shape("circle")
food.color("red")
food.penup()
food.hideturtle()
def create_food():
while True:
x = random.randint(-14, 14) * 20
y = random.randint(-14, 14) * 20
if all(seg.distance(x, y) > 20 for seg in snake):
food.goto(x, y)
food.showturtle()
return
# ---------- 计分板 ----------
score_pen = turtle.Turtle()
score_pen.speed(0)
score_pen.color("white")
score_pen.penup()
score_pen.hideturtle()
score_pen.goto(0, 260)
def update_score():
score_pen.clear()
status = "PAUSED | Score: {} | Speed: {}".format(score, move_speed) if paused else "Score: {} | Speed: {}".format(score, move_speed)
score_pen.write(status, align="center", font=("Arial", 16, "bold"))
# ---------- 方向控制 ----------
def go_up():
global new_direction
if direction != "down" and not paused and game_active:
new_direction = "up"
def go_down():
global new_direction
if direction != "up" and not paused and game_active:
new_direction = "down"
def go_left():
global new_direction
if direction != "right" and not paused and game_active:
new_direction = "left"
def go_right():
global new_direction
if direction != "left" and not paused and game_active:
new_direction = "right"
# ---------- 游戏控制 ----------
def toggle_pause():
global paused
if game_active:
paused = not paused
update_score()
def restart_game():
global game_active, score, direction, new_direction, move_speed
# 重置游戏状态
game_active = True
score = 0
direction = "right"
new_direction = direction
move_speed = 200
# 重新初始化
init_snake()
create_food()
update_score()
# 重新启动游戏循环
game_loop()
# ---------- 速度控制 ----------
def speed_up():
global move_speed
if move_speed > 50:
move_speed -= 50
update_score()
def speed_down():
global move_speed
if move_speed < 500:
move_speed += 50
update_score()
# ---------- 按键绑定 ----------
screen.listen()
screen.onkeypress(go_up, "Up")
screen.onkeypress(go_down, "Down")
screen.onkeypress(go_left, "Left")
screen.onkeypress(go_right, "Right")
screen.onkeypress(toggle_pause, "space")
screen.onkeypress(speed_up, "F1") # F1加速
screen.onkeypress(speed_down, "F2") # F2减速
screen.onkeypress(restart_game, "F5") # F5重启
# ---------- 游戏逻辑 ----------
def game_loop():
global direction, new_direction, score, game_active
if game_active and not paused:
# 更新方向
direction = new_direction
# 移动身体
for i in range(len(snake)-1, 0, -1):
snake[i].goto(snake[i-1].xcor(), snake[i-1].ycor())
# 移动头部
if direction == "up":
head.sety(head.ycor() + 20)
elif direction == "down":
head.sety(head.ycor() - 20)
elif direction == "left":
head.setx(head.xcor() - 20)
elif direction == "right":
head.setx(head.xcor() + 20)
# 边界检测
if not (-290 <= head.xcor() <= 290 and -290 <= head.ycor() <= 290):
game_over()
return
# 自身碰撞检测
for segment in snake[1:]:
if head.distance(segment) < 15:
game_over()
return
# 吃食物检测
if head.distance(food) < 20:
food.hideturtle()
create_food()
add_segment()
score += 10
update_score()
if game_active:
screen.ontimer(game_loop, move_speed) # 动态速度控制
screen.update()
def add_segment():
new_segment = turtle.Turtle()
new_segment.shape("square")
new_segment.color("green")
new_segment.penup()
new_segment.goto(snake[-1].xcor(), snake[-1].ycor())
snake.append(new_segment)
def game_over():
global game_active
game_active = False
# 显示结束信息
over = turtle.Turtle()
over.hideturtle()
over.color("red")
over.write("Game Over! Final Score: {}\nPress F5 to restart".format(score),
align="center",
font=("Arial", 16, "bold"))
screen.update()
# ---------- 启动游戏 ----------
init_snake()
create_food()
update_score()
game_loop()
screen.mainloop()