200字
从零开始:用 Python + Pygame 打造经典贪吃蛇小游戏
2026-03-17
2026-03-17

🐍 从零开始:用 Python + Pygame 打造经典贪吃蛇小游戏

无论是作为大学计算机专业的​毕业设计​、​课程设计​,还是作为编程新手的练手项目,贪吃蛇都是一个绝佳的选择。它的规则简单直观,但却涵盖了图形界面渲染、事件监听、坐标系运算、碰撞检测等游戏开发的核心逻辑。

今天,我将带大家从零开始,使用 Python 的经典图形库 pygame,一步步实现一款带有网格辅助线、支持中文显示且速度适中的贪吃蛇小游戏。

image-ABCl.png


完整代码

🛠️ 1. 开发环境准备

首先,确保你的电脑上已经安装了 Python(推荐 3.x 版本)。接着,我们需要安装开发游戏所需的核心库:pygame

在终端中运行以下命令:

pip install pygame

如果你是在准备毕业设计,建议将项目依赖写入 requirements.txt 文件,方便后续答辩或在其他电脑上运行:

pygame

🎨 2. 游戏核心设计思路

在动手写代码之前,我们需要理清游戏的几个核心要素:

    1. 游戏窗口与坐标系​:我们将窗口设定为 800x600 像素,为了让蛇和食物的移动更加规整,我们引入​网格系统​,每个网格的大小(BLOCK_SIZE)设为 20x20 像素。
    1. 蛇的身体结构​:蛇是由多个方块组成的,在代码中,我们可以用一个列表 (List) 来存储蛇身每一节的坐标 [x, y]。列表的最后一个元素是蛇头。
    1. 食物生成​:食物必须随机生成,但​必须对齐到网格上​。
    1. 游戏结束条件​:
    • 撞到四周墙壁(超出窗口边界)。
    • 撞到自己的身体(蛇头坐标与蛇身任意一节坐标重合)。

💻 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. 毕业设计扩展建议 (进阶方向)

如果你准备将这个项目作为毕业设计,现有的功能可能略显单薄,你可以考虑加入以下功能来丰富你的课题:

    1. 难度分级系统​:随着得分的增加,逐渐提高 SNAKE_SPEED,让游戏越来越快。
    1. 障碍物系统​:在地图中间随机生成一些不可穿透的“墙壁”,增加游戏难度。
    1. 多种道具​:除了普通食物,还可以加入“加速药水”、“减速冰块”、“得分翻倍金币”等。
    1. 最高分记录​:将历史最高分保存到本地文件(如 JSON 或 TXT)或 SQLite 数据库中,下次打开游戏时自动读取。
    1. 背景音乐与音效​:使用 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()
从零开始:用 Python + Pygame 打造经典贪吃蛇小游戏
作者
一晌小贪欢
发表于
2026-03-17
License
CC BY-NC-SA 4.0

评论