ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python贪吃蛇游戏开发实战:从Pygame环境搭建到碰撞检测实现

Python贪吃蛇游戏开发实战:从Pygame环境搭建到碰撞检测实现 最近在尝试用 Python 制作一个简单的游戏 Demo发现从零开始搭建一个可玩的游戏框架远比调用现成引擎更有挑战性也更能理解游戏运行的核心逻辑。本文将分享一个完整的《贪吃蛇》游戏 Demo 制作过程从 Pygame 环境搭建、游戏循环原理到蛇的移动、食物生成、碰撞检测等核心功能实现最后还会探讨如何添加分数、难度递增等扩展功能。无论你是 Python 初学者想通过项目练手还是对游戏开发原理感兴趣的开发者都能从这篇实战指南中获得一套可直接运行、易于修改的代码方案。1. 游戏 Demo 核心概念与设计思路在开始写代码之前我们需要明确我们要做什么以及为什么选择这些技术。1.1 为什么选择《贪吃蛇》作为 Demo《贪吃蛇》是一个经典且结构清晰的游戏它几乎包含了 2D 游戏的所有基础元素游戏循环 (Game Loop)处理输入、更新状态、渲染画面。精灵 (Sprite) 与图形绘制用简单的方块代表蛇和食物。用户输入处理通过键盘方向键控制蛇的移动方向。游戏状态管理蛇身长度的增长、食物的随机生成、得分计算。碰撞检测 (Collision Detection)蛇头与食物、蛇头与边界、蛇头与自身身体的碰撞判断。通过实现它可以系统地学习游戏开发的基础范式而代码量又相对可控非常适合作为入门项目。1.2 技术选型Pygame我们将使用Pygame这个 Python 库。它是一个为制作多媒体应用程序尤其是游戏而设计的跨平台模块。它基于 SDLSimple DirectMedia Layer库提供了对图像、声音、输入设备的简单访问足以支撑我们 2D 游戏 Demo 的开发。优点轻量、易学、文档丰富、社区活跃。核心模块pygame.display窗口管理、pygame.event事件处理、pygame.draw图形绘制、pygame.time时间控制。1.3 游戏基本规则与状态设计我们的贪吃蛇 Demo 需要定义以下规则和状态游戏区域一个固定大小的网格如 20x20 个格子每个格子是一个基本单元。蛇由一系列连续的格子组成。初始长度为 1头部用一个特殊颜色标识。蛇只能朝上下左右四个方向移动不能反向移动例如不能从左直接向右。食物在游戏区域内随机生成的一个格子颜色与蛇不同。移动规则蛇按固定时间间隔帧向当前方向移动一格。移动时蛇头向前一格蛇尾最后一格消失保持长度不变。吃食物如果蛇头移动到了食物所在的格子则“吃掉”食物。食物消失并在新位置随机生成同时蛇的长度增加一格通常通过在蛇尾添加一格实现。碰撞与失败撞墙蛇头移出游戏区域边界。撞自己蛇头碰到蛇身的任何一部分。 发生以上任何一种情况游戏结束。得分每吃掉一个食物得分增加。2. 环境准备与项目初始化2.1 安装 Python 与 Pygame首先确保你的电脑上安装了 Python。本文示例使用 Python 3.8 版本。你可以通过命令行检查python --version # 或 python3 --version接下来使用 pip 安装 Pygame。建议在虚拟环境中进行。# 使用国内镜像源加速安装 pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以运行一个简单测试来验证import pygame pygame.init() print(Pygame 安装成功) pygame.quit()2.2 创建项目结构创建一个新的项目文件夹例如snake_game_demo并在其中创建以下文件snake_game_demo/ ├── main.py # 游戏主入口包含游戏循环 ├── snake.py # 蛇类的定义 ├── food.py # 食物类的定义 ├── game_config.py # 游戏常量配置 └── README.md # 项目说明可选这种模块化的结构让代码更清晰易于维护和扩展。2.3 基础配置 (game_config.py)我们将游戏中的常量集中管理方便后期调整。# game_config.py # 游戏窗口与网格配置 SCREEN_WIDTH 800 SCREEN_HEIGHT 600 GRID_SIZE 20 # 每个格子的像素大小 GRID_WIDTH SCREEN_WIDTH // GRID_SIZE GRID_HEIGHT SCREEN_HEIGHT // GRID_SIZE # 颜色定义 (R, G, B) BLACK (0, 0, 0) WHITE (255, 255, 255) GREEN (0, 255, 0) RED (255, 0, 0) BLUE (0, 120, 255) GRAY (40, 40, 40) # 游戏速度帧率控制蛇移动快慢 FPS 10 # 蛇的初始位置和方向 INIT_SNAKE_POS [(GRID_WIDTH // 2, GRID_HEIGHT // 2)] INIT_DIRECTION (1, 0) # 初始向右移动 (dx, dy)3. 核心类与游戏逻辑实现3.1 实现蛇类 (snake.py)蛇是这个游戏的核心对象我们需要一个类来管理它的位置、方向、长度和绘制。# snake.py import pygame from game_config import GRID_SIZE, GREEN, BLUE, BLACK class Snake: def __init__(self): from game_config import INIT_SNAKE_POS, INIT_DIRECTION self.body INIT_SNAKE_POS[:] # 蛇身坐标列表每个元素是 (x, y) self.direction INIT_DIRECTION # 当前移动方向 (dx, dy) self.grow_pending False # 标记是否需要在下次移动时增长 def get_head_position(self): 获取蛇头位置 return self.body[0] def update(self): 更新蛇的位置移动 head_x, head_y self.get_head_position() dx, dy self.direction # 计算新的头部位置 new_head ((head_x dx) % GRID_WIDTH, (head_y dy) % GRID_HEIGHT) # 这里使用了取模运算让蛇可以穿过边界经典模式。如需撞墙死亡需修改此处逻辑。 self.body.insert(0, new_head) # 在头部插入新位置 if not self.grow_pending: # 如果不增长则移除尾部保持长度不变 self.body.pop() else: # 如果增长则保留尾部长度1并重置标记 self.grow_pending False def grow(self): 标记蛇需要增长将在下一次 update 时生效 self.grow_pending True def draw(self, surface): 在给定的 surface 上绘制蛇 for index, (x, y) in enumerate(self.body): # 蛇头用不同颜色绘制 color BLUE if index 0 else GREEN rect pygame.Rect(x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE) pygame.draw.rect(surface, color, rect) pygame.draw.rect(surface, BLACK, rect, 1) # 绘制黑色边框使格子更清晰 def reset(self): 重置蛇的状态到初始值 from game_config import INIT_SNAKE_POS, INIT_DIRECTION self.body INIT_SNAKE_POS[:] self.direction INIT_DIRECTION self.grow_pending False def check_collision_with_self(self): 检查蛇头是否与自己的身体碰撞 head self.get_head_position() # 从第二格开始检查因为头部是第一格 return head in self.body[1:]关键点解释body列表存储的是网格坐标不是像素坐标。绘制时才乘以GRID_SIZE转换为像素。update()方法中的% GRID_WIDTH实现了“穿墙”效果。如果你想改为“撞墙即死”可以在此处判断new_head的 x, y 是否在[0, GRID_WIDTH)和[0, GRID_HEIGHT)范围内如果超出则触发游戏结束。grow_pending机制是为了让增长在移动后生效逻辑更清晰。3.2 实现食物类 (food.py)食物需要在游戏区域内随机生成且不能生成在蛇身上。# food.py import pygame import random from game_config import GRID_WIDTH, GRID_HEIGHT, GRID_SIZE, RED, BLACK class Food: def __init__(self, snake_body_positions): self.position (0, 0) self.randomize_position(snake_body_positions) def randomize_position(self, snake_body_positions): 随机生成一个不在蛇身上的位置 while True: self.position (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1)) if self.position not in snake_body_positions: break def draw(self, surface): 在给定的 surface 上绘制食物 rect pygame.Rect(self.position[0] * GRID_SIZE, self.position[1] * GRID_SIZE, GRID_SIZE, GRID_SIZE) pygame.draw.rect(surface, RED, rect) pygame.draw.rect(surface, BLACK, rect, 1) # 绘制黑色边框3.3 实现游戏主循环 (main.py)这是游戏的大脑负责初始化、事件处理、状态更新和画面渲染。# main.py import pygame import sys from game_config import SCREEN_WIDTH, SCREEN_HEIGHT, BLACK, WHITE, GRAY, FPS from snake import Snake from food import Food def draw_grid(surface): 绘制网格线辅助观察 for x in range(0, SCREEN_WIDTH, GRID_SIZE): pygame.draw.line(surface, GRAY, (x, 0), (x, SCREEN_HEIGHT)) for y in range(0, SCREEN_HEIGHT, GRID_SIZE): pygame.draw.line(surface, GRAY, (0, y), (SCREEN_WIDTH, y)) def show_game_over(surface, font, score): 游戏结束时显示信息 game_over_text font.render(f游戏结束! 得分: {score}, True, WHITE) restart_text font.render(按 R 键重新开始按 ESC 键退出, True, WHITE) surface.blit(game_over_text, (SCREEN_WIDTH // 2 - game_over_text.get_width() // 2, SCREEN_HEIGHT // 2 - 50)) surface.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2, SCREEN_HEIGHT // 2)) def main(): pygame.init() clock pygame.time.Clock() screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(贪吃蛇 Demo - CSDN 实战教程) # 创建游戏对象 snake Snake() food Food(snake.body) # 初始食物要避开蛇身 # 游戏状态变量 score 0 game_over False font pygame.font.SysFont(None, 36) # 使用系统默认字体 # 主游戏循环 while True: # 1. 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() if event.type pygame.KEYDOWN: if game_over: # 游戏结束后的按键处理 if event.key pygame.K_r: # 重置游戏 snake.reset() food.randomize_position(snake.body) score 0 game_over False elif event.key pygame.K_ESCAPE: pygame.quit() sys.exit() else: # 游戏进行中的方向键控制 # 禁止直接反向移动例如不能从向右直接改为向左 if event.key pygame.K_UP and snake.direction ! (0, 1): snake.direction (0, -1) elif event.key pygame.K_DOWN and snake.direction ! (0, -1): snake.direction (0, 1) elif event.key pygame.K_LEFT and snake.direction ! (1, 0): snake.direction (-1, 0) elif event.key pygame.K_RIGHT and snake.direction ! (-1, 0): snake.direction (1, 0) if not game_over: # 2. 更新游戏状态 snake.update() # 3. 碰撞检测 # 吃食物检测 if snake.get_head_position() food.position: snake.grow() food.randomize_position(snake.body) score 10 # 每吃一个食物得10分 # 撞自己检测穿墙模式下只检测撞自己 if snake.check_collision_with_self(): game_over True # 4. 绘制画面 screen.fill(BLACK) draw_grid(screen) # 绘制网格可选 snake.draw(screen) food.draw(screen) # 绘制分数 score_text font.render(f得分: {score}, True, WHITE) screen.blit(score_text, (10, 10)) else: # 游戏结束画面 screen.fill(BLACK) show_game_over(screen, font, score) # 5. 刷新屏幕 pygame.display.flip() # 6. 控制游戏速度 clock.tick(FPS) if __name__ __main__: main()4. 运行与效果验证现在所有代码都已就绪。在项目根目录下打开终端运行python main.py如果一切正常你将看到一个黑色窗口中间有一条蓝色的蛇头蛇身绿色和一个红色的食物。使用键盘的上、下、左、右方向键控制蛇的移动。吃到食物后蛇身变长得分增加食物会在新的随机位置出现。当蛇头碰到自己的身体时游戏结束屏幕中央会显示最终得分并提示按R键重新开始或ESC键退出。5. 核心机制深度解析与优化5.1 游戏循环 (Game Loop) 详解main.py中的while True循环是游戏的心脏。每一次循环称为一“帧”Frame。标准游戏循环包含以下步骤事件处理 (Event Handling)pygame.event.get()获取所有输入键盘、鼠标、退出事件并做出响应。状态更新 (Update)根据输入和游戏逻辑更新所有游戏对象的状态如snake.update()。渲染 (Render/Draw)清空上一帧画面将所有对象绘制到屏幕上。帧率控制 (Frame Rate Control)clock.tick(FPS)确保循环以固定的频率运行本例中每秒10帧。这控制了蛇的移动速度。5.2 方向控制与输入处理方向控制有一个关键细节不能立即反向。例如蛇正在向右移动 (direction (1, 0))此时按下左键如果直接设置方向为(-1, 0)在下一帧蛇头就会反向移动一格导致“自己撞自己”的瞬间死亡。我们的代码通过条件判断避免了这种情况。5.3 碰撞检测的两种模式我们的 Demo 实现了“穿墙”模式通过取模运算。另一种更常见的模式是“撞墙死亡”。修改snake.py中的update方法即可实现# snake.py (修改 update 方法实现撞墙死亡) def update(self, game_over_callback): 更新蛇的位置移动如果撞墙则调用回调函数 head_x, head_y self.get_head_position() dx, dy self.direction new_head (head_x dx, head_y dy) # 不再取模 # 检查是否撞墙 if not (0 new_head[0] GRID_WIDTH and 0 new_head[1] GRID_HEIGHT): game_over_callback() # 触发游戏结束 return # 不再移动 self.body.insert(0, new_head) if not self.grow_pending: self.body.pop() else: self.grow_pending False同时需要在main.py中定义一个游戏结束的回调函数并修改调用方式。6. 常见问题与排查思路在编写和运行这个 Demo 时你可能会遇到以下问题问题现象可能原因解决方案运行python main.py报错ModuleNotFoundError: No module named pygamePygame 未安装或未安装在当前 Python 环境。1. 确认在正确的终端/环境中执行pip install pygame。2. 尝试使用pip3或python -m pip install pygame。窗口一闪而过或立即关闭代码可能在初始化或导入阶段就出错了。1. 在命令行中运行查看具体的错误信息。2. 检查game_config.py等导入路径是否正确文件是否在同一个目录。蛇的移动速度过快或过慢FPS帧率常量设置不合理。在game_config.py中调整FPS的值。值越大蛇移动越快。建议范围 5-15。按键控制无反应或反应迟钝1. 事件处理代码有误。2. 按键冲突如大写锁定。3. 帧率太低导致输入响应慢。1. 检查pygame.KEYDOWN事件处理代码块是否正确嵌套在循环中。2. 确保使用方向键而不是小键盘。3. 适当提高FPS。食物生成在了蛇的身体里Food.randomize_position方法中的循环可能因为蛇身过长而陷入长时间循环概率极低但可能。可以添加一个安全计数器如果尝试多次如100次仍未找到空位可以视为游戏胜利或进行特殊处理。游戏结束后按 R 键无法重启游戏结束后的按键处理逻辑未正确重置所有状态。检查if game_over:代码块中的重置逻辑确保snake.reset()、score0、game_overFalse都被执行并且食物被重新初始化。7. 功能扩展与工程化建议一个基础的 Demo 完成后可以考虑以下方向进行扩展和优化这能让你更深入地理解游戏工程。7.1 功能扩展点子难度递增随着分数增加逐步提高FPS让蛇移动更快。# 在 main.py 的更新部分 current_speed FPS score // 50 # 每得50分速度加1 clock.tick(current_speed)多种食物定义不同颜色的食物提供不同分数或特殊效果如加速、减速、蛇身缩短。游戏关卡设计不同的地图障碍物增加通关挑战。音效与背景音乐使用pygame.mixer模块在吃食物、碰撞、游戏结束时播放音效。最高分记录将最高分保存到本地文件如highscore.txt中每次游戏开始和结束时读写。更精美的图形使用pygame.image.load()加载蛇和食物的图片精灵替代简单的色块。7.2 工程化与代码优化建议状态机管理将游戏状态如菜单、游戏中、暂停、结束抽象成状态机使逻辑更清晰。配置外部化将FPS、颜色、初始长度等配置移至一个独立的config.ini或settings.json文件便于非开发者调整。异常处理对文件读写如最高分、资源加载如图片、声音添加try-except块增强鲁棒性。模块解耦Snake和Food类不应直接依赖pygame的绘制细节。可以考虑将绘制逻辑抽离到单独的Renderer类中这样未来更换图形库会更方便。添加注释与文档为复杂的函数和类添加文档字符串 (docstring)方便他人阅读和维护。通过这个《贪吃蛇》Demo 的从零构建你不仅学会了 Pygame 的基本使用更重要的是理解了游戏循环、对象更新、碰撞检测、状态管理等通用游戏开发概念。这套模式可以迁移到任何其他 2D 小游戏的开发中。接下来你可以尝试修改规则如改为撞墙死亡、添加新功能或者用同样的框架尝试制作“俄罗斯方块”、“打砖块”等经典游戏巩固你的游戏开发技能。
返回列表