🐍 从零开始:用 Python + Pygame 打造经典贪吃蛇小游戏
无论是作为大学计算机专业的毕业设计、课程设计,还是作为编程新手的练手项目,贪吃蛇都是一个绝佳的选择。它的规则简单直观,但却涵盖了图形界面渲染、事件监听、坐标系运算、碰撞检测等游戏开发的核心逻辑。
今天,我将带大家从零开始,使用 Python 的经典图形库 pygame,一步步实现一款带有网格辅助线、支持中文显示且速度适中的贪吃蛇小游戏。

完整代码
- 1:点我下载
- 2:放置文章最后
🛠️ 1. 开发环境准备
首先,确保你的电脑上已经安装了 Python(推荐 3.x 版本)。接着,我们需要安装开发游戏所需的核心库:pygame。
在终端中运行以下命令:
pip install pygame
如果你是在准备毕业设计,建议将项目依赖写入 requirements.txt 文件,方便后续答辩或在其他电脑上运行:
pygame
🎨 2. 游戏核心设计思路
在动手写代码之前,我们需要理清游戏的几个核心要素:
-
- 游戏窗口与坐标系:我们将窗口设定为 800x600 像素,为了让蛇和食物的移动更加规整,我们引入网格系统,每个网格的大小(
BLOCK_SIZE)设为 20x20 像素。
- 游戏窗口与坐标系:我们将窗口设定为 800x600 像素,为了让蛇和食物的移动更加规整,我们引入网格系统,每个网格的大小(
-
- 蛇的身体结构:蛇是由多个方块组成的,在代码中,我们可以用一个列表 (List) 来存储蛇身每一节的坐标
[x, y]。列表的最后一个元素是蛇头。
- 蛇的身体结构:蛇是由多个方块组成的,在代码中,我们可以用一个列表 (List) 来存储蛇身每一节的坐标
-
- 食物生成:食物必须随机生成,但必须对齐到网格上。
-
- 游戏结束条件:
- 撞到四周墙壁(超出窗口边界)。
- 撞到自己的身体(蛇头坐标与蛇身任意一节坐标重合)。
💻 3. 核心代码拆解与实现
3.1 解决中文乱码问题
很多新手在使用 Pygame 时会发现,如果直接在界面上渲染中文字符,会显示成一个个“豆腐块”。这是因为 Pygame 默认的字体不支持中文。
解决方案:使用系统自带的中文字体(例如 Windows 下的黑体 simhei)。
# 初始化字体,使用 simhei (黑体) 以支持中文显示
self.font_style = pygame.font.SysFont("simhei", 25)
self.score_font = pygame.font.SysFont("simhei", 35)
3.2 绘制背景网格 (Grid)
为了增加游戏的高级感并帮助玩家更好地判断距离,我们在背景上绘制一层暗色的辅助网格:
def draw_grid(self):
COLOR_GRID = (40, 40, 40) # 深灰色
# 画垂直线
for x in range(0, WINDOW_WIDTH, BLOCK_SIZE):
pygame.draw.line(self.window, COLOR_GRID, (x, 0), (x, WINDOW_HEIGHT))
# 画水平线
for y in range(0, WINDOW_HEIGHT, BLOCK_SIZE):
pygame.draw.line(self.window, COLOR_GRID, (0, y), (WINDOW_WIDTH, y))
在主循环中,每次使用 self.window.fill(COLOR_BLACK) 清屏后,立即调用 self.draw_grid() 即可。
3.3 蛇的移动与生长逻辑
蛇的移动本质上是:根据当前方向计算出新的蛇头坐标,将其加入蛇身列表,同时删掉蛇尾(列表的第一个元素)。
如果吃到了食物,则不删除蛇尾,这样蛇的长度就增加了 1。
# 1. 更新蛇头坐标
snake_Head = []
snake_Head.append(self.x1)
snake_Head.append(self.y1)
self.snake_List.append(snake_Head)
# 2. 控制蛇身长度(没吃到食物时,移除尾巴)
if len(self.snake_List) > self.Length_of_snake:
del self.snake_List[0]
3.4 碰撞检测与食物生成
为了确保食物完美地出现在网格中,生成食物坐标时必须是 BLOCK_SIZE (20) 的整数倍:
# 随机生成食物坐标并对齐网格
self.foodx = float(random.randrange(0, WINDOW_WIDTH, BLOCK_SIZE))
self.foody = float(random.randrange(0, WINDOW_HEIGHT, BLOCK_SIZE))
碰撞检测非常简单,我们利用 Pygame 的 Rect 对象来判断蛇头和食物是否重叠:
snake_rect = pygame.Rect(self.x1, self.y1, BLOCK_SIZE, BLOCK_SIZE)
food_rect = pygame.Rect(self.foodx, self.foody, BLOCK_SIZE, BLOCK_SIZE)
if snake_rect.colliderect(food_rect):
# 吃掉食物,重新生成
self.foodx = float(random.randrange(0, WINDOW_WIDTH, BLOCK_SIZE))
self.foody = float(random.randrange(0, WINDOW_HEIGHT, BLOCK_SIZE))
# 蛇身长度 + 1
self.Length_of_snake += 1
3.5 游戏速度控制
新手往往会觉得贪吃蛇跑得太快了,一不小心就撞墙。通过调整 Pygame 的时钟刷新率 clock.tick(FPS) 可以轻松控制速度。
我们将速度设置得慢一些,提高可玩性:
SNAKE_SPEED = 4 # 速度越小,移动越慢
# 在主循环的最后调用
self.clock.tick(SNAKE_SPEED)
🎮 4. 运行效果展示
启动游戏后,你会看到:
- 黑色的背景上带有深灰色的规整网格。
- 绿色的贪吃蛇在网格中平滑移动。
- 红色的食物随机散布在网格内。
- 左上角实时显示当前的中文得分(“得分: X”)。
- 如果撞墙或自咬,屏幕中央会用红色中文字体提示:“游戏结束! 按 Q-退出 或 C-重玩”。
(可以在此处插入游戏运行截图)
🎓 5. 毕业设计扩展建议 (进阶方向)
如果你准备将这个项目作为毕业设计,现有的功能可能略显单薄,你可以考虑加入以下功能来丰富你的课题:
-
- 难度分级系统:随着得分的增加,逐渐提高
SNAKE_SPEED,让游戏越来越快。
- 难度分级系统:随着得分的增加,逐渐提高
-
- 障碍物系统:在地图中间随机生成一些不可穿透的“墙壁”,增加游戏难度。
-
- 多种道具:除了普通食物,还可以加入“加速药水”、“减速冰块”、“得分翻倍金币”等。
-
- 最高分记录:将历史最高分保存到本地文件(如 JSON 或 TXT)或 SQLite 数据库中,下次打开游戏时自动读取。
-
- 背景音乐与音效:使用
pygame.mixer加入吃食物的音效和背景 BGM。
- 背景音乐与音效:使用
结语
使用 Python 开发贪吃蛇不仅能锻炼编程逻辑思维,还能让你快速体会到“从代码到可视化产品”的成就感。希望这篇博客能为你的课程设计或毕业设计提供一些灵感!
完整源码我已经整理好,可以直接运行。祝大家 Coding 愉快,顺利毕业! 🎉
import pygame
import random
import sys
import time
# --- Configuration ---
# Window size
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
# Colors (RGB)
COLOR_BLACK = (0, 0, 0)
COLOR_WHITE = (255, 255, 255)
COLOR_RED = (255, 0, 0)
COLOR_GREEN = (0, 255, 0)
COLOR_BLUE = (0, 0, 255)
COLOR_GRID = (40, 40, 40)
# Snake settings
BLOCK_SIZE = 20
SNAKE_SPEED = 4
# Directions
UP = 'UP'
DOWN = 'DOWN'
LEFT = 'LEFT'
RIGHT = 'RIGHT'
class SnakeGame:
def __init__(self):
# Initialize Pygame
pygame.init()
self.window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('贪吃蛇小游戏 (Snake Game)')
self.clock = pygame.time.Clock()
# Fonts
# Use SimHei for Chinese support
self.font_style = pygame.font.SysFont("simhei", 25)
self.score_font = pygame.font.SysFont("simhei", 35)
self.reset_game()
def reset_game(self):
"""Reset game state to initial values."""
self.game_over = False
self.game_close = False
# Initial position (center of screen)
self.x1 = WINDOW_WIDTH / 2
self.y1 = WINDOW_HEIGHT / 2
self.x1_change = 0
self.y1_change = 0
# Initial snake body (list of coordinates)
self.snake_List = []
self.Length_of_snake = 1
# Initial food position
self.foodx = float(random.randrange(0, WINDOW_WIDTH, BLOCK_SIZE))
self.foody = float(random.randrange(0, WINDOW_HEIGHT, BLOCK_SIZE))
# Current direction
self.direction = None
def display_score(self, score):
value = self.score_font.render("得分: " + str(score), True, COLOR_WHITE)
self.window.blit(value, [0, 0])
def draw_grid(self):
for x in range(0, WINDOW_WIDTH, BLOCK_SIZE):
pygame.draw.line(self.window, COLOR_GRID, (x, 0), (x, WINDOW_HEIGHT))
for y in range(0, WINDOW_HEIGHT, BLOCK_SIZE):
pygame.draw.line(self.window, COLOR_GRID, (0, y), (WINDOW_WIDTH, y))
def draw_snake(self, snake_block, snake_list):
for x in snake_list:
pygame.draw.rect(self.window, COLOR_GREEN, [x[0], x[1], snake_block, snake_block])
def message(self, msg, color):
mesg = self.font_style.render(msg, True, color)
# Center the message
text_rect = mesg.get_rect(center=(WINDOW_WIDTH/2, WINDOW_HEIGHT/2))
self.window.blit(mesg, text_rect)
def run(self):
while not self.game_over:
while self.game_close:
self.window.fill(COLOR_BLACK)
self.message("游戏结束! 按 Q-退出 或 C-重玩", COLOR_RED)
self.display_score(self.Length_of_snake - 1)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
self.game_over = True
self.game_close = False
if event.key == pygame.K_c:
self.reset_game()
if event.type == pygame.QUIT:
self.game_over = True
self.game_close = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.game_over = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT and self.direction != RIGHT:
self.x1_change = -BLOCK_SIZE
self.y1_change = 0
self.direction = LEFT
elif event.key == pygame.K_RIGHT and self.direction != LEFT:
self.x1_change = BLOCK_SIZE
self.y1_change = 0
self.direction = RIGHT
elif event.key == pygame.K_UP and self.direction != DOWN:
self.y1_change = -BLOCK_SIZE
self.x1_change = 0
self.direction = UP
elif event.key == pygame.K_DOWN and self.direction != UP:
self.y1_change = BLOCK_SIZE
self.x1_change = 0
self.direction = DOWN
# Check boundaries
if self.x1 >= WINDOW_WIDTH or self.x1 < 0 or self.y1 >= WINDOW_HEIGHT or self.y1 < 0:
self.game_close = True
self.x1 += self.x1_change
self.y1 += self.y1_change
self.window.fill(COLOR_BLACK)
self.draw_grid()
# Draw Food
pygame.draw.rect(self.window, COLOR_RED, [self.foodx, self.foody, BLOCK_SIZE, BLOCK_SIZE])
# Update Snake Body
snake_Head = []
snake_Head.append(self.x1)
snake_Head.append(self.y1)
self.snake_List.append(snake_Head)
if len(self.snake_List) > self.Length_of_snake:
del self.snake_List[0]
# Check self-collision
for x in self.snake_List[:-1]:
if x == snake_Head:
self.game_close = True
self.draw_snake(BLOCK_SIZE, self.snake_List)
self.display_score(self.Length_of_snake - 1)
pygame.display.update()
# Check if food eaten
# Simple collision detection (rect overlap)
# Since we move by BLOCK_SIZE, exact match might be needed if grid aligned,
# but let's use a small tolerance or range overlap logic just in case.
# Here we use simple distance logic or rect collision logic.
# Rect collision is cleaner.
snake_rect = pygame.Rect(self.x1, self.y1, BLOCK_SIZE, BLOCK_SIZE)
food_rect = pygame.Rect(self.foodx, self.foody, BLOCK_SIZE, BLOCK_SIZE)
if snake_rect.colliderect(food_rect):
# Using grid alignment logic for food generation to ensure it aligns with snake movement
self.foodx = float(random.randrange(0, WINDOW_WIDTH, BLOCK_SIZE))
self.foody = float(random.randrange(0, WINDOW_HEIGHT, BLOCK_SIZE))
self.Length_of_snake += 1
self.clock.tick(SNAKE_SPEED)
pygame.quit()
sys.exit()
if __name__ == "__main__":
game = SnakeGame()
game.run()