使用python制作一个贪吃蛇游戏,并为每一句添加注释方便学习
今天来设计一个贪吃蛇的经典小游戏。
先介绍下核心代码功能(源代码请往最后面拉):
游戏功能 :
- 四个难度等级 :简单(8 FPS)、中等(12 FPS)、困难(18 FPS)、专家(25 FPS)
- 美观的视觉效果 :蛇头蓝色渐变带眼睛、蛇身绿色渐变、食物红色高光效果
- 完整的控制系统 :方向键移动、空格暂停、R键重启、ESC退出
- 实时信息显示 :分数、难度、蛇身长度
- 防反向移动 :避免蛇撞到自己
下面是对代码的解析包含:
编程思想解析:
面向对象设计原理 枚举类的使用和优势
游戏循环的核心概念 坐标系统设计思路
核心算法讲解:
蛇的移动算法(位置计算、碰撞检测)
食物生成算法(随机位置、避免重叠)
渐变绘制算法(线性插值、颜色计算)
难度系统设计(帧率控制)
编程技巧总结:
Python列表操作技巧
事件驱动编程模式
图形绘制和坐标转换
代码组织和模块化设计
贪吃蛇游戏代码解析
一、整体架构设计
1. 面向对象编程思想
- 封装性:游戏功能集中在SnakeGame类中
- 模块化:独立方法对应独立功能(如移动、绘制、输入处理)
- 数据隐藏:游戏状态(蛇位置、分数等)作为类属性,避免全局变量
2. 枚举类的使用
class Difficulty(Enum):
EASY = 1; MEDIUM = 2; HARD = 3; EXPERT = 4
class Direction(Enum):
UP = (0, -1); DOWN = (0, 1)
LEFT = (-1, 0); RIGHT = (1, 0)
- 提升代码可读性,避免 "魔法数字"
- Direction直接存储移动向量,简化位置计算
二、核心功能模块
1. 游戏初始化(__init__方法)
def __init__(self):
pygame.init()
self.WINDOW_WIDTH = 800; self.WINDOW_HEIGHT = 600
self.GRID_SIZE = 20 # 网格尺寸(像素)
# 计算网格数量(逻辑坐标范围)
self.GRID_WIDTH = self.WINDOW_WIDTH // self.GRID_SIZE
self.GRID_HEIGHT = self.WINDOW_HEIGHT // self.GRID_SIZE
- 初始化游戏环境(窗口、网格参数)
- 定义常量(尺寸、颜色),便于统一修改
2. 游戏重置(reset_game方法)
def reset_game(self):
start_x = self.GRID_WIDTH // 2 # 屏幕中央
start_y = self.GRID_HEIGHT // 2
self.snake = [ # 蛇身初始位置(列表存储,0为蛇头)
(start_x, start_y),
(start_x - 1, start_y),
(start_x - 2, start_y)
]
self.direction = Direction.RIGHT # 初始方向
self.score = 0; self.game_over = False
self.generate_food() # 生成初始食物
- 蛇初始位置居中,长度为 3(平衡开局难度)
- 重置分数和游戏状态,为新局做准备
3. 食物生成(generate_food方法)
def generate_food(self):
while True:
# 随机生成网格内坐标
food_x = random.randint(0, self.GRID_WIDTH - 1)
food_y = random.randint(0, self.GRID_HEIGHT - 1)
# 确保食物不在蛇身上
if (food_x, food_y) not in self.snake:
self.food = (food_x, food_y)
break
- 无限循环保证食物位置合法性(不与蛇身重叠)
- 随机生成增强游戏随机性
4. 输入处理(handle_input方法)
def handle_input(self):
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
# 防止反向移动(如向上时不能直接向下)
if event.key == pygame.K_UP and self.direction != Direction.DOWN:
self.direction = Direction.UP
elif event.key == pygame.K_DOWN and self.direction != Direction.UP:
self.direction = Direction.DOWN
# 左右方向同理...
- 事件驱动:响应键盘输入
- 逻辑限制:避免蛇头直接撞向自身
5. 蛇移动更新(update_snake方法)
def update_snake(self):
head_x, head_y = self.snake[0] # 当前蛇头位置
dx, dy = self.direction.value # 移动向量(从Direction获取)
new_head = (head_x + dx, head_y + dy) # 新蛇头位置
# 碰撞检测(边界或自身)
if (new_head[0] < 0 or new_head[0] >= self.GRID_WIDTH or
new_head[1] < 0 or new_head[1] >= self.GRID_HEIGHT or
new_head in self.snake):
self.game_over = True
return
# 更新蛇身
self.snake.insert(0, new_head) # 添加新蛇头
if new_head == self.food: # 吃到食物
self.score += 10
self.generate_food() # 重新生成食物
else:
self.snake.pop() # 没吃到则移除尾部(保持长度)
- 核心逻辑:新蛇头 = 旧蛇头 + 方向向量
- 碰撞检测:边界(超出网格)或自碰撞(新蛇头在蛇身中)
- 长度控制:吃到食物则增长(只加头),否则移动(加头去尾)
6. 绘制功能(示例:draw_snake)
def draw_snake(self):
for i, (x, y) in enumerate(self.snake):
# 逻辑坐标转屏幕坐标(网格×尺寸)
rect = pygame.Rect(x*self.GRID_SIZE, y*self.GRID_SIZE,
self.GRID_SIZE-1, self.GRID_SIZE-1)
if i == 0: # 蛇头特殊绘制(蓝色+眼睛)
pygame.draw.rect(self.screen, (0, 0, 255), rect)
# 绘制眼睛(偏移量调整位置)
pygame.draw.circle(self.screen, (255,255,255),
(x*self.GRID_SIZE+5, y*self.GRID_SIZE+5), 3)
else: # 蛇身渐变(越后越暗)
color = (0, 255 - i*10, 0) # 绿色渐变
pygame.draw.rect(self.screen, color, rect)
- 坐标转换:逻辑坐标(网格索引)→ 屏幕坐标(像素)
- 视觉设计:蛇头 / 蛇身差异化,渐变增强立体感
三、游戏循环设计
def run(self):
while True:
# 1. 处理输入(返回是否退出游戏)
if not self.handle_input():
break
# 2. 更新游戏状态(蛇移动、碰撞检测)
if not self.game_over:
self.update_snake()
# 3. 绘制画面(蛇、食物、分数等)
self.draw()
# 4. 控制帧率(根据难度调整速度)
self.clock.tick(self.get_speed())
- 核心流程:输入→更新→渲染→控速(循环往复)
- 帧率控制:通过clock.tick()保证游戏速度稳定
四、难度系统设计
def get_speed(self):
speed_map = { # 难度→帧率映射(FPS越高速度越快)
Difficulty.EASY: 8,
Difficulty.MEDIUM: 12,
Difficulty.HARD: 18,
Difficulty.EXPERT: 25
}
return speed_map[self.difficulty]
- 通过帧率控制难度:高难度对应更高移动速度
- 线性递增设计,适应不同玩家水平
五、编程最佳实践
- 常量集中定义:尺寸、颜色等参数统一管理,便于修改
- 单一职责原则:每个方法只做一件事(如generate_food仅生成食物)
- 逻辑与渲染分离:先计算状态(更新蛇位置),再绘制(与屏幕交互)
- 防御性编程:碰撞检测、输入合法性判断,避免程序异常
六、学习要点总结
- 游戏开发核心:游戏循环、状态管理、碰撞检测、坐标转换
- Python 技巧:枚举类、列表操作(insert/pop)、字典映射、面向对象
- 图形编程基础:像素坐标、颜色模型(RGB)、事件驱动
- 设计思想:模块化、可扩展性(如新增难度只需改speed_map)
源代码:
import pygame
import random
import sys
from enum import Enum
# 游戏难度枚举类 - 定义不同的难度等级
class Difficulty(Enum):
EASY = 1 # 简单:速度慢
MEDIUM = 2 # 中等:中等速度
HARD = 3 # 困难:速度快
EXPERT = 4 # 专家:非常快
# 方向枚举类 - 定义蛇的移动方向
class Direction(Enum):
UP = (0, -1) # 向上移动
DOWN = (0, 1) # 向下移动
LEFT = (-1, 0) # 向左移动
RIGHT = (1, 0) # 向右移动
class SnakeGame:
def __init__(self):
"""初始化游戏 - 设置游戏的基本参数和pygame"""
# 初始化pygame模块
pygame.init()
# 游戏窗口尺寸设置
self.WINDOW_WIDTH = 800 # 窗口宽度
self.WINDOW_HEIGHT = 600 # 窗口高度
self.GRID_SIZE = 20 # 网格大小(每个方块的像素大小)
# 计算游戏区域的网格数量
self.GRID_WIDTH = self.WINDOW_WIDTH // self.GRID_SIZE # 水平方向网格数
self.GRID_HEIGHT = self.WINDOW_HEIGHT // self.GRID_SIZE # 垂直方向网格数
# 颜色定义 - 使用RGB值定义各种颜色
self.BLACK = (0, 0, 0) # 黑色 - 背景色
self.GREEN = (0, 255, 0) # 绿色 - 蛇身基础色
self.DARK_GREEN = (0, 200, 0) # 深绿色 - 蛇身阴影
self.LIGHT_GREEN = (100, 255, 100) # 浅绿色 - 蛇身高光
self.RED = (255, 0, 0) # 红色 - 食物色
self.DARK_RED = (200, 0, 0) # 深红色 - 食物阴影
self.WHITE = (255, 255, 255) # 白色 - 文字色
self.BLUE = (0, 100, 255) # 蓝色 - 蛇头色
# 创建游戏窗口
self.screen = pygame.display.set_mode((self.WINDOW_WIDTH, self.WINDOW_HEIGHT))
pygame.display.set_caption("贪吃蛇游戏 - Snake Game") # 设置窗口标题
# 创建时钟对象用于控制游戏帧率
self.clock = pygame.time.Clock()
# 初始化字体 - 用于显示分数和菜单,使用支持中文的字体
# 按优先级尝试不同的中文字体
font_names = [
'microsoftyahei', # 微软雅黑
'simsun', # 宋体
'simhei', # 黑体
'kaiti', # 楷体
'arial unicode ms', # Arial Unicode MS
'dengxian', # 等线
'fangsong' # 仿宋
]
self.font = None
self.big_font = None
self.small_font = None
# 尝试加载支持中文的字体
for font_name in font_names:
try:
self.font = pygame.font.SysFont(font_name, 32) # 调整为32号字体
self.big_font = pygame.font.SysFont(font_name, 56) # 调整为56号字体
self.small_font = pygame.font.SysFont(font_name, 24) # 添加24号小字体
# 测试字体是否能正确渲染中文
test_surface = self.font.render('测试', True, (255, 255, 255))
if test_surface.get_width() > 0:
break
except:
continue
# 如果所有中文字体都失败,使用默认字体
if self.font is None:
self.font = pygame.font.Font(None, 32)
self.big_font = pygame.font.Font(None, 56)
self.small_font = pygame.font.Font(None, 24)
# 游戏状态变量
self.difficulty = Difficulty.MEDIUM # 默认难度
self.speed_modifier = 1.0 # 速度调节倍数,1.0为正常速度
self.reset_game() # 重置游戏到初始状态
def reset_game(self):
"""重置游戏到初始状态 - 重新开始游戏时调用"""
# 蛇的初始位置 - 在屏幕中央
start_x = self.GRID_WIDTH // 2
start_y = self.GRID_HEIGHT // 2
# 蛇身列表 - 每个元素是一个(x, y)坐标
# 初始蛇身有3节,从头到尾排列
self.snake = [
(start_x, start_y), # 蛇头
(start_x - 1, start_y), # 蛇身第一节
(start_x - 2, start_y) # 蛇身第二节
]
# 蛇的移动方向 - 初始向右移动
self.direction = Direction.RIGHT
# 生成第一个食物
self.generate_food()
# 游戏分数
self.score = 0
# 游戏状态标志
self.game_over = False
self.paused = False
def generate_food(self):
"""生成食物 - 在空白位置随机生成食物"""
while True:
# 在游戏区域内随机生成坐标
food_x = random.randint(0, self.GRID_WIDTH - 1)
food_y = random.randint(0, self.GRID_HEIGHT - 1)
# 检查生成的位置是否与蛇身重叠
if (food_x, food_y) not in self.snake:
self.food = (food_x, food_y) # 设置食物位置
break # 找到合适位置,退出循环
def get_speed(self):
"""根据难度获取游戏速度 - 返回每秒帧数"""
speed_map = {
Difficulty.EASY: 8, # 简单:8 FPS
Difficulty.MEDIUM: 12, # 中等:12 FPS
Difficulty.HARD: 18, # 困难:18 FPS
Difficulty.EXPERT: 25 # 专家:25 FPS
}
base_speed = speed_map[self.difficulty]
# 应用速度调节倍数,限制在0.5到3.0倍之间
adjusted_speed = int(base_speed * self.speed_modifier)
return max(3, min(60, adjusted_speed)) # 限制在3-60 FPS之间
def handle_input(self):
"""处理用户输入 - 键盘事件处理"""
for event in pygame.event.get():
if event.type == pygame.QUIT: # 点击关闭按钮
return False
elif event.type == pygame.KEYDOWN: # 按键按下事件
if event.key == pygame.K_ESCAPE: # ESC键退出
return False
elif event.key == pygame.K_SPACE: # 空格键暂停/继续
self.paused = not self.paused
elif event.key == pygame.K_r and self.game_over: # R键重新开始(游戏结束时)
self.reset_game()
# 方向控制 - 防止蛇反向移动(撞到自己)
elif not self.game_over and not self.paused:
if event.key == pygame.K_UP and self.direction != Direction.DOWN:
self.direction = Direction.UP
elif event.key == pygame.K_DOWN and self.direction != Direction.UP:
self.direction = Direction.DOWN
elif event.key == pygame.K_LEFT and self.direction != Direction.RIGHT:
self.direction = Direction.LEFT
elif event.key == pygame.K_RIGHT and self.direction != Direction.LEFT:
self.direction = Direction.RIGHT
# 难度选择(游戏开始前或结束后)
elif self.game_over:
if event.key == pygame.K_1:
self.difficulty = Difficulty.EASY
self.reset_game()
elif event.key == pygame.K_2:
self.difficulty = Difficulty.MEDIUM
self.reset_game()
elif event.key == pygame.K_3:
self.difficulty = Difficulty.HARD
self.reset_game()
elif event.key == pygame.K_4:
self.difficulty = Difficulty.EXPERT
self.reset_game()
# 速度调节(任何时候都可以使用)
if event.key == pygame.K_PLUS or event.key == pygame.K_KP_PLUS or event.key == pygame.K_EQUALS:
# 增加速度(最大3倍)
self.speed_modifier = min(3.0, self.speed_modifier + 0.2)
elif event.key == pygame.K_MINUS or event.key == pygame.K_KP_MINUS:
# 减少速度(最小0.5倍)
self.speed_modifier = max(0.5, self.speed_modifier - 0.2)
return True # 继续游戏
def update_snake(self):
"""更新蛇的位置 - 游戏核心逻辑"""
if self.game_over or self.paused:
return # 游戏结束或暂停时不更新
# 获取蛇头当前位置
head_x, head_y = self.snake[0]
# 根据移动方向计算新的蛇头位置
dx, dy = self.direction.value
new_head = (head_x + dx, head_y + dy)
# 检查边界碰撞 - 蛇头是否撞墙
if (new_head[0] < 0 or new_head[0] >= self.GRID_WIDTH or
new_head[1] < 0 or new_head[1] >= self.GRID_HEIGHT):
self.game_over = True
return
# 检查自身碰撞 - 蛇头是否撞到蛇身
if new_head in self.snake:
self.game_over = True
return
# 将新蛇头添加到蛇身前端
self.snake.insert(0, new_head)
# 检查是否吃到食物
if new_head == self.food:
# 吃到食物:增加分数,生成新食物
self.score += 10
self.generate_food()
else:
# 没吃到食物:移除蛇尾(保持蛇身长度)
self.snake.pop()
def draw_gradient_rect(self, surface, rect, color1, color2, vertical=True):
"""绘制渐变矩形 - 创建美观的视觉效果"""
if vertical:
# 垂直渐变
for y in range(rect.height):
# 计算当前行的颜色(线性插值)
ratio = y / rect.height
r = int(color1[0] * (1 - ratio) + color2[0] * ratio)
g = int(color1[1] * (1 - ratio) + color2[1] * ratio)
b = int(color1[2] * (1 - ratio) + color2[2] * ratio)
# 绘制一条水平线
pygame.draw.line(surface, (r, g, b),
(rect.x, rect.y + y),
(rect.x + rect.width, rect.y + y))
else:
# 水平渐变
for x in range(rect.width):
ratio = x / rect.width
r = int(color1[0] * (1 - ratio) + color2[0] * ratio)
g = int(color1[1] * (1 - ratio) + color2[1] * ratio)
b = int(color1[2] * (1 - ratio) + color2[2] * ratio)
pygame.draw.line(surface, (r, g, b),
(rect.x + x, rect.y),
(rect.x + x, rect.y + rect.height))
def draw_snake(self):
"""绘制蛇 - 美观的蛇身渲染"""
for i, (x, y) in enumerate(self.snake):
# 计算屏幕像素坐标
rect = pygame.Rect(x * self.GRID_SIZE, y * self.GRID_SIZE,
self.GRID_SIZE, self.GRID_SIZE)
if i == 0: # 蛇头 - 使用蓝色渐变
self.draw_gradient_rect(self.screen, rect, self.BLUE, (0, 50, 200))
# 添加蛇头边框
pygame.draw.rect(self.screen, self.WHITE, rect, 2)
# 绘制眼睛
eye_size = 3
eye1_pos = (x * self.GRID_SIZE + 5, y * self.GRID_SIZE + 5)
eye2_pos = (x * self.GRID_SIZE + 15, y * self.GRID_SIZE + 5)
pygame.draw.circle(self.screen, self.WHITE, eye1_pos, eye_size)
pygame.draw.circle(self.screen, self.WHITE, eye2_pos, eye_size)
else: # 蛇身 - 使用绿色渐变
# 根据位置创建不同深度的绿色
intensity = max(150, 255 - i * 10) # 越靠后颜色越深
body_color = (0, intensity, 0)
shadow_color = (0, max(100, intensity - 50), 0)
self.draw_gradient_rect(self.screen, rect, body_color, shadow_color)
# 添加蛇身边框
pygame.draw.rect(self.screen, self.DARK_GREEN, rect, 1)
def draw_food(self):
"""绘制食物 - 美观的食物渲染"""
x, y = self.food
rect = pygame.Rect(x * self.GRID_SIZE, y * self.GRID_SIZE,
self.GRID_SIZE, self.GRID_SIZE)
# 绘制食物主体(圆形)
center = rect.center
radius = self.GRID_SIZE // 2 - 2
# 外圈(阴影效果)
pygame.draw.circle(self.screen, self.DARK_RED, center, radius + 1)
# 内圈(主体)
pygame.draw.circle(self.screen, self.RED, center, radius)
# 高光效果
highlight_pos = (center[0] - 3, center[1] - 3)
pygame.draw.circle(self.screen, (255, 100, 100), highlight_pos, 3)
def draw_ui(self):
"""绘制用户界面 - 分数、难度等信息"""
# 绘制分数
score_text = self.small_font.render(f"分数: {self.score}", True, self.WHITE)
self.screen.blit(score_text, (10, 10))
# 绘制难度
difficulty_names = {
Difficulty.EASY: "简单",
Difficulty.MEDIUM: "中等",
Difficulty.HARD: "困难",
Difficulty.EXPERT: "专家"
}
difficulty_text = self.small_font.render(f"难度: {difficulty_names[self.difficulty]}", True, self.WHITE)
self.screen.blit(difficulty_text, (10, 35))
# 绘制蛇身长度
length_text = self.small_font.render(f"长度: {len(self.snake)}", True, self.WHITE)
self.screen.blit(length_text, (10, 60))
# 绘制速度倍数
speed_text = self.small_font.render(f"速度: {self.speed_modifier:.1f}x", True, self.WHITE)
self.screen.blit(speed_text, (10, 85))
# 绘制控制提示
if not self.game_over:
control_text = self.small_font.render("+ / - 调速度", True, self.WHITE)
self.screen.blit(control_text, (10, 110))
# 暂停提示
if self.paused:
pause_text = self.big_font.render("游戏暂停", True, self.WHITE)
text_rect = pause_text.get_rect(center=(self.WINDOW_WIDTH//2, self.WINDOW_HEIGHT//2))
self.screen.blit(pause_text, text_rect)
resume_text = self.font.render("按空格键继续", True, self.WHITE)
resume_rect = resume_text.get_rect(center=(self.WINDOW_WIDTH//2, self.WINDOW_HEIGHT//2 + 50))
self.screen.blit(resume_text, resume_rect)
def draw_game_over(self):
"""绘制游戏结束界面"""
# 半透明背景
overlay = pygame.Surface((self.WINDOW_WIDTH, self.WINDOW_HEIGHT))
overlay.set_alpha(128) # 设置透明度
overlay.fill(self.BLACK)
self.screen.blit(overlay, (0, 0))
# 游戏结束文字
game_over_text = self.big_font.render("游戏结束!", True, self.WHITE)
text_rect = game_over_text.get_rect(center=(self.WINDOW_WIDTH//2, self.WINDOW_HEIGHT//2 - 120))
self.screen.blit(game_over_text, text_rect)
# 最终分数
final_score_text = self.font.render(f"最终分数: {self.score}", True, self.WHITE)
score_rect = final_score_text.get_rect(center=(self.WINDOW_WIDTH//2, self.WINDOW_HEIGHT//2 - 70))
self.screen.blit(final_score_text, score_rect)
# 操作提示 - 使用小字体和更大间距
instructions = [
"按 R 键重新开始",
"按 1-4 键选择难度:",
"1: 简单 2: 中等 3: 困难 4: 专家",
"按 + / - 键调整速度",
"按 ESC 键退出"
]
start_y = self.WINDOW_HEIGHT//2 - 10 # 起始Y位置
for i, instruction in enumerate(instructions):
text = self.small_font.render(instruction, True, self.WHITE) # 使用小字体
text_rect = text.get_rect(center=(self.WINDOW_WIDTH//2, start_y + i * 35)) # 增加行间距
self.screen.blit(text, text_rect)
def draw(self):
"""绘制游戏画面 - 主渲染函数"""
# 清空屏幕(填充黑色背景)
self.screen.fill(self.BLACK)
# 绘制游戏元素
self.draw_snake() # 绘制蛇
self.draw_food() # 绘制食物
self.draw_ui() # 绘制界面
# 如果游戏结束,绘制结束界面
if self.game_over:
self.draw_game_over()
# 更新显示
pygame.display.flip()
def run(self):
"""运行游戏 - 主游戏循环"""
print("贪吃蛇游戏启动!")
print("控制说明:")
print("- 方向键控制蛇的移动")
print("- 空格键暂停/继续游戏")
print("- R键重新开始(游戏结束时)")
print("- ESC键退出游戏")
print("- 数字键1-4选择难度")
running = True
while running:
# 处理用户输入
running = self.handle_input()
# 更新游戏状态
self.update_snake()
# 绘制游戏画面
self.draw()
# 控制游戏帧率(根据难度调整速度)
self.clock.tick(self.get_speed())
# 退出pygame
pygame.quit()
sys.exit()
# 程序入口点
if __name__ == "__main__":
# 创建游戏实例并运行
game = SnakeGame()
game.run()