Bootstrap

从零到EXE-探索用Python开发贪吃蛇单机小游戏全过程及避坑指南

从零到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实战)

通过智能对话完成核心代码迭代:

  1. 生成基础移动逻辑框架
  2. 添加计分系统与碰撞检测
  3. 优化方向控制(关键代码):
# 禁止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 图标获取与处理

  1. 访问Flaticon下载256x256 PNG图标
  2. 使用在线转换工具生成标准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:图标显示异常
✅ 处理步骤:

  1. 验证图标尺寸为256x256
  2. 在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:闪退无报错
✅ 调试方案:

  1. 临时启用控制台:
pyinstaller --noconsole snake.spec → 改为 --console
  1. 添加异常捕获:
try:
    main()
except Exception as e:
    with open('error.log', 'w') as f:
        f.write(str(e))

问题6:杀毒软件误报
✅ 应对策略:

  1. 使用UPX压缩:
exe = EXE(..., upx=True)
  1. 添加数字签名(推荐使用开源工具osslsigncode)

五、总结与进阶建议

5.1 项目收获

  • 掌握PyGame基础开发流程
  • 理解虚拟环境的重要性
  • 熟悉PyInstaller打包机制

5.2 优化方向

  1. 性能提升
# 启用双缓冲
screen = pygame.display.set_mode((800,600), pygame.DOUBLEBUF)
  1. 功能扩展
  • 添加关卡系统
  • 实现网络排行榜

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()
;