最近在追《万妖最新季》,看到姜姐开局不杀妖那段,弹幕齐刷刷蹦出一句:“搁这玩蒙眼抓娘子呢?” 本来只是个玩梗,但我仔细一想,这个场景其实戳中了一个特别经典的玩法设定——玩家视野受限、目标在暗处游走、每一步都要靠判断去摸人。这种“蒙眼抓人”的玩法,放游戏里是个非常典型的小型交互原型,完全可以用 Python + pygame 实现。
所以这篇文章我们就干一件事:把“蒙眼抓娘子”完整做成一个可运行的小游戏。我会从玩法分析、pygame 核心概念、环境搭建、完整代码到常见报错全部走一遍,零基础也能跟着跑起来,有一点 Python 基础的开发者则可以顺手把项目结构、碰撞检测、视野机制这些思路直接抄进自己的项目里。
1. 玩法背后的游戏设计
1.1 什么是“蒙眼抓娘子”玩法
“蒙眼抓娘子”这个名字虽然是从剧情梗里来的,但它对应的游戏设计模式其实很常见:玩家在一个封闭地图中移动,但看不到全部场景,只能看到以自己为中心的一小块视野区域,目标角色则在地图里游走,玩家需要在接近后捕捉它们。
这类玩法的核心体验是“信息不对称”。玩家不知道目标在哪,不知道下一步会不会走错方向,只能在有限视野内做判断。这和传统 RPG 的“小地图”不同,小地图是辅助信息,而“蒙眼抓人”直接把地图信息遮掉,逼着玩家用空间记忆和移动策略去解决问题。
在实际项目中,这种机制常被用在以下场景:
- 恐怖游戏里的“盲区探索”;
- 非对称对抗游戏里的“追捕者视角受限”;
- 小游戏原型中的“抓人”、“寻找目标”玩法;
- 教学演示中的“视野裁剪”技术。
所以不要觉得这是个小梗,它背后的视野裁剪、目标游走、碰撞捕捉,都是游戏开发里的基础能力。
1.2 这类玩法需要解决的核心问题
把“蒙眼抓娘子”抽象成技术方案,我们需要处理四件事。
第一,视野怎么计算。最直观的方案是“圆形视野”:以玩家角色坐标为圆心,画一个固定半径的圆,只有落在这个圆内的目标才渲染出来,圆外的目标一律不绘制。
第二,角色怎么移动。玩家通过键盘控制上下左右,本质上就是把按键状态映射成 x 轴和 y 轴上的速度增量。
第三,碰撞怎么判断。角色之间不是像素级碰撞,而是两个圆形对象做“圆心距离判断”:当两个圆心距离小于两者半径之和,就认为碰撞发生。
第四,游戏状态怎么管理。一局游戏至少要有“进行中”和“胜利结束”两个状态,还要支持按键重置。
这四个问题都不难,但串联起来就是一个小型游戏框架,正好适合用 pygame 来做。
1.3 技术选型:为什么用 pygame
Python 做游戏开发常用的库有几个:pygame、arcade、cocos2d-python、panda3d。其中 pygame 是最适合入门和原型验证的。
原因有三点:
- 够轻量,安装一个依赖就能跑窗口、画图形、处理键盘事件;
- 文档和社区例子很多,遇到问题容易搜到解法;
- 它足够“底层”,能让你理解游戏循环、事件处理、碰撞检测是怎么工作的。
本文就用 pygame 2.x 来写。如果你的 Python 环境是 3.7 以上,都可以正常使用,推荐 3.10 或更新版本。
2. 环境准备与项目结构
2.1 环境准备
先检查 Python 版本,在命令行执行:
python --version如果还没安装 pygame,执行:
pip install pygame安装完成后可以用一段简单的命令验证是否成功:
python -m pygame.examples.aliens能弹出一个 pygame 示例窗口,就说明环境没问题。如果这个命令提示找不到模块,说明 pygame 没有安装成功,重新执行一次 pip 安装即可。
部分国内网络环境下 pip 可能比较慢,可以顺手换镜像源,例如:
pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple版本方面不需要纠结,pygame 2.x 都可以。如果之前装过旧版 pygame 1.9,建议先卸掉再装新版:
pip uninstall pygame pip install pygame因为 1.9 和 2.x 在字体、坐标、特殊绘制标志等 API 上存在差异,本文代码以 pygame 2.x 为准。
2.2 项目结构
为了演示方便,我们把所有代码放在一个单文件里。项目目录如下:
wan_yao_game/ ├── main.py └── requirements.txtrequirements.txt 内容很简单:
pygame>=2.0.0这样结构清晰,方便你以后拆分成多文件版本。
2.3 IDE 与运行建议
推荐使用 PyCharm 或 VS Code 进行开发。PyCharm 的好处是自带运行按钮和调试器等,VS Code 则有很好的 Python 插件支持。
运行方式很简单:
python main.py一定要在项目根目录下运行,确保当前工作目录正确。
3. pygame 核心概念拆解
在放完整代码之前,先把 pygame 的几个核心概念讲清楚,这样你后面看代码不会一头雾水。
3.1 游戏循环
pygame 程序本质上是一个“死循环”,每次循环做三件事:
- 遍历事件队列;
- 更新游戏状态;
- 重新绘制画面。
事件队列里包含窗口关闭、键盘按下、鼠标移动等输入。游戏状态包括角色坐标、目标是否被抓到、是否胜利等。
这个循环会一直执行,直到用户关闭窗口或者程序主动退出。帧率由clock.tick(FPS)控制,意思是每秒最多执行多少次循环。建议设成 60,画面会比较流畅。
3.2 坐标系与移动
pygame 的坐标系原点是窗口左上角,x 轴向右,y 轴向下。所以“向上移动”实际上是 y 坐标减小,“向下移动”是 y 坐标增大。
角色移动的通用思路是:通过pygame.key.get_pressed()拿到每一帧的按键状态,然后计算出 vx 和 vy,最后把坐标更新为:
x += vx y += vy用get_pressed()的好处是不需要额外维护按键按下和抬起的逻辑,当你一直按着方向键时,角色就会连续移动。
3.3 距离计算与碰撞检测
圆形碰撞是游戏开发里最简单也最实用的碰撞方式。两个圆是否碰撞,只看圆心之间的距离是否小于两个圆的半径之和。
距离计算用 Python 标准库的math.hypot即可:
import math distance = math.hypot(x1 - x2, y1 - y2)如果distance < r1 + r2,则碰撞成立。
这个判断方式比使用矩形碰撞更符合直觉,尤其适合圆形角色、圆形障碍物。对于边界判断,则只需要把坐标限制在窗口尺寸以内。
3.4 视野机制
实现“蒙眼”效果最简单的办法不是去遮罩画面,而是直接控制渲染内容:只有距离玩家小于某个半径的目标,才被绘制出来,否则跳过绘制。
这意味着目标其实一直存在、一直在移动,只是玩家看不到它。这种“存在但不可见”的设计思路,非常适合游戏原型,也很容易解释清楚。后续如果你想做更精致的“光照遮罩”,可以基于半透明 Surface 和 alpha 混合去挖洞,不过本文先不展开那么深。
4. 完整实战:实现“蒙眼抓娘子”
4.1 需求设计
我们的游戏规则如下:
- 玩家控制角色“姜姐”在地图中移动;
- 场上存在 3 个“娘子”,它们会随机游走并碰到屏幕边界反弹;
- 玩家角色只能看到以自己为中心、半径 130 像素范围内的区域;
- 玩家碰到娘子后,娘子被抓住并从场上消失;
- 抓住全部 3 个娘子后,游戏胜利;
- 按 R 键重新开始,按 ESC 或关闭窗口退出游戏。
为了增加一点操作感,我还会在地图中添加几个静态圆形障碍物,玩家撞到障碍物后无法穿过。这样游戏不会变成“闭眼乱跑也能赢”,策略性会强一点。
4.2 定义常量和辅助函数
我们把窗口大小、帧率、速度、视野半径、障碍物坐标都抽成常量。这样想调整数值时,只需要改最上面一块,不用在代码里到处找魔法数字。
以下是核心常量:
SCREEN_WIDTH = 900 SCREEN_HEIGHT = 600 FPS = 60 PLAYER_SPEED = 5 TARGET_SPEED = 3 VIEW_RADIUS = 130 TOTAL_TARGETS = 3 BACKGROUND_COLOR = (20, 24, 34) PLAYER_COLOR = (80, 200, 255) TARGET_COLOR = (255, 180, 220) OBSTACLE_COLOR = (120, 120, 140) TEXT_COLOR = (255, 255, 255) WIN_COLOR = (255, 220, 100)障碍物是一个简单的坐标元组列表,每个元组是(x, y, 半径):
OBSTACLES = [ (250, 200, 40), (650, 400, 35), (450, 150, 30), ]然后写一个距离工具函数:
def distance(x1, y1, x2, y2): return math.hypot(x1 - x2, y1 - y2)这里单独抽出函数是为了在多个地方复用,不要每次手动写math.hypot。
4.3 玩家类 Player
Player 类负责保存玩家坐标、半径、速度方向,并提供移动和边界限制方法。
class Player: def __init__(self, x, y): self.x = x self.y = y self.r = 18 self.speed = PLAYER_SPEED self.vx = 0 self.vy = 0 def set_speed(self, vx, vy): self.vx = vx self.vy = vy def update(self): self.x += self.vx self.y += self.vy self.x = max(self.r, min(SCREEN_WIDTH - self.r, self.x)) self.y = max(self.r, min(SCREEN_HEIGHT - self.r, self.y))set_speed负责接收主循环里计算好的速度,update负责更新坐标并限制在窗口边界内。这里使用max/min组合来限制边界,写法简洁且不容易出错。
4.4 目标类 Target
Target 类表示一个“娘子”。它保存位置、半径、移动速度,以及是否已经被抓住。被抓住后我们不需要立即从列表里删除,而是通过caught标记跳过更新和绘制。
class Target: def __init__(self, x, y): self.x = x self.y = y self.r = 15 self.dx = TARGET_SPEED * random.choice([-1, 1]) self.dy = TARGET_SPEED * random.choice([-1, 1]) self.caught = False @property def active(self): return not self.caught def update(self): if not self.active: return self.x += self.dx self.y += self.dy if self.x < self.r or self.x > SCREEN_WIDTH - self.r: self.dx *= -1 self.x = max(self.r, min(SCREEN_WIDTH - self.r, self.x)) if self.y < self.r or self.y > SCREEN_HEIGHT - self.r: self.dy *= -1 self.y = max(self.r, min(SCREEN_HEIGHT - self.r, self.y))active属性让代码可读性更好,判断是否被抓住时直接写if target.active:,比到处写if not target.caught:清晰很多。
4.5 初始化游戏与字体
重置游戏时,需要创建玩家和 3 个目标。目标生成位置要尽量远离玩家,避免开局就莫名其妙碰上。
def reset_game(): player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2) targets = [] for _ in range(TOTAL_TARGETS): while True: x = random.randint(80, SCREEN_WIDTH - 80) y = random.randint(80, SCREEN_HEIGHT - 80) if distance(x, y, player.x, player.y) > 120: break targets.append(Target(x, y)) return player, targets字体部分单独封装一个函数,优先从系统里查找中文字体,避免中文标签显示成方块。
def load_font(size): for name in ["microsoftyahei", "simhei", "notosanscjksc", "wenquanyimicrohei"]: path = pygame.font.match_font(name) if path: return pygame.font.Font(path, size) return pygame.font.Font(None, size)这个函数在 Windows、macOS、Linux 上都能兜底。如果找不到任何中文字体,就退回 pygame 默认字体,但此时中文可能会显示为方块,所以常见问题里我会单独说明。
4.6 绘制函数与视野光圈
绘制部分,我单独抽出一个“视野光圈”函数。它是一个半透明圆环,用来告诉玩家“你现在只能看清楚这个范围”。
def draw_circle_glow(screen, center, radius): x, y = center glow = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA) pygame.draw.circle(glow, (255, 255, 255, 40), (radius, radius), radius) pygame.draw.circle(glow, (255, 255, 255, 120), (radius, radius), max(4, radius // 4)) screen.blit(glow, (int(x - radius), int(y - radius)))这里pygame.SRCALPHA表示创建带透明通道的 Surface,这样画出的是半透明光圈,叠加到深色背景上会有微弱的发光效果。
游戏中的“蒙眼”逻辑在绘制阶段实现,判断逻辑如下:
if distance(player.x, player.y, t.x, t.y) <= VIEW_RADIUS: # 绘制娘子也就是说,娘子跑出视野半径后,直接从屏幕上消失,但它的坐标还在持续更新。玩家需要移动视野去重新发现它。
4.7 主函数与游戏循环
主函数的流程是:
- 初始化 pygame;
- 创建窗口、时钟、字体;
- 调用
reset_game()初始化角色; - 进入游戏循环;
- 处理退出、重置、移动输入;
- 更新所有角色;
- 检测碰撞并更新游戏状态;
- 绘制背景、障碍物、可见目标、玩家、视野光圈和文字;
- 刷新画面并控制帧率。
完整代码如下,可以直接复制保存到main.py:
import pygame import random import math # ===== 游戏常量 ===== SCREEN_WIDTH = 900 SCREEN_HEIGHT = 600 FPS = 60 PLAYER_SPEED = 5 TARGET_SPEED = 3 VIEW_RADIUS = 130 TOTAL_TARGETS = 3 BACKGROUND_COLOR = (20, 24, 34) PLAYER_COLOR = (80, 200, 255) TARGET_COLOR = (255, 180, 220) OBSTACLE_COLOR = (120, 120, 140) TEXT_COLOR = (255, 255, 255) WIN_COLOR = (255, 220, 100) OBSTACLES = [ (250, 200, 40), (650, 400, 35), (450, 150, 30), ] def distance(x1, y1, x2, y2): return math.hypot(x1 - x2, y1 - y2) class Player: def __init__(self, x, y): self.x = x self.y = y self.r = 18 self.speed = PLAYER_SPEED self.vx = 0 self.vy = 0 def set_speed(self, vx, vy): self.vx = vx self.vy = vy def update(self): self.x += self.vx self.y += self.vy self.x = max(self.r, min(SCREEN_WIDTH - self.r, self.x)) self.y = max(self.r, min(SCREEN_HEIGHT - self.r, self.y)) class Target: def __init__(self, x, y): self.x = x self.y = y self.r = 15 self.dx = TARGET_SPEED * random.choice([-1, 1]) self.dy = TARGET_SPEED * random.choice([-1, 1]) self.caught = False @property def active(self): return not self.caught def update(self): if not self.active: return self.x += self.dx self.y += self.dy if self.x < self.r or self.x > SCREEN_WIDTH - self.r: self.dx *= -1 self.x = max(self.r, min(SCREEN_WIDTH - self.r, self.x)) if self.y < self.r or self.y > SCREEN_HEIGHT - self.r: self.dy *= -1 self.y = max(self.r, min(SCREEN_HEIGHT - self.r, self.y)) def reset_game(): player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2) targets = [] for _ in range(TOTAL_TARGETS): while True: x = random.randint(80, SCREEN_WIDTH - 80) y = random.randint(80, SCREEN_HEIGHT - 80) if distance(x, y, player.x, player.y) > 120: break targets.append(Target(x, y)) return player, targets def load_font(size): for name in ["microsoftyahei", "simhei", "notosanscjksc", "wenquanyimicrohei"]: path = pygame.font.match_font(name) if path: return pygame.font.Font(path, size) return pygame.font.Font(None, size) def draw_circle_glow(screen, center, radius): x, y = center glow = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA) pygame.draw.circle(glow, (255, 255, 255, 40), (radius, radius), radius) pygame.draw.circle(glow, (255, 255, 255, 120), (radius, radius), max(4, radius // 4)) screen.blit(glow, (int(x - radius), int(y - radius))) def start_game(): pygame.init() screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("蒙眼抓娘子 - 万妖最新季同款小游戏") clock = pygame.time.Clock() font = load_font(30) player, targets = reset_game() caught_count = 0 win = False running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_r: player, targets = reset_game() caught_count = 0 win = False elif event.key == pygame.K_ESCAPE: running = False keys = pygame.key.get_pressed() vx = 0 vy = 0 if keys[pygame.K_LEFT] or keys[pygame.K_a]: vx = -PLAYER_SPEED if keys[pygame.K_RIGHT] or keys[pygame.K_d]: vx = PLAYER_SPEED if keys[pygame.K_UP] or keys[pygame.K_w]: vy = -PLAYER_SPEED if keys[pygame.K_DOWN] or keys[pygame.K_s]: vy = PLAYER_SPEED player.set_speed(vx, vy) old_x, old_y = player.x, player.y player.update() for ox, oy, or_ in OBSTACLES: if distance(player.x, player.y, ox, oy) < player.r + or_: player.x, player.y = old_x, old_y break for t in targets: t.update() if not win: for t in targets: if t.active and distance(player.x, player.y, t.x, t.y) < player.r + t.r: t.caught = True caught_count += 1 if caught_count >= TOTAL_TARGETS: win = True screen.fill(BACKGROUND_COLOR) for ox, oy, or_ in OBSTACLES: if distance(player.x, player.y, ox, oy) <= VIEW_RADIUS: pygame.draw.circle(screen, OBSTACLE_COLOR, (int(ox), int(oy)), or_) for t in targets: if not t.active: continue if distance(player.x, player.y, t.x, t.y) <= VIEW_RADIUS: pygame.draw.circle(screen, TARGET_COLOR, (int(t.x), int(t.y)), t.r) label = font.render("娘子", True, TEXT_COLOR) screen.blit(label, (int(t.x) - label.get_width() // 2, int(t.y) - t.r - 28)) pygame.draw.circle(screen, PLAYER_COLOR, (int(player.x), int(player.y)), player.r) label = font.render("姜姐", True, TEXT_COLOR) screen.blit(label, (int(player.x) - label.get_width() // 2, int(player.y) - player.r - 28)) draw_circle_glow(screen, (int(player.x), int(player.y)), VIEW_RADIUS) info = font.render(f"已抓住:{caught_count}/{TOTAL_TARGETS}", True, TEXT_COLOR) screen.blit(info, (20, 20)) if win: win_text = font.render("全部抓住!按 R 重新开始", True, WIN_COLOR) screen.blit(win_text, (SCREEN_WIDTH // 2 - win_text.get_width() // 2, 90)) pygame.display.flip() clock.tick(FPS) pygame.quit() if __name__ == "__main__": start_game()4.8 运行与预期效果
在项目目录下执行:
python main.py窗口启动后你会看到:深蓝色背景中有一个发光的圆形视野区域,中心是玩家角色“姜姐”;玩家按下方向键或 WASD 键即可移动;地图中有几个深灰色障碍物,玩家碰到障碍物会停止前进;3 个“娘子”在地图中随机游走,但只有进入玩家视野区域时才会被看到。
当你控制玩家碰到一个娘子时,该娘子会被标记为已抓住并消失,左上角计数加 1。当计数达到 3 时,屏幕中间显示胜利提示,按 R 键可以重新开局。
如果运行时窗口标题和角色名称都能正常显示中文,说明字体加载成功。如果显示为方块,说明系统没有匹配到中文字体,此时需要安装中文字体或修改load_font函数里的字体名称列表。
5. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
运行报错ModuleNotFoundError: No module named 'pygame' | pygame 未安装或安装到了错误环境 | 先执行pip install pygame,再用python -m pygame.examples.aliens验证 |
| 中文标签显示为方块 | pygame 默认字体不支持中文 | 安装微软雅黑、宋体或 Noto Sans CJK 字体,并让load_font能找到字体路径 |
| 窗口一闪而过就退出了 | 代码里pygame.quit()后没有sys.exit(),或者运行前没有事件循环 | 检查事件处理逻辑,确保while running循环正常执行 |
| 按键没有反应 | 键盘事件获取方式错误 | 优先使用pygame.key.get_pressed()处理连续移动,不要只在KEYDOWN里修改坐标 |
| 角色移动太快或太慢 | 没有控制帧率,或速度常量设置不合理 | 用clock.tick(FPS)锁帧,并统一调整PLAYER_SPEED和TARGET_SPEED |
| 碰撞判断偶尔漏掉 | 角色单帧移动距离超过目标半径之和 | 提高 FPS 或限制单帧最大移动速度,也可对碰撞做插值检测 |
| 目标开局就撞上玩家 | 目标生成位置没有做距离约束 | 参考reset_game中的 while 循环,确保生成点与玩家保持距离 |
| 障碍物无法阻挡玩家 | 移动后没有检测障碍物,或者检测后没有回退 | 保存移动前坐标,检测到碰撞时将坐标回退到旧位置 |
如果你在运行过程中遇到上面没覆盖的问题,可以先做两件事:第一,把完整报错信息复制下来;第二,检查 Python 和 pygame 版本是否匹配。再不行就逐步打印玩家坐标、目标坐标、距离值,确认逻辑在哪一步断了。
6. 最佳实践与工程建议
6.1 常量和配置管理
上面的代码把所有数值都放在文件顶部,这是单文件原型项目的好习惯。窗口大小、帧率、颜色、速度、障碍物位置都应该是显式常量,而不是散落在代码里的魔法数字。
如果项目继续变大,可以把这些常量抽到独立的config.py,例如:
# config.py WINDOW_WIDTH = 900 WINDOW_HEIGHT = 600 FPS = 60 VIEW_RADIUS = 130这样一来调整数值就非常安全,不会意外改到业务逻辑。
6.2 类职责划分
Player、Target、主游戏循环的职责要做到基本分离。Player 只负责自己的移动和边界限制,Target 只负责游走和被抓状态,主循环负责输入、碰撞、绘制调度。
很多初学者会忍不住把所有逻辑写进一个 while 循环,导致代码越来越长。我建议至少从本文这个阶段开始,养成“每个类只干一件事”的习惯,后面做大型项目时会轻松很多。
6.3 碰撞检测的精度问题
本文的碰撞检测是每帧判断一次。在 60 FPS 下,玩家单帧移动距离只有 5 像素左右,目标也移动 3 像素左右,圆形碰撞基本不会漏判。
但如果游戏角色未来速度提高到十几像素每帧,就可能出现“上一帧还没碰,下一帧已经穿过去”的问题。解决方法有两种。
第一种是限制移动步长,例如每帧最多移动 2 像素。第二种是做分段移动,把一次大移动拆成多次小位移,逐段做碰撞检测。
# 分段移动示例 steps = max(abs(vx), abs(vy), 1) step_x = vx / steps step_y = vy / steps for _ in range(int(steps)): player.x += step_x player.y += step_y # 每段都做碰撞检测这种思路在迷宫、贪吃蛇、平台跳跃里都非常常见。
6.4 视野与性能优化
现在地图里只有 3 个目标,视野判断的循环次数很少。但如果之后目标数量增加到几百个,就可以插入空间网格或四叉树做优化,把坐标按格子划分,只检测玩家附近的格子。
对于本文这种小游戏,完全没必要过度设计。但有一个性能习惯应该从第一天就养成:不要在游戏循环里每次都创建新的 Surface 或加载字体、图片,应该把昂贵的资源提前加载好。
6.5 可玩性扩展方向
做完这个基础版本后,扩展方向其实非常多。
给娘子增加“警觉值”:如果玩家进入某个范围,娘子会加速逃跑,玩家需要预判走位。
增加“脚步声”机制:玩家持续移动时会产生声音提示,声音越大,盲区中的角色越容易感知到玩家方向,形成反向博弈。
把单人玩法改成双人“捉迷藏”:一个玩家蒙眼,另一个玩家操作娘子逃跑,这就变成小型非对称对抗游戏。
增加地图道具:例如“火把”可以临时扩大视野、“定身符”可以让所有娘子停止移动几秒。
这些扩展不仅能提升游戏性,也能帮助你练习更多 pygame 能力,比如音效播放、动画状态机、计时器系统、AI 寻路等。
6.6 调试建议
开发游戏时不要直接猜 bug,建议在代码里加一个调试开关:
DEBUG = True if DEBUG: # 绘制视野圆的白色边缘 pygame.draw.circle(screen, (255, 255, 255), (int(player.x), int(player.y)), VIEW_RADIUS, 1) # 绘制所有目标位置,即使不可见也画成小点 for t in targets: pygame.draw.circle(screen, (255, 0, 0), (int(t.x), int(t.y)), 3)调试模式下把不可见目标标成红点,视野边缘画一圈白线,能快速看出“看不见”到底是不是逻辑问题。等调试完成后再关掉DEBUG开关。
7. 总结与下一步学习建议
这篇文章从《万妖最新季》的一个梗切入,把“蒙眼抓娘子”做成了一个可运行的 pygame 小游戏。你学会了游戏循环、键盘控制、坐标更新、圆形碰撞检测、视野裁剪、状态重置这几个核心模块,也掌握了一个完整的单文件游戏项目结构。
下一步建议你别急着换框架,先把三个方向练熟:
第一个方向,把“娘子”从随机游走改成“看到玩家就跑”。这需要你增加一个追击状态机,比如空闲、警觉、逃跑、被抓,每种状态对应不同移动逻辑。这个练习能提升你对状态管理的理解。
第二个方向,尝试增加更多地图元素,比如围墙、传送门、道具。你会发现碰撞检测从“圆形对圆形”变成“圆形对矩形”,处理方式会有变化。
第三个方向,把单文件拆分成真正的多模块项目,例如main.py、player.py、target.py、config.py。拆分过程中你会逐渐理解模块之间的依赖关系,这对以后接触大型项目很有帮助。
如果棋盘上的代码哪里跑不通,优先用添加print的方式定位问题。游戏开发和技术学习没有捷径,多跑、多改、多调试就会越来越熟。希望这篇实战笔记能帮你把 pygame 的基础能力真正落到自己的代码里。