
最近在整理旧项目时翻到了一个几年前用 Python 写的小游戏名字叫“亚当找夏娃1”。这其实是一个非常简单、甚至可以说有点“无聊”的点击解谜游戏核心玩法就是在一个静态场景里找到隐藏的“夏娃”并点击她。虽然玩法简单但当时为了把它做出来涉及了游戏窗口创建、事件监听、图像加载、碰撞检测等 Pygame 的基础知识。对于刚接触游戏开发或者想用 Python 做点小东西的开发者来说这是一个非常不错的练手项目。本文将带你从零开始完整复刻这个“超无聊的小游戏”。我们将使用 Pygame 库一步步实现游戏窗口、背景绘制、角色放置、鼠标交互和简单的胜利判定。通过这个实战你不仅能掌握 Pygame 的核心模块使用还能理解一个可运行的游戏项目的基本结构。无论你是 Python 初学者还是想给编程学习增加点趣味性的朋友都能跟着本文一步步实现。1. 游戏核心概念与设计思路在开始写代码之前我们先明确一下这个游戏到底是什么以及我们要如何实现它。1.1 游戏玩法与目标“亚当找夏娃”是一个典型的“寻物”或“隐藏对象”类游戏。在一个固定的游戏画面背景图中“夏娃”这个角色被巧妙地隐藏了起来比如藏在树后、石头缝里。玩家亚当需要通过移动鼠标在画面上仔细寻找当鼠标移动到夏娃所在的区域并点击时游戏判定成功弹出胜利提示。游戏的核心逻辑非常简单展示场景呈现一张包含各种元素的背景图片。隐藏目标在背景的某个坐标位置放置一张较小的“夏娃”图片。这个位置是固定的。玩家交互玩家用鼠标在屏幕上探索。碰撞检测实时判断鼠标位置是否与“夏娃”图片所在的矩形区域重叠。胜利判定当检测到鼠标在目标区域内且发生了点击事件时游戏结束并宣布胜利。1.2 技术选型为什么是 PygamePygame 是一套基于 SDLSimple DirectMedia Layer库的 Python 模块专为电子游戏设计。它提供了对图像、声音、事件、碰撞检测等游戏开发常见功能的封装让开发者无需接触底层复杂的图形和音频接口就能快速构建2D游戏原型。对于本项目Pygame 的优势在于简单易上手API 直观学习曲线平缓适合实现这种小型游戏。功能齐全pygame.display管理窗口pygame.image加载图片pygame.event处理鼠标键盘事件pygame.Rect处理矩形区域和碰撞完全满足需求。跨平台编写的代码可以在 Windows、macOS、Linux 上运行。1.3 项目文件结构规划在编码前规划好文件结构能让开发更清晰。我们创建一个项目文件夹find_eve结构如下find_eve/ ├── main.py # 游戏主程序入口 ├── assets/ # 资源文件夹 │ ├── background.jpg # 游戏背景图 │ └── eve.png # “夏娃”角色图 └── README.md # 项目说明可选将游戏所需的图片素材放入assets文件夹便于统一管理。2. 开发环境准备2.1 安装 Python 与 Pygame首先确保你的电脑上安装了 Python。推荐使用 Python 3.7 及以上版本。你可以通过在终端或命令提示符中输入python --version来检查。接下来安装 Pygame 库。使用 pip 安装非常简单pip install pygame安装完成后可以在 Python 交互环境中输入import pygame来验证是否成功不报错即说明安装成功。2.2 准备游戏素材你需要准备两张图片背景图 (background.jpg)一张场景图尺寸建议为 800x600 像素作为游戏窗口的大小。目标图 (eve.png)“夏娃”的图片建议是 PNG 格式支持透明背景尺寸较小例如 50x50 像素。你可以自己绘制也可以从一些免费无版权的素材网站获取。将这两张图片保存到前面提到的assets文件夹中。3. Pygame 核心模块与初始化任何 Pygame 程序都始于初始化和主循环。我们来创建main.py文件并搭建起游戏的基本骨架。3.1 初始化与游戏窗口# main.py import pygame import sys # 初始化pygame的所有模块 pygame.init() # 定义窗口尺寸与背景图尺寸一致 SCREEN_WIDTH 800 SCREEN_HEIGHT 600 # 创建游戏窗口 screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) # 设置窗口标题 pygame.display.set_caption(亚当找夏娃 - 超无聊的小游戏) # 游戏主循环控制变量 running True # 游戏主循环 while running: # 处理事件 for event in pygame.event.get(): # 如果点击了窗口关闭按钮则退出游戏 if event.type pygame.QUIT: running False # 更新屏幕显示目前是黑屏 pygame.display.flip() # 退出pygame pygame.quit() sys.exit()代码解释pygame.init(): 这是必须的第一步它初始化 Pygame 的所有导入模块。pygame.display.set_mode(): 创建指定大小的显示窗口返回一个Surface对象screen我们之后的所有绘制操作都基于这个 surface。pygame.event.get(): 获取当前帧中发生的所有事件如按键、鼠标移动、退出等。pygame.QUIT: 当用户点击窗口关闭按钮时触发的事件类型。pygame.display.flip(): 更新整个屏幕的显示。在游戏循环中它负责将我们在内存中绘制好的画面“翻”到屏幕上显示出来。pygame.quit()和sys.exit(): 退出游戏释放资源。运行这段代码你应该能看到一个大小为 800x600、标题为“亚当找夏娃”的黑色窗口。点击关闭按钮可以正常退出。3.2 加载与绘制游戏素材接下来我们把背景图和“夏娃”图加载进来并绘制到窗口上。# main.py (续接在初始化代码之后主循环之前) # 加载图片 # 注意图片路径需要根据你的实际文件位置调整 background_img pygame.image.load(assets/background.jpg).convert() eve_img pygame.image.load(assets/eve.png).convert_alpha() # 定义夏娃的初始位置例如坐标 (500, 300) eve_x 500 eve_y 300 # 获取夏娃图片的矩形对象用于定位和碰撞检测 eve_rect eve_img.get_rect(topleft(eve_x, eve_y))代码解释pygame.image.load(): 从文件路径加载一张图片返回一个Surface对象。.convert(): 将图片转换为与当前屏幕相同的像素格式可以显著提升后续的绘制速度。背景图通常使用这个。.convert_alpha(): 与convert()类似但会保留图片的透明通道Alpha通道。对于需要透明背景的“夏娃”图片必须使用这个方法。Surface.get_rect(): 返回一个Rect对象它代表了该 Surface 的矩形区域。topleft(x, y)参数可以同时设置矩形的位置。Rect对象在 Pygame 中非常重要用于定位、移动和碰撞检测。现在我们需要在主循环的绘制阶段将这两张图“画”到屏幕 Surface 上。# main.py (修改主循环内的绘制部分) while running: # 处理事件... for event in pygame.event.get(): if event.type pygame.QUIT: running False # --- 绘制阶段开始 --- # 1. 绘制背景图覆盖整个屏幕 screen.blit(background_img, (0, 0)) # 2. 绘制夏娃图到指定位置 screen.blit(eve_img, (eve_x, eve_y)) # 更新屏幕显示 pygame.display.flip() # --- 绘制阶段结束 ---代码解释Surface.blit(source, dest): 这是 Pygame 中最核心的绘制方法。source是要绘制的 Surface如图片dest是一个(x, y)坐标元组或一个Rect对象表示绘制的位置。绘制顺序很重要先画背景再画前景角色这样角色才会显示在背景之上。运行代码你应该能看到背景图并且“夏娃”出现在背景图的 (500, 300) 坐标位置。4. 实现鼠标交互与碰撞检测游戏目前是静态的。我们需要让玩家能与“夏娃”互动。4.1 获取鼠标状态与位置Pygame 提供了多种获取鼠标信息的方式。我们将在主循环中实时获取鼠标位置和按键状态。# main.py (在主循环开始处事件处理之前) while running: # 获取当前鼠标的位置 (x, y) mouse_pos pygame.mouse.get_pos() # 获取鼠标按键状态返回一个三元组代表左、中、右键是否被按下 mouse_pressed pygame.mouse.get_pressed() # 处理事件...4.2 实现简单的矩形碰撞检测我们需要判断鼠标光标一个点是否落在了“夏娃”的矩形区域内。Pygame 的Rect对象有一个非常方便的方法collidepoint()。# main.py (在主循环内获取鼠标信息之后绘制之前) # 碰撞检测判断鼠标位置是否在夏娃的矩形区域内 if eve_rect.collidepoint(mouse_pos): # 鼠标悬停在夏娃上可以给点视觉反馈比如改变光标或打印信息 # pygame.mouse.set_cursor(*pygame.cursors.diamond) # 示例改变光标形状 pass else: # 鼠标不在夏娃上恢复默认光标如果之前改变了的话 # pygame.mouse.set_cursor(*pygame.cursors.arrow) pass4.3 实现点击判定与游戏胜利我们不仅需要鼠标悬停还需要在悬停时检测到鼠标左键被按下。结合mouse_pressed和碰撞检测就能实现点击判定。# main.py (增加一个游戏状态变量) # 在游戏初始化部分定义游戏是否结束 game_over False # 可以加载一个胜利提示的字体 font pygame.font.SysFont(None, 48) # 使用系统默认字体大小48 # 在主循环内修改碰撞检测和事件处理部分 while running: mouse_pos pygame.mouse.get_pos() mouse_pressed pygame.mouse.get_pressed() for event in pygame.event.get(): if event.type pygame.QUIT: running False # 监听鼠标按下事件另一种方式与 mouse_pressed 状态变量等效 if event.type pygame.MOUSEBUTTONDOWN and event.button 1: # 1代表左键 if not game_over and eve_rect.collidepoint(event.pos): game_over True print(恭喜你找到了夏娃) # 也可以使用状态变量 mouse_pressed 在循环中检测持续按下 # if not game_over and mouse_pressed[0] and eve_rect.collidepoint(mouse_pos): # game_over True # print(恭喜你找到了夏娃) # --- 绘制阶段 --- screen.blit(background_img, (0, 0)) screen.blit(eve_img, (eve_x, eve_y)) # 如果游戏结束在屏幕上绘制胜利文字 if game_over: win_text font.render(恭喜亚当找到夏娃, True, (255, 215, 0)) # 金色文字 text_rect win_text.get_rect(center(SCREEN_WIDTH//2, SCREEN_HEIGHT//2)) screen.blit(win_text, text_rect) pygame.display.flip()代码解释pygame.mouse.get_pressed(): 返回一个三元组(左键, 中键, 右键)值为True或False表示当前帧这些按键是否被按住。pygame.MOUSEBUTTONDOWN: 这是一个事件类型当鼠标按键被按下时触发一次。event.button属性表示哪个按键1左键2中键3右键。这种方式更适合处理“点击”动作。pygame.font.SysFont(): 创建一个字体对象用于渲染文字。Font.render(text, antialias, color): 将文字渲染成一个新的 Surface 对象。antialias为True时开启抗锯齿文字边缘更平滑。我们添加了一个game_over布尔变量来控制游戏状态。当找到夏娃后将其设为True并绘制胜利文本。现在运行游戏当你用鼠标点击“夏娃”图片时屏幕中央应该会出现金色的胜利提示文字。5. 完整项目代码与优化将以上所有部分整合并做一些优化比如隐藏鼠标光标、增加随机初始位置等得到最终版本的main.py。# main.py - 亚当找夏娃1 完整代码 import pygame import sys import random # 用于随机放置夏娃 # 初始化 pygame.init() # 屏幕常量 SCREEN_WIDTH 800 SCREEN_HEIGHT 600 screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(亚当找夏娃1 - 超无聊的小游戏) # 颜色定义 (RGB) GOLD (255, 215, 0) WHITE (255, 255, 255) # 加载资源 try: background_img pygame.image.load(assets/background.jpg).convert() eve_img pygame.image.load(assets/eve.png).convert_alpha() except FileNotFoundError as e: print(f错误找不到资源文件 {e}) pygame.quit() sys.exit() # 随机生成夏娃的位置确保不会太靠边 eve_x random.randint(50, SCREEN_WIDTH - eve_img.get_width() - 50) eve_y random.randint(50, SCREEN_HEIGHT - eve_img.get_height() - 50) eve_rect eve_img.get_rect(topleft(eve_x, eve_y)) # 游戏状态 game_over False # 字体 font_large pygame.font.SysFont(None, 72) font_small pygame.font.SysFont(None, 36) # 隐藏系统鼠标光标我们将自己绘制一个自定义光标可选 # pygame.mouse.set_visible(False) # 游戏主循环 clock pygame.time.Clock() # 创建时钟对象用于控制帧率 running True while running: # 控制帧率60帧/秒 clock.tick(60) # 获取鼠标信息 mouse_pos pygame.mouse.get_pos() mouse_pressed pygame.mouse.get_pressed() # 事件处理 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 鼠标点击事件判定 if event.type pygame.MOUSEBUTTONDOWN and event.button 1: if not game_over and eve_rect.collidepoint(event.pos): game_over True # --- 绘制 --- # 绘制背景和夏娃 screen.blit(background_img, (0, 0)) screen.blit(eve_img, (eve_x, eve_y)) # 如果游戏未结束绘制提示文字和鼠标位置反馈 if not game_over: # 提示文字 hint_text font_small.render(移动鼠标找到并点击夏娃, True, WHITE) screen.blit(hint_text, (10, 10)) # 高亮反馈当鼠标悬停在夏娃上时绘制一个发光圈可选 if eve_rect.collidepoint(mouse_pos): # 绘制一个半透明的黄色圆圈 s pygame.Surface((eve_rect.width 20, eve_rect.height 20), pygame.SRCALPHA) pygame.draw.circle(s, (255, 255, 0, 128), (s.get_width()//2, s.get_height()//2), s.get_width()//2) screen.blit(s, (eve_rect.x - 10, eve_rect.y - 10)) else: # 游戏胜利画面 win_text font_large.render(胜利, True, GOLD) win_rect win_text.get_rect(center(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 - 50)) screen.blit(win_text, win_rect) found_text font_small.render(亚当终于找到了夏娃。, True, WHITE) found_rect found_text.get_rect(center(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 20)) screen.blit(found_text, found_rect) restart_text font_small.render(按 R 键重新开始按 ESC 键退出。, True, WHITE) restart_rect restart_text.get_rect(center(SCREEN_WIDTH//2, SCREEN_HEIGHT//2 70)) screen.blit(restart_text, restart_rect) # 绘制一个简单的自定义鼠标光标一个红色小圆圈 pygame.draw.circle(screen, (255, 50, 50), mouse_pos, 5) # 更新屏幕 pygame.display.flip() # 游戏结束后的按键处理放在绘制之后避免同一帧内逻辑混乱 if game_over: keys pygame.key.get_pressed() if keys[pygame.K_r]: # 按R键重置游戏 game_over False eve_x random.randint(50, SCREEN_WIDTH - eve_img.get_width() - 50) eve_y random.randint(50, SCREEN_HEIGHT - eve_img.get_height() - 50) eve_rect.topleft (eve_x, eve_y) if keys[pygame.K_ESCAPE]: # 按ESC键退出 running False # 退出游戏 pygame.quit() sys.exit()6. 运行与效果验证确保你的项目目录结构正确且assets文件夹中有对应的图片。在终端中进入find_eve目录运行命令python main.py游戏窗口弹出背景图显示夏娃随机隐藏在某个位置。移动鼠标屏幕上有一个红色小圆点作为光标在背景中寻找夏娃。当鼠标移动到夏娃身上时她会有一个淡黄色的光圈高亮视觉反馈。点击夏娃屏幕中央会显示金色的“胜利”字样以及操作提示。此时按R键可以重新开始游戏夏娃会换一个位置按ESC键可以直接退出游戏。7. 常见问题与排查思路在实现过程中你可能会遇到以下问题问题现象可能原因解决思路运行后黑屏无图片1. 图片路径错误。2. 图片文件名或扩展名拼写错误。3. 图片格式不被支持。1. 使用绝对路径或检查相对路径。print(os.path.exists(‘assets/background.jpg’))验证。2. 仔细检查文件名注意大小写。3. 尝试将图片转换为 JPG 或 PNG 格式。报错pygame.error: Couldn‘t open ...无法打开文件通常是路径问题。确保assets文件夹和main.py在同一目录下。或使用os.path.join(‘assets’, ‘background.jpg’)来构建路径。夏娃图片有白色背景加载图片时使用了.convert()而不是.convert_alpha()。对于 PNG 等带透明通道的图片必须使用.convert_alpha()方法加载。游戏窗口卡顿或闪烁绘制逻辑效率低或者没有控制帧率。1. 确保图片使用了.convert()。2. 使用clock.tick(60)限制帧率。3. 避免在循环中重复加载资源。点击没有反应1. 碰撞检测坐标错误。2. 事件处理逻辑有误比如在game_over后仍判断点击。3. 夏娃的Rect位置未更新。1. 打印mouse_pos和eve_rect的值进行调试。2. 检查if判断条件确保游戏未结束时才处理点击。3. 重置游戏时记得更新eve_rect.topleft。文字显示为方块或乱码系统缺少对应字体或字体名错误。使用pygame.font.get_fonts()查看可用字体列表换一个字体名或者使用None默认字体。8. 项目扩展与最佳实践这个基础版本已经可以运行但我们可以从工程化和可玩性角度进行扩展这也是学习的重要部分。8.1 代码结构优化使用类封装当游戏逻辑变复杂时将所有代码写在主循环里会难以维护。使用面向对象的思想进行重构是更好的实践。# game.py import pygame import random import sys class FindEveGame: def __init__(self): pygame.init() self.screen_width 800 self.screen_height 600 self.screen pygame.display.set_mode((self.screen_width, self.screen_height)) pygame.display.set_caption(亚当找夏娃 - 面向对象版) self.clock pygame.time.Clock() self.running True self.game_over False self.load_assets() self.reset_game() def load_assets(self): try: self.background pygame.image.load(assets/background.jpg).convert() self.eve_image pygame.image.load(assets/eve.png).convert_alpha() except FileNotFoundError as e: print(f资源加载失败: {e}) self.quit_game() self.font_large pygame.font.SysFont(None, 72) self.font_small pygame.font.SysFont(None, 36) def reset_game(self): 重置游戏状态随机放置夏娃 self.game_over False margin 50 self.eve_x random.randint(margin, self.screen_width - self.eve_image.get_width() - margin) self.eve_y random.randint(margin, self.screen_height - self.eve_image.get_height() - margin) self.eve_rect self.eve_image.get_rect(topleft(self.eve_x, self.eve_y)) def handle_events(self): for event in pygame.event.get(): if event.type pygame.QUIT: self.running False if event.type pygame.MOUSEBUTTONDOWN and event.button 1: if not self.game_over and self.eve_rect.collidepoint(event.pos): self.game_over True if event.type pygame.KEYDOWN: if event.key pygame.K_r and self.game_over: self.reset_game() if event.key pygame.K_ESCAPE: self.running False def update(self): 更新游戏逻辑本例中逻辑简单主要留给扩展 pass def draw(self): self.screen.blit(self.background, (0, 0)) self.screen.blit(self.eve_image, (self.eve_x, self.eve_y)) mouse_pos pygame.mouse.get_pos() if not self.game_over: hint self.font_small.render(找到并点击夏娃, True, (255, 255, 255)) self.screen.blit(hint, (10, 10)) if self.eve_rect.collidepoint(mouse_pos): # 绘制高亮效果 s pygame.Surface((self.eve_rect.width20, self.eve_rect.height20), pygame.SRCALPHA) pygame.draw.circle(s, (255,255,0,128), (s.get_width()//2, s.get_height()//2), s.get_width()//2) self.screen.blit(s, (self.eve_rect.x-10, self.eve_rect.y-10)) else: # 绘制胜利文本 win_text self.font_large.render(胜利, True, (255, 215, 0)) win_rect win_text.get_rect(center(self.screen_width//2, self.screen_height//2)) self.screen.blit(win_text, win_rect) # 绘制自定义光标 pygame.draw.circle(self.screen, (255, 50, 50), mouse_pos, 5) pygame.display.flip() def run(self): 游戏主循环 while self.running: self.clock.tick(60) self.handle_events() self.update() self.draw() self.quit_game() def quit_game(self): pygame.quit() sys.exit() if __name__ __main__: game FindEveGame() game.run()这样封装后游戏状态、资源、逻辑都被清晰地组织在类中后续要添加关卡、分数、音效等功能会容易得多。8.2 功能扩展建议增加关卡准备多张不同的背景图和多个隐藏位置每关随机选择一个。通关后进入下一关。添加计时与计分记录玩家找到夏娃所用的时间时间越短得分越高。使用pygame.time.get_ticks()获取毫秒时间戳来计算耗时。加入音效使用pygame.mixer模块在点击成功、游戏胜利时播放音效。制作开始/结束界面使用不同的游戏状态如MENU,PLAYING,GAME_OVER来管理界面切换。优化视觉反馈当鼠标靠近夏娃时可以让她微微晃动或者发出脉冲光晕增加提示性。8.3 工程实践要点资源管理将所有图片、声音、字体文件放在独立的assets目录下。可以使用字典来管理资源避免重复加载。路径处理使用os.path.join()或pathlib库来构建文件路径增强代码在不同操作系统上的可移植性。异常处理对资源加载如图片、字体进行try-except处理避免因个别资源缺失导致整个程序崩溃。配置常量将屏幕尺寸、颜色、速度等数值定义为模块顶部的常量方便统一调整。控制帧率始终使用Clock.tick(FPS)来控制游戏循环速度保证在不同性能的电脑上体验一致。通过这个“超无聊的小游戏”项目我们走完了从设计、编码到调试、优化的完整开发流程。虽然游戏本身简单但它涵盖了2D游戏开发最核心的循环事件处理、逻辑更新、画面渲染。掌握了这个框架你就可以尝试添加更多元素比如会移动的障碍物、多个寻找目标、道具系统等逐步把它变成一个真正“有聊”的游戏。动手修改代码加入你自己的创意是学习编程最好的方式。