ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

pygame矩形移动实战:从游戏循环到碰撞检测

pygame矩形移动实战:从游戏循环到碰撞检测 【黎】Day3 矩形移动是这套游戏开发学习记录系列里第一个真正的交互练习。前两天把窗口打开、把图形画上去到了 Day3要做的事情就从“静态展示”变成“实时响应”按下方向键一个矩形在窗口里跟着移动。这个看似简单的功能实际上把游戏循环、事件监听、坐标更新、边界判断、渲染刷新一整条链路打通了后续所有 2D 小游戏的交互基础都在这里。这篇文章直接把 Day3 的题目展开成一个完整实现从环境准备开始到键盘控制矩形移动再到多个矩形自动移动、反弹和碰撞检测每一步都给出可运行的代码和验证方法。如果你正在照着学习日志学 pygame这文章可以直接对照着做。读完你能拿起键盘控制一个矩形把它限制在窗口范围内并且理解“为什么这样写会动、那样写会拖影”。先给一个整体判断矩形移动的代码量不大但它覆盖了游戏开发里最高频的几个知识点。这篇文章后面会依次讲核心实现思路、环境准备、第一个可运行版本、功能测试、代码封装、碰撞扩展和排查清单级别从零基础到能自己改着玩。适合刚学完 pygame 绘图、想进入交互阶段的人也适合做课程设计时快速补游戏循环常识的人。1. 核心能力速览先看一张总表把矩形移动这个练习的边界和能力看清楚再决定要不要往下跑。能力项说明技术栈Python 3 pygame核心功能键盘控制矩形移动、自动移动、边界限制、碰撞检测运行方式本地 Python 脚本直接运行无 Web 界面资源占用极低普通开发机运行无压力是否支持 API否不涉及 HTTP 接口服务是否支持批量任务否但多矩形对象更新可以看作轻量批量对象管理涉及知识点游戏循环、事件监听、坐标更新、Rect 碰撞、帧率控制适合读者pygame 入门者、游戏开发新手、第一次接触实时交互的同学对这个练习来说“能不能跑起来”比“功能多不多”重要。整篇文章的重心也放在从“能画一个矩形”走到“能控制一个矩形动起来”这条完整链路上前面五节讲的是让代码跑通后面几节讲的是把代码写得更像工程。有一点需要提前说明网上很多教程会把矩形移动直接写成一段“复制就能跑”的整块代码但那只是结果。理解为什么矩形会动、为什么会有拖影、为什么速度忽快忽慢才是真正值钱的部分这也是本文每一节都保留验证步骤的原因。2. 矩形移动的实现思路与适用场景先讲原理再讲场景。pygame 里的所有运动本质上都是坐标变化。窗口是一块平面画布矩形显示在哪里由它的左上角坐标x, y决定。所谓移动就是在每一帧刷新前修改这个坐标然后重新绘制整个画面。因为人眼对连续帧有暂留效应只要帧率足够高就会产生“物体在动”的错觉。标准游戏循环通常有四步处理事件有没有按键盘、有没有点击关闭按钮。更新逻辑根据输入或游戏规则修改坐标、速度等状态。绘制画面把背景和物体画到屏幕上。控制帧率并刷新显示。# 伪代码游戏循环骨架 while running: handle_events() # 1. 处理事件 update_logic() # 2. 更新坐标 draw() # 3. 绘制画面 clock.tick(60) # 4. 控制帧率这个骨架几乎适用于所有 2D 小游戏。矩形移动的本质就是“按方向键 - 修改坐标 - 重绘矩形”这条链路跑通。这里有一个关键点事件处理和逻辑更新是分开的。很多新手习惯在事件循环里直接改坐标形式上看好像也“动”了但一旦要加碰撞、加计时、加多物体这样写的代码就会立刻乱掉。从适用场景看矩形移动适合这几种情况零基础学 pygame 的过渡任务验证自己是否真的理解游戏循环。第一次接触“实时交互”想感受键盘输入如何影响屏幕输出的人。做挡板游戏、贪吃蛇、飞机大战之前的热身练习。课程作业里需要演示“物体运动 边界控制”的最小案例。不适合的场景也要说清楚如果目标是做完整小游戏比如带多场景切换、复杂物理、粒子特效的项目矩形移动只是一个起点。后续还需要引入 Sprite、Group、场景栈、资源加载等机制Day3 的几行代码只是地基。认清这个边界能避免在错误的地方花太多时间。坐标方向也需要确认一次。pygame 的坐标系原点在窗口左上角x 轴向右递增y 轴向下递增。也就是说键盘的“上”对应的不是 y 减小方向的直觉而是“y 坐标减去一个值”按键坐标变化视觉结果右键x speed向右移动左键x - speed向左移动下键y speed向下移动上键y - speed向上移动这段坐标方向的内容虽然简单但值得记牢因为后面做碰撞反弹、地图控制、镜头跟随全部绕不开“y 轴向下为正”这个约定。3. 环境准备与前置条件写移动逻辑之前先确认运行环境。矩形移动练习对硬件几乎没有要求普通笔记本即可也不需要独立显卡。需要的条件操作系统Windows、macOS、Linux 都可以。Python 3.8 或更高版本推荐 3.10 以上。pygame 库通过 pip 安装。一个能运行 GUI 窗口的桌面环境。如果是无桌面的云服务器运行时会报视频设备错误。先确认 Python 版本python --versionWindows 下如果python命令不可用可以试py -V然后建议在项目目录创建一个虚拟环境把 pygame 装到隔离环境里避免污染全局 Pythonpython -m venv venv # Windows 激活虚拟环境 venv\Scripts\activate # macOS / Linux 激活虚拟环境 source venv/bin/activate激活后安装 pygamepip install pygame安装完成后用一行命令验证python -c import pygame; print(pygame.version.ver)如果能输出版本号说明 pygame 已经装好可以继续写矩形移动的脚本。如果这里报ModuleNotFoundError先检查是否还在虚拟环境里再检查pip是不是对应当前 Python。这个问题在第八节的排查表里也会出现。4. 第一个可运行版本键盘控制矩形移动这是 Day3 的核心结果屏幕上有一个矩形按方向键矩形跟着移动。新建一个文件建议命名为rect_move.py把下面的代码完整保存进去。import pygame import sys def main(): pygame.init() screen pygame.display.set_mode((800, 600)) pygame.display.set_caption(Day3 矩形移动) clock pygame.time.Clock() # 矩形初始位置和大小 x, y 100, 100 width, height 60, 40 speed 5 running True while running: # 1. 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 2. 读取键盘状态并更新坐标 keys pygame.key.get_pressed() if keys[pygame.K_LEFT]: x - speed if keys[pygame.K_RIGHT]: x speed if keys[pygame.K_UP]: y - speed if keys[pygame.K_DOWN]: y speed # 3. 绘制 screen.fill((30, 30, 30)) pygame.draw.rect(screen, (0, 200, 255), (x, y, width, height)) pygame.display.flip() # 4. 控制帧率 clock.tick(60) pygame.quit() sys.exit() if __name__ __main__: main()运行方式python rect_move.py这段代码有几个关键点值得拆开讲。第一pygame.key.get_pressed()返回的是当前所有按键的状态所以这段代码实现的是“按住方向键持续移动”而不是“按一下动一下”。两者在游戏里的用途完全不同。持续移动适合角色行走单次触发适合菜单选择。后面如果需要“按一下切换一次状态”要改用KEYDOWN事件for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: if event.key pygame.K_SPACE: print(按下空格键)第二screen.fill((30, 30, 30))的作用是每帧先把背景刷成深灰色覆盖上一帧的矩形。如果不填充背景新帧的矩形会画在旧帧矩形上面屏幕上留下一串拖影。这是新手最容易遇到的问题之一。第三clock.tick(60)把帧率限制在 60 FPS。没有这一步程序会以 CPU 能跑多快就画多快不同机器的矩形速度完全不同换台机器运行体验就会变。固定帧率的意义在于让移动速度和屏幕刷新频率解耦。第四代码用了main()函数入口和if __name__ __main__:保护。这对一个小练习来说不是必须的但养成这个习惯后以后代码被导入到别的模块时不会误执行游戏循环。5. 功能测试与效果验证代码能跑只是第一步。作为 Day3 练习建议做几组简单的功能测试确认移动逻辑在不同操作下都符合预期。这里给出一套完整的验证流程符合“先小参数测试再观察效果”的思路。5.1 基础按键测试操作预期结果按住右键矩形向右连续移动按住左键矩形向左连续移动按住上键矩形向上连续移动按住下键矩形向下连续移动同时按上和右矩形向右上方向移动松开按键矩形立即停止移动尤其推荐测“同时按两个方向键”。get_pressed()会同时返回多个按键的状态只要代码里用的是独立的if而不是elif两个方向的位移会叠加矩形就会沿斜线移动。这是符合预期的行为也是检查条件分支写法的一个有效方法。测完这四个方向后把speed从 5 改成 2 或 10再跑一次。如果速度变化明显说明坐标更新逻辑走的是同一套流程没有写死在哪里。5.2 帧率与速度验证把clock.tick(60)改成clock.tick(30)再跑一次。正常情况下矩形看起来变慢但是不卡顿。这说明帧率控制真实生效。再把固定值移动改成按时间移动这是更推荐的做法dt clock.tick(60) / 1000.0 # 上一帧耗时单位秒 speed 300 # 像素/秒 x speed * dt这样写的好处是即使帧率在 50 到 60 之间波动矩形的实际移动速度也基本稳定。而“固定 5 像素 60 FPS”的写法在 144Hz 显示器上会明显更快。Day3 阶段只需要理解两种写法的差别正式做项目时推荐直接使用基于dt的方式。5.3 判断是否成功完成测试后用下面几条标准判断练习是否通过窗口能正常打开并保持运行不闪退。按住方向键时矩形连续移动松开后立即停止。屏幕没有明显拖影。修改speed后移动速度有对应变化。关闭窗口后进程正常退出控制台没有残留异常。5.4 失败时的排查思路如果某一步没达到预期先不要急着改代码按照“现象 - 定位 - 修复”的顺序走窗口一闪而退多半是while循环之前就抛出了异常看控制台 Traceback 的最后几行。矩形不动检查get_pressed()后面的条件写没写对是否把多个方向写成了elif导致部分方向失效。画面闪烁或拖影确认是否每帧调用了screen.fill()以及flip()是否在绘制之后调用。速度异常快确认clock.tick(60)是否在循环内、是否被注释掉。6. 逻辑封装把移动能力做成可复用模块矩形移动做到这里已经解决了“能不能动”。但直接用x、y变量写逻辑矩形一多就会乱。Day3 可以延伸一步把移动能力抽成一个Rectangle类。这里要说明一下这个练习不涉及 HTTP 接口和批量任务但“封装对象 批量更新多个对象”的思路和后面做精灵管理是一致的可以看作给游戏对象提供了一套可复用接口。先定义一个矩形类import pygame class Rectangle: def __init__(self, x, y, width, height, color, vx0, vy0): self.rect pygame.Rect(x, y, width, height) self.color color self.vx vx # x 方向速度 self.vy vy # y 方向速度 def move(self): self.rect.x self.vx self.rect.y self.vy def draw(self, surface): pygame.draw.rect(surface, self.color, self.rect)封装之后主循环变得清晰很多rect Rectangle(100, 100, 60, 40, (0, 200, 255), vx0, vy0) running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False keys pygame.key.get_pressed() if keys[pygame.K_LEFT]: rect.vx -5 elif keys[pygame.K_RIGHT]: rect.vx 5 elif keys[pygame.K_UP]: rect.vy -5 elif keys[pygame.K_DOWN]: rect.vy 5 else: rect.vx, rect.vy 0, 0 rect.move() screen.fill((30, 30, 30)) rect.draw(screen) pygame.display.flip() clock.tick(60)这里有一个明显变化方向键不再直接改坐标而是先把速度vx、vy设置好再由move()统一应用。这样做的好处是后续加加速度、加反弹、加碰撞时只需要修改move()里面的逻辑不用动按键处理部分。使用pygame.Rect而不是直接存x、y还有一个长期收益Rect 自带colliderect()、contains()、union()等碰撞和区域方法。后面做碰撞检测时直接调用不需要手写坐标比较。6.1 为什么说这是一套可复用接口从接口设计的角度看Rectangle类对外暴露了三个方法构造方法、move()、draw()。无论外部调用者是一个矩形还是十个矩形使用方式完全一样rects [Rectangle(50, 50, 40, 40, (255, 0, 0), vx2), Rectangle(200, 100, 60, 30, (0, 255, 0), vx-2)] for r in rects: r.move() r.draw(screen)这段代码就是“批量更新”的最小形态。每个矩形自己维护位置、速度和绘制方式外部只负责遍历。以后做精灵、敌人、子弹时结构基本是这个模式的放大版。所以 Day3 花时间把封装写明白是性价比很高的投资。7. 扩展练习自动移动、反弹与碰撞检测基础移动验证完成后可以给矩形加三种常见行为自动移动、边界反弹、矩形之间碰撞检测。这三种行为在后续小游戏里出现的频率极高。7.1 多个矩形自动移动生成一批矩形每个矩形有随机位置、随机速度和随机颜色主循环里统一更新。先写一个支持反弹的子类import random class BouncingRect(Rectangle): def __init__(self, x, y, width, height, color, vx, vy): super().__init__(x, y, width, height, color, vx, vy) def update(self, screen_width, screen_height): self.move() if self.rect.left 0 or self.rect.right screen_width: self.vx -self.vx if self.rect.top 0 or self.rect.bottom screen_height: self.vy -self.vy生成 10 个矩形rects [] for _ in range(10): w random.randint(30, 80) h random.randint(20, 60) x random.randint(0, 800 - w) y random.randint(0, 600 - h) vx random.choice([-3, -2, 2, 3]) vy random.choice([-3, -2, 2, 3]) color ( random.randint(50, 255), random.randint(50, 255), random.randint(50, 255) ) rects.append(BouncingRect(x, y, w, h, color, vx, vy))主循环里批量更新while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False screen.fill((30, 30, 30)) for r in rects: r.update(800, 600) r.draw(screen) pygame.display.flip() clock.tick(60)运行后可以看到一群矩形在窗口里来回反弹。这个“列表 遍历 统一更新”的模式就是后面做敌机群、子弹阵列、粒子系统的初始形态。更重要的是通过这一段能直观感受到封装带来的好处主循环没有任何一个矩形的位置细节只有遍历调用。7.2 边界限制与反弹的区别扩展练习里最容易混淆的是“限制在窗口内”和“反弹”两种处理方式。它们的区别要分清。方式一限制在窗口内。矩形碰到边界就停住适合玩家角色。x max(0, min(x, screen.get_width() - width)) y max(0, min(y, screen.get_height() - height))方式二反弹。矩形到达边界后反转速度方向适合自动运动的物体。上面的BouncingRect就是这种处理。选择哪种取决于物体的运动意图。玩家控制的角色通常用限制方式因为玩家需要停在地图边缘自动巡逻的敌人、弹射的子弹通常用反弹方式。理解这个区别后遇到不同游戏需求可以快速切换。7.3 矩形之间碰撞检测用 Rect 自带的colliderect()方法判断两个矩形是否相交def check_collision(a, b): return a.rect.colliderect(b.rect)在主循环里做两两检测for i in range(len(rects)): for j in range(i 1, len(rects)): if check_collision(rects[i], rects[j]): # 碰撞后把两个矩形都变成白色方便观察 rects[i].color (255, 255, 255) rects[j].color (255, 255, 255)碰撞检测是后面很多玩法的核心吃金币、撞敌人、挡板接球本质上都是矩形相交判断。Day3 阶段能用colliderect()跑通两两检测后面的学习会顺畅很多。这里要注意两两检测的复杂度是 O(n²)矩形数量少时无所谓如果以后有成百上千个对象就需要考虑空间分区优化。8. 常见问题与排查方法矩形移动练习代码量不大但新手最容易在环境、事件、绘制三个环节出问题。下面整理一份排查表遇到问题先查表再查代码。问题现象可能原因排查方式解决方案报错ModuleNotFoundError: No module named pygamepygame 未安装或装到别的 Python 环境在激活的虚拟环境里运行pip show pygame按第三节步骤重新安装并激活虚拟环境窗口一闪而过就退出循环前抛异常或缺少事件循环观察控制台 Traceback 最后几行检查pygame.init()和while结构按方向键矩形没反应窗口未激活、按键判断位置不对运行后先点击游戏窗口再按键确认get_pressed()放在主循环内矩形移动留下拖影没有填充背景色观察矩形后方是否有残留色带每次绘制前调用screen.fill()矩形速度忽快忽慢没有限制帧率查看主循环是否缺少clock.tick()在循环末尾调用clock.tick(60)按一下键移动很长距离用KEYDOWN做持续移动检查事件类型处理逻辑持续移动用get_pressed()单次触发用KEYDOWN矩形移动到窗口外找不到了缺少边界限制在循环里打印矩形坐标观察数值增加边界判断逻辑窗口一直黑屏绘制后没刷新检查是否调用flip()绘制完调用pygame.display.flip()云服务器上运行报视频设备错误无桌面环境确认运行环境是否有 GUI换本地开发机运行关闭窗口后进程不退出事件循环检测不到QUIT检查事件循环是否被continue或异常跳过确保QUIT事件设置running False遇到报错时第一步永远是看控制台输出。Traceback 最后几行会直接告诉你是哪一行出了问题比盲目改代码快得多。另一个实用技巧是在关键位置加print()输出坐标、速度、事件类型一次只改一个变量看起来笨但定位问题最快。9. 最佳实践与后续扩展方向9.1 代码组织建议窗口尺寸、矩形大小、移动速度这类参数提到文件顶部的常量区或独立配置变量里不要散落在代码中间。每帧流程固定为“处理事件 - 更新逻辑 - 绘制 - 刷新”这是游戏循环的标准顺序先按这个顺序写再加功能。移动逻辑和绘制逻辑分开。现在看是小题大做但后续要加网络同步、存档、回放时逻辑和绘制不分离会非常痛苦。统一用pygame.Rect管理位置和碰撞不要同一时间维护一套x、y变量和一套rect对象。一个建议的文件结构即使对 Day3 也适用rect_move/ ├── rect_move.py # 主脚本 ├── objects.py # Rectangle 等对象定义 └── config.py # 屏幕尺寸、速度、颜色等配置主脚本只负责游戏循环对象定义放在objects.py配置放在config.py。后面每多一个功能就往对应文件里加不用推翻重写。9.2 合规与工程提醒这个练习用的是 pygame不涉及网络请求、音频处理和用户数据所以安全风险很低。但如果后续把矩形扩展成完整小游戏并公开发布需要注意游戏素材图片、音效、字体必须来自合法渠道涉及他人肖像、品牌、版权素材的内容不要直接用于公开作品如果要商用更要确认授权。代码里引用了第三方资源时保留来源信息这也是开发者之间基本的版权素养。9.3 下一步可以做什么Day3 做完矩形移动后面有几个不错的扩展方向把矩形换成图片用pygame.image.load()加载精灵图。加入鼠标控制让矩形跟随鼠标位置移动。增加计分系统控制矩形去“吃”随机生成的小方块吃了加一分并重新生成。加入加速度和摩擦力让移动不是匀速而是有启停手感。引入Sprite和Group把多个矩形交给 pygame 内置的精灵机制管理。尝试做“挡住掉落的方块”这种最简单的玩法把移动、碰撞、计分串成完整闭环。如果继续沿这套学习记录走Day4 比较合适的方向是“事件系统 游戏状态管理”把碰撞反弹、计分、结束条件整合到一个有完整流程的小游戏里矩形移动会从练习变成真正的玩法雏形。最后给一个实用建议把这篇的终极版本类封装 多矩形 反弹 碰撞保存好整套代码带注释基本不到一百五十行。后面做任意 2D 小游戏交互手感的起点都是“一个能移动的矩形”。建议收藏备用哪天写游戏循环卡住了翻回来对照一下就能恢复进度。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进