ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python Pygame 开发 2D 追逐游戏:从 AI 逻辑到碰撞检测的完整实践

Python Pygame 开发 2D 追逐游戏:从 AI 逻辑到碰撞检测的完整实践 之前在做游戏开发时经常需要实现一些有趣的、带有“追逐”或“躲避”核心玩法的原型。这类玩法看似简单但要让角色移动流畅、AI行为自然、碰撞检测精准背后涉及不少细节。本文将以一个名为「MIKU」Catch_Me_If_You_Can 的趣味小游戏项目为例完整拆解其开发全流程。我们将使用 Python 的 Pygame 库来实现涵盖从环境搭建、精灵动画、玩家控制、AI 追逐逻辑、碰撞检测到游戏状态管理的所有环节。无论你是想学习 Pygame 基础还是希望掌握一个完整小游戏的开发思路都能从本文获得可直接复用的代码和清晰的实现逻辑。1. 背景与核心概念「Catch Me If You Can」直译为“如果你能抓住我”通常指代一种“追逐-躲避”的游戏模式。在本项目中我们将其具象化为一个简单的 2D 游戏玩家控制一个角色例如一个可爱的卡通形象「MIKU」在有限的屏幕空间内移动目标是躲避一个或多个由简单 AI 控制的“追逐者”。游戏的核心乐趣在于利用灵活的走位与 AI 周旋尽可能长时间地生存下去。从技术角度看这个项目完美涵盖了 2D 游戏开发的基础要素图形渲染与动画加载图片、绘制精灵、实现帧动画。用户输入处理响应键盘或鼠标事件控制玩家角色移动。游戏物理与逻辑包括速度、方向、边界限制等。人工智能AI为追逐者实现自动寻路或追踪逻辑这是游戏可玩性的关键。碰撞检测判断玩家是否被追逐者抓住触发游戏结束。游戏状态管理管理游戏开始、进行中、结束等不同状态。我们将使用Pygame这个轻量级且功能强大的 Python 库来完成这一切。Pygame 提供了对图像、声音、输入设备和基础 2D 图形操作的封装非常适合快速开发游戏原型和小型项目。2. 环境准备与版本说明在开始编写代码之前我们需要搭建开发环境。请确保你的系统已安装 Python。2.1 安装 Python 与 Pygame首先你需要安装 Python。建议使用 Python 3.7 或更高版本。你可以从 Python 官网 下载并安装。安装好 Python 后通过 pip 安装 Pygame。打开你的终端Windows 上是 CMD 或 PowerShellmacOS/Linux 上是 Terminal并运行以下命令pip install pygame安装完成后可以通过以下命令验证安装是否成功python -c “import pygame; print(pygame.version.ver)”如果输出版本号如2.5.2则说明安装成功。2.2 项目结构与资源准备我们将创建一个简单的项目文件夹来组织代码和资源。建议的目录结构如下catch_me_if_you_can/ ├── main.py # 游戏主程序入口 ├── game.py # 游戏主逻辑类 ├── player.py # 玩家角色类 ├── chaser.py # 追逐者AI类 ├── assets/ # 资源文件夹 │ ├── images/ # 图片资源 │ │ ├── miku.png # 玩家角色图片 │ │ └── chaser.png # 追逐者图片 │ └── sounds/ # 音效资源可选 └── README.md # 项目说明可选你需要准备两张 PNG 格式的图片分别代表玩家角色如miku.png和追逐者如chaser.png。图片背景最好是透明的PNG 支持透明通道尺寸建议在 64x64 像素左右以适应游戏窗口。你可以使用任何图像编辑软件制作或在开源素材网站寻找合适的素材。本文的代码示例将基于此结构。如果你暂时没有图片也可以使用 Pygame 绘制简单的矩形或圆形来代替代码中会提供备选方案。3. 核心原理与 Pygame 基础拆解在深入项目之前我们先快速理解 Pygame 程序的基本骨架和几个核心概念。3.1 Pygame 程序主循环每个 Pygame 游戏都围绕一个“游戏循环”运行。这个循环每秒运行数十次由帧率控制在每次循环中程序依次完成以下工作处理事件检查是否有键盘按下、鼠标点击、退出等事件发生。更新游戏状态根据输入和游戏逻辑更新所有游戏对象如角色位置、AI 决策的状态。渲染画面将更新后的游戏状态绘制到屏幕上。# 这是一个极简的 Pygame 主循环框架 import pygame pygame.init() screen pygame.display.set_mode((800, 600)) clock pygame.time.Clock() running True while running: # 1. 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 2. 更新游戏状态 (这里暂时为空) # ... # 3. 渲染画面 screen.fill((0, 0, 0)) # 用黑色填充屏幕 pygame.display.flip() # 更新整个屏幕显示 # 控制帧率 clock.tick(60) # 每秒60帧 pygame.quit()3.2 精灵Sprite与图像处理在 Pygame 中Sprite类代表游戏中的一个可视对象。我们通常会创建自己的角色类来继承pygame.sprite.Sprite并为其添加图像image和位置矩形rect属性。加载图片使用pygame.image.load()。为了获得更好的性能和视觉效果如透明背景通常会对加载的图片进行转换convert_alpha()。class Player(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() # 加载图片并转换 self.image pygame.image.load(‘assets/images/miku.png’).convert_alpha() # 获取图片的矩形区域并设置初始位置 self.rect self.image.get_rect(center(x, y)) # 自定义属性如速度 self.speed 53.3 移动与向量运算角色的移动本质上是其rect的x和y坐标的连续变化。根据键盘输入如 WASD 或方向键我们修改这些坐标。更优雅的方式是使用向量pygame.math.Vector2来表示位置和速度。向量可以方便地进行加减、缩放等运算特别适合处理涉及角度和速度的移动。# 使用向量表示位置和速度 self.pos pygame.math.Vector2(x, y) # 精确位置浮点数 self.velocity pygame.math.Vector2(0, 0) # 速度向量 self.rect.center self.pos # 用精确位置更新显示矩形 # 在更新函数中 self.pos self.velocity self.rect.center self.pos3.4 简单的 AI 追逐逻辑追逐者 AI 的核心是如何让追逐者朝着玩家当前位置移动。一个简单而有效的方法是计算从追逐者到玩家的方向向量将其标准化使其长度为1然后乘以追逐者的速度即可得到每帧的速度向量。# 在 Chaser 类的 update 方法中 def update(self, player_pos): # player_pos 是玩家位置的 Vector2 # 计算方向向量目标位置 - 自身位置 direction player_pos - self.pos # 如果距离不为零则标准化方向向量 if direction.length() 0: direction direction.normalize() # 速度 方向 * 速度标量 self.velocity direction * self.speed # 更新位置 self.pos self.velocity self.rect.center self.pos3.5 碰撞检测Pygame 的sprite模块提供了便捷的碰撞检测方法。最常用的是pygame.sprite.spritecollide()它可以检测一个精灵是否与一个精灵组中的任何精灵发生碰撞。我们用它来判断玩家是否被任何一个追逐者抓住。# 假设 players 是包含玩家精灵的组chasers 是包含所有追逐者的组 if pygame.sprite.spritecollide(player, chasers, False): # 发生碰撞游戏结束 game_over True4. 完整实战构建「MIKU」Catch_Me_If_You_Can现在我们将上述概念整合起来构建完整的游戏。我们将按照模块化的思想分别创建玩家、追逐者和游戏主控类。4.1 创建玩家角色类 (player.py)玩家角色需要响应键盘输入并在屏幕边界内移动。# file: player.py import pygame class Player(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() # 尝试加载图片如果失败则绘制一个矩形 try: self.image pygame.image.load(‘assets/images/miku.png’).convert_alpha() except FileNotFoundError: self.image pygame.Surface((50, 50), pygame.SRCALPHA) pygame.draw.circle(self.image, (100, 200, 255), (25, 25), 25) # 画一个蓝色圆形代表玩家 self.rect self.image.get_rect(center(x, y)) # 使用向量存储精确位置和速度 self.pos pygame.math.Vector2(self.rect.center) self.velocity pygame.math.Vector2(0, 0) self.speed 5 # 屏幕边界将在游戏主类中设置 self.screen_width 800 self.screen_height 600 def set_screen_bounds(self, width, height): 设置屏幕边界防止角色移出屏幕 self.screen_width width self.screen_height height def handle_input(self): 处理键盘输入更新速度向量 keys pygame.key.get_pressed() self.velocity pygame.math.Vector2(0, 0) # 使用 WASD 或方向键控制 if keys[pygame.K_w] or keys[pygame.K_UP]: self.velocity.y -self.speed if keys[pygame.K_s] or keys[pygame.K_DOWN]: self.velocity.y self.speed if keys[pygame.K_a] or keys[pygame.K_LEFT]: self.velocity.x -self.speed if keys[pygame.K_d] or keys[pygame.K_RIGHT]: self.velocity.x self.speed # 如果同时按了斜方向键速度会叠加导致斜向移动更快。 # 标准化速度向量确保斜向移动速度与横向/纵向一致。 if self.velocity.length() 0: self.velocity self.velocity.normalize() * self.speed def update(self): 更新角色位置并限制在屏幕内 self.handle_input() self.pos self.velocity # 边界检查 self.pos.x max(self.rect.width // 2, min(self.screen_width - self.rect.width // 2, self.pos.x)) self.pos.y max(self.rect.height // 2, min(self.screen_height - self.rect.height // 2, self.pos.y)) # 更新显示矩形的位置 self.rect.center (int(self.pos.x), int(self.pos.y))4.2 创建追逐者 AI 类 (chaser.py)追逐者会自动追踪玩家并拥有自己的速度。# file: chaser.py import pygame import random class Chaser(pygame.sprite.Sprite): def __init__(self, x, y, speed3): super().__init__() # 尝试加载图片如果失败则绘制一个红色矩形 try: self.image pygame.image.load(‘assets/images/chaser.png’).convert_alpha() except FileNotFoundError: self.image pygame.Surface((40, 40), pygame.SRCALPHA) pygame.draw.rect(self.image, (255, 50, 50), (0, 0, 40, 40), border_radius5) # 画一个红色圆角矩形 self.rect self.image.get_rect(center(x, y)) self.pos pygame.math.Vector2(self.rect.center) self.velocity pygame.math.Vector2(0, 0) self.speed speed # 可以添加一个“视野”或“激活距离”来增加趣味性 self.activation_distance 300 # 像素当玩家进入此距离才开始追逐 def update(self, player_pos): 根据玩家位置更新追逐者状态 # 计算与玩家的距离 distance_to_player self.pos.distance_to(player_pos) # 如果玩家在激活距离内则进行追逐 if distance_to_player self.activation_distance: direction player_pos - self.pos if direction.length() 0: # 标准化方向并乘以速度 direction direction.normalize() self.velocity direction * self.speed else: self.velocity pygame.math.Vector2(0, 0) else: # 玩家在视野外可以停止移动或随机游走 self.velocity pygame.math.Vector2(0, 0) # 可选简单随机游走逻辑 # if random.random() 0.02: # 2%的概率改变方向 # self.velocity pygame.math.Vector2(random.uniform(-1, 1), random.uniform(-1, 1)).normalize() * self.speed * 0.5 # 更新位置 self.pos self.velocity self.rect.center (int(self.pos.x), int(self.pos.y))4.3 创建游戏主逻辑类 (game.py)这个类负责初始化 Pygame、管理游戏状态、创建精灵、运行主循环以及处理碰撞。# file: game.py import pygame import sys from player import Player from chaser import Chaser class CatchMeGame: def __init__(self, width800, height600): pygame.init() self.screen pygame.display.set_mode((width, height)) pygame.display.set_caption(‘「MIKU」Catch Me If You Can’) self.clock pygame.time.Clock() self.running True self.game_over False self.survival_time 0 # 生存时间帧或秒 self.width width self.height height # 创建精灵组 self.all_sprites pygame.sprite.Group() self.chasers pygame.sprite.Group() # 创建玩家 self.player Player(width // 2, height // 2) self.player.set_screen_bounds(width, height) self.all_sprites.add(self.player) # 创建初始追逐者例如4个放在屏幕四角附近 self.spawn_chasers(4) # 字体用于显示时间和游戏结束信息 self.font pygame.font.Font(None, 36) def spawn_chasers(self, count): 在屏幕边缘随机位置生成指定数量的追逐者 for _ in range(count): # 随机选择在屏幕的哪条边生成 side random.randint(0, 3) if side 0: # 上边 x random.randint(50, self.width - 50) y -50 elif side 1: # 右边 x self.width 50 y random.randint(50, self.height - 50) elif side 2: # 下边 x random.randint(50, self.width - 50) y self.height 50 else: # 左边 x -50 y random.randint(50, self.height - 50) # 追逐者速度可以随机增加难度变化 speed random.uniform(2.0, 4.0) chaser Chaser(x, y, speed) self.all_sprites.add(chaser) self.chasers.add(chaser) def handle_events(self): 处理系统事件如退出 for event in pygame.event.get(): if event.type pygame.QUIT: self.running False elif event.type pygame.KEYDOWN: if event.key pygame.K_ESCAPE: self.running False # 游戏结束后按 R 键重新开始 if self.game_over and event.key pygame.K_r: self.__init__(self.width, self.height) # 简单重置游戏状态 def update(self): 更新所有游戏对象的状态 if not self.game_over: self.survival_time 1 # 更新玩家 self.player.update() # 更新所有追逐者传入玩家位置 for chaser in self.chasers: chaser.update(self.player.pos) # 碰撞检测玩家是否被抓住 if pygame.sprite.spritecollide(self.player, self.chasers, False): self.game_over True # 可选每隔一段时间生成新的追逐者增加难度 # if self.survival_time % 600 0: # 每10秒假设60帧生成一个 # self.spawn_chasers(1) def draw(self): 绘制所有游戏对象和UI信息 self.screen.fill((30, 30, 40)) # 深蓝色背景 # 绘制所有精灵 self.all_sprites.draw(self.screen) # 绘制UI生存时间 time_text self.font.render(f’Time: {self.survival_time // 60}‘, True, (255, 255, 255)) self.screen.blit(time_text, (10, 10)) # 绘制追逐者数量 chaser_count_text self.font.render(f’Chasers: {len(self.chasers)}‘, True, (255, 255, 255)) self.screen.blit(chaser_count_text, (10, 50)) # 如果游戏结束显示提示信息 if self.game_over: game_over_surface pygame.Surface((self.width, self.height), pygame.SRCALPHA) game_over_surface.fill((0, 0, 0, 180)) # 半透明黑色遮罩 self.screen.blit(game_over_surface, (0, 0)) text self.font.render(‘GAME OVER! You were caught.’, True, (255, 100, 100)) text_rect text.get_rect(center(self.width // 2, self.height // 2 - 30)) self.screen.blit(text, text_rect) restart_text self.font.render(‘Press R to Restart, ESC to Quit’, True, (200, 200, 255)) restart_rect restart_text.get_rect(center(self.width // 2, self.height // 2 30)) self.screen.blit(restart_text, restart_rect) pygame.display.flip() def run(self): 游戏主循环 while self.running: self.handle_events() self.update() self.draw() self.clock.tick(60) # 60 FPS pygame.quit() sys.exit()4.4 创建程序入口 (main.py)这是启动游戏的入口文件非常简单。# file: main.py from game import CatchMeGame if __name__ ‘__main__’: game CatchMeGame() game.run()4.5 运行与验证确保所有文件 (main.py,game.py,player.py,chaser.py) 和assets/images/文件夹内含图片都放在正确的目录结构中。在终端中导航到项目根目录catch_me_if_you_can/。运行命令python main.py游戏窗口应该会弹出。使用WASD或方向键控制蓝色圆形或你的 MIKU 图片移动。红色方块或追逐者图片会从屏幕外生成并开始追逐你。尝试躲避红色方块。一旦被碰到游戏会显示“GAME OVER”并暂停。此时按R键可以重新开始按ESC键退出游戏。观察屏幕左上角显示的生存时间和追逐者数量。5. 常见问题与排查思路在开发或运行上述代码时你可能会遇到一些典型问题。下面是一个快速排查指南。问题现象可能原因解决思路运行后立即报错ModuleNotFoundError: No module named ‘pygame’Pygame 未安装或未正确安装。在终端运行pip install pygame或pip3 install pygame。确保使用的 Python 环境与运行代码的环境一致。报错FileNotFoundError: No such file or directory: ‘assets/images/miku.png’图片文件路径错误或文件不存在。1. 检查assets/images/目录是否存在图片文件名是否拼写正确包括大小写和扩展名。2. 暂时使用代码中的备选绘图方案注释掉try块中的load确保执行except块。3. 使用绝对路径或修改load函数中的相对路径。游戏窗口卡顿或移动不流畅帧率不稳定或每帧计算量过大。1. 检查clock.tick(60)是否在主循环中它限制了最高帧率。2. 如果追逐者数量非常多AI 计算可能成为瓶颈。考虑优化距离计算如使用距离平方进行比较或使用空间划分算法如四叉树来减少不必要的计算。角色移动速度比预期快尤其是斜向未对速度向量进行标准化。在Player.handle_input()方法中当同时按下两个方向键时速度向量是(speed, speed)其长度是sqrt(2)*speed。务必在计算后调用self.velocity.normalize() * self.speed来确保各方向速度一致。追逐者“抖动”或移动不自然AI 更新逻辑或位置取整问题。1. 确保Chaser.update()中direction向量在标准化前检查长度是否大于 0避免除以零错误。2. 位置 (self.pos) 使用浮点数Vector2而显示矩形 (self.rect.center) 使用整数。这是正常做法但可能导致轻微视觉抖动。如果追求平滑可以考虑使用子像素渲染更高级的主题。碰撞检测不准确或延迟碰撞矩形 (rect) 与图像大小不匹配或更新顺序问题。1. 确保self.rect是通过self.image.get_rect()正确获取的。2. 碰撞检测应在所有精灵的update()方法被调用之后进行以确保位置是最新的。按 R 键无法重新开始游戏游戏结束状态game_over的逻辑或事件处理有误。检查Game.handle_events()中对KEYDOWN事件和pygame.K_r键值的判断是否在self.game_over为True的条件下执行。6. 最佳实践与工程建议将一个小游戏跑起来只是第一步。要让代码更健壮、可维护、易扩展可以参考以下实践6.1 资源管理与错误处理集中管理资源路径不要在多个类中硬编码‘assets/images/...’。可以创建一个settings.py或config.py文件定义资源根目录和具体路径常量。健壮的资源加载像我们代码中那样使用try-except是一个好习惯。你还可以实现一个资源加载器在游戏初始化时加载所有资源如果失败则给出友好提示并退出而不是在游戏中途崩溃。使用精灵表Sprite Sheet如果你的角色有多个动画帧使用一张包含所有帧的精灵表图片然后通过pygame.Rect来裁剪比加载多个独立图片更高效。6.2 游戏架构优化状态模式State Pattern当前我们用game_over布尔值管理状态。对于更复杂的游戏开始菜单、设置、多个关卡建议使用状态机。每个状态如MenuState,PlayState,GameOverState是一个独立的类负责自己的事件处理、更新和渲染。事件系统除了 Pygame 的系统事件可以创建自定义的游戏事件如PLAYER_CAUGHT,SPAWN_NEW_CHASER通过pygame.event.post()发布在其他模块中监听处理实现松耦合。配置数据驱动将追逐者的速度、生成间隔、玩家初始生命值等游戏平衡参数提取到外部配置文件如 JSON 或 YAML中。这样调整游戏难度时无需修改代码。6.3 AI 与游戏性增强更智能的追逐者路径预测让追逐者不是直接奔向玩家当前位置而是预测玩家未来的位置基于玩家当前速度方向。行为树或状态机为追逐者实现“巡逻”、“追逐”、“困惑”、“返回”等不同状态使行为更丰富。差异化能力创建不同类型的追逐者比如有的速度快但视野短有的速度慢但会“包抄”。玩家能力与道具添加“冲刺”、“隐身”、“放置障碍”等主动技能。在地图上随机生成临时道具拾取后获得短暂增益效果。关卡与进度设计多个关卡随着关卡提升追逐者数量、速度或 AI 强度逐渐增加。生存足够时间后进入下一关。6.4 性能与代码质量脏矩形渲染如果游戏场景中只有少量区域发生变化可以使用pygame.display.update(rect_list)只更新这些“脏”矩形区域而不是整个屏幕以提高性能。使用精灵组高效碰撞检测pygame.sprite.groupcollide()可以高效地检测两个精灵组之间的所有碰撞。对于大量精灵考虑使用pygame.sprite.spritecollideany()或空间划分。代码注释与文档为类和方法添加清晰的文档字符串docstring解释其作用、参数和返回值。复杂的逻辑处添加行内注释。版本控制使用 Git 等工具管理你的代码定期提交。这不仅是好习惯也能让你大胆尝试新功能而不怕破坏现有代码。7. 总结与扩展方向通过这个「MIKU」Catch_Me_If_You_Can 项目我们走完了一个完整 2D 小游戏从零到一的开发流程。我们掌握了 Pygame 的核心循环、精灵管理、用户输入、基础 AI 和碰撞检测。更重要的是我们实践了如何将游戏功能模块化为Player、Chaser和Game等类这是构建更复杂项目的基础。你可以以此项目为起点进行多方面的扩展练习美术与音效寻找或绘制更精美的精灵和背景图为碰撞、游戏结束等事件添加音效。游戏机制实现分数系统、多种道具、多个关卡、Boss 战等。网络功能尝试使用 Socket 或更高层的库实现一个简单的双人联机版一人扮演 MIKU另一人控制追逐者。移植与打包使用pyinstaller等工具将你的 Python 游戏打包成独立的可执行文件分享给没有安装 Python 的朋友。游戏开发是编程技能的综合体现涉及逻辑、架构、数学甚至美学。希望这个项目能成为你探索游戏开发世界的一块坚实跳板。动手修改代码加入你自己的创意是学习的最佳方式。如果在实践中遇到新的问题不妨回顾文中提到的原理和排查思路或查阅 Pygame 官方文档你总能找到解决方案。
返回列表