简介这份Pygame教程面向具备一定Python基础、希望入门2D游戏开发的初学者与自学者系统讲解游戏主循环、Surface对象、Event事件、Rect区域等核心概念帮助读者从零搭建可运行的游戏程序。教程采用“知识点示例”的编写方式内容覆盖Display显示模块、图像变形、时间控制与FPS设置、键盘与鼠标事件处理、矩形/多边形/圆形/椭圆/圆弧/直线等绘图函数以及Font字体与精灵碰撞检测并附有pip与二进制包两种安装方式说明便于边学边练、逐点吃透。资源包共1个PDF文件大小约1.32MB单文件结构完整、便于离线阅读与检索。目前已有1568人学习适合作为进入游戏开发领域的敲门砖也可供需要快速查阅Pygame常用API与示例的读者参考。1. 从一份 32 页的 Pygame 教程说起它到底能帮你把 2D 游戏做到什么程度如果你手上正躺着一份 32 页、从安装一路讲到精灵碰撞的 Pygame 教程却不确定它值不值得花一个周末啃完那这篇拆解就是写给你的。Pygame 是 Python 生态里专门做 2D 游戏开发的第三方包底层封装了 SDL 这套 C 语言多媒体库跨 Windows、Linux、Mac 都能跑免费开源。这份教程的定位很明确面向有 Python 基础、想入门游戏开发的人用“知识点 示例”的方式把游戏主循环、Surface、Event、Rect、Draw、Font、精灵碰撞这些核心概念串一遍。它不教你做 3D也不教你上引擎但足够让你把飞机大战、贪吃蛇、扫雷这类 2D 小游戏从零跑起来。适合谁适合想用最短路径理解“游戏是怎么一帧一帧动起来”的从业者也适合拿它当课程设计或教学素材的人。2. 把 Pygame 跑起来安装、初始化与第一个可交互窗口2.1 安装路径的选择与验证教程给了两条安装路径pip 包管理器安装和二进制 whl 包安装。我一般只推荐第一条原因很实际——pip 会自动处理依赖和平台匹配而 whl 包需要你手动确认 Python 版本、系统位数、cp 标签是否对得上稍不留神就装了个不兼容的包。先确认 Python 版本教程建议 3.7 以上这个门槛放到今天依然合理。# 确认 Python 版本建议 3.7 python --version # 推荐方式pip 直接安装 pip install pygame # 验证安装是否成功 python -m pygame --versionpip install pygame这条命令背后做的是从 PyPI 拉取与当前解释器 ABI 匹配的 wheel 包并解压到 site-packages不需要编译。python -m pygame --version则是把 pygame 当模块执行输出内部版本号比在交互界面import pygame更干净因为它不会把 pygame 的欢迎横幅打出来干扰判断。如果这条命令报No module named pygame八成是 pip 和 python 不是同一个解释器用python -m pip install pygame强制对齐即可。二进制包那条路教程给的命令是Python -m pip install --user pygame-2.0.2-cp27m-win_amd64.whl这里有个血泪经验cp27m表示 CPython 2.7win_amd64表示 64 位 Windows两个标签必须和你的解释器完全一致否则 pip 会直接拒绝。--user参数是把包装到用户目录而非系统目录避免权限问题。新手如果非要走这条路先跑python -c import sys; print(sys.version)看清版本再选包。2.2 第一个程序的结构拆解教程的第一个示例虽然只有二十来行但把 Pygame 程序的骨架全摆出来了。我把它拆成五段来看这样后面写任何游戏都能套。import sys import pygame # 第一步初始化所有子模块display/font/mixer/cursors 等 pygame.init() # 第二步创建主窗口本质是一个 Surface尺寸 400x400 screen pygame.display.set_mode((400, 400)) pygame.display.set_caption(hello world) # 第三步创建字体对象并渲染文本 Surface f pygame.font.Font(C:/Windows/Fonts/simhei.ttf, 50) text f.render(示例文本, True, (255, 0, 0), (0, 0, 0)) textRect text.get_rect() textRect.center (200, 200) # 第四步把文本 Surface 贴到主窗口上 screen.blit(text, textRect) # 第五步游戏主循环监听事件 刷新显示 while True: for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() pygame.display.flip()pygame.init()不能漏它负责检测硬件接口并初始化 display、font、mixer 等所有子模块漏掉它后面调set_mode会直接抛异常。set_mode((400,400))返回的 screen 是一个 Surface而且是整个游戏里尺寸最大的那张“纸”所有其他 Surface 都要 blit 到它上面。f.render()的四个参数分别是文本内容、是否抗锯齿、前景色 RGB、背景色 RGB返回一个新的文本 Surface。textRect.center (200,200)是把矩形中心对齐到窗口中心比手动算左上角坐标省事。主循环里pygame.event.get()一次性取走事件队列pygame.QUIT对应点右上角关闭按钮pygame.quit()卸载模块、sys.exit()终止进程这两句要成对出现。最后pygame.display.flip()把后台缓冲整屏翻到前台没有它窗口会一闪而过或者一片黑。提示字体路径写死C:/Windows/Fonts/simhei.ttf只在 Windows 成立换平台会翻车。跨平台场景用pygame.font.SysFont(simhei, 50)让 Pygame 自己去系统字体表里找。3. Surface、Display 与 Transform图像从哪来、怎么摆、怎么变形3.1 Surface 的三种来源与 blit 参数Pygame 里一切可见的东西都是 Surface。教程把来源归了三类display.set_mode 创建的主窗口、pygame.Surface 新建的空白画布、pygame.image.load 加载的外部图片。理解这三者的区别是后面做精灵和碰撞的基础。import pygame pygame.init() screen pygame.display.set_mode((500, 250)) # 来源一空白 Surface50x50HWSURFACE 表示尽量放显存 face pygame.Surface((50, 50), flagspygame.HWSURFACE) face.fill(pink) # 来源二从外部图片加载convert() 统一像素格式提升 blit 速度 image_surface pygame.image.load(demo.png).convert() # blit 的 dest 可以是 (x, y) 元组也可以是 Rect screen.blit(face, (100, 100)) screen.blit(image_surface, (0, 0)) pygame.display.update()pygame.Surface((50,50))不传尺寸时会默认创建一个和主屏一样大的 Surface这个默认行为很容易埋雷——你以为建了个小方块结果内存里多了一张全屏图。convert()的作用是把加载进来的图片像素格式转成和当前显示表面一致不做这一步每次 blit 都要现场转换帧率会明显掉。blit(source, dest, areaNone, special_flags0)里area传一个 Rect 就是抠图只贴图片的指定区域special_flags控制颜色混合默认 0 是直接覆盖传BLEND_RGBA_ADD之类的值可以做叠加发光效果。3.2 Display 模块的 flags 与刷新方式set_mode(size, flags)的 flags 决定窗口形态教程列了一张表实际常用的就几个FULLSCREEN全屏、RESIZABLE可拖拽改变大小、NOFRAME无边框、HWSURFACE硬件加速必须配合 FULLSCREEN、DOUBLEBUF双缓冲建议配合 HWSURFACE 或 OPENGL。做课程设计演示时我一般用默认 flags需要全屏展示才加 FULLSCREEN因为全屏模式下调试信息看不到退出还得靠代码里的 QUIT 事件。刷新有两个方法pygame.display.flip()整屏更新pygame.display.update()可以传 Rect 只更新局部。教程说得很清楚后者不传参数时等价于 flip。局部更新在静态背景 小范围动画的场景里能省不少开销但新手容易漏更新区域导致残影所以第一版代码我建议统一用 flip跑通了再优化。3.3 Transform 的缩放、旋转与 rotozoompygame.transform是对已有 Surface 做几何变换的模块教程重点讲了 scale、rotate、rotozoom 三个。import pygame pygame.init() screen pygame.display.set_mode((500, 250)) image_surface pygame.image.load(demo.png).convert() # 缩放到 300x300返回新 Surface原图不变 image_new pygame.transform.scale(image_surface, (300, 300)) # 旋转 45 度注意旋转后尺寸会变大返回新 Surface image_1 pygame.transform.rotate(image_new, 45) # 旋转 0 度同时缩小到 0.5 倍一步完成 image_2 pygame.transform.rotozoom(image_1, 0, 0.5) screen.blit(image_2, (0, 0)) pygame.display.update()这三个函数都返回新的 Surface不会修改原对象这点和fill、scroll这类原地操作不同记混了会出现“我明明旋转了怎么没变化”的困惑。rotate的角度是逆时针为正旋转后外接矩形会变大如果你拿旋转前的 Rect 去做碰撞检测边界会对不上这是后面精灵碰撞章节的一个隐藏坑。rotozoom(surface, angle, scale)把旋转和缩放合并内部做了一次平滑采样比先 rotate 再 scale 画质略好适合做子弹、道具这类需要频繁变换的小图。4. 时间、事件与 Rect让画面动起来、让操作有反馈4.1 Clock 控制 FPS 与时间单位Pygame 的时间单位统一是毫秒pygame.time.get_ticks()返回从 init 之后到调用时刻的毫秒数pygame.time.wait(3000)让程序硬暂停 3 秒pygame.time.set_timer()建定时器周期性投递事件。但真正控制帧率的是 Clock 对象。import pygame pygame.init() screen pygame.display.set_mode((500, 300)) clock pygame.time.Clock() running True while running: # tick(60) 表示这一帧最多等 16.6ms把循环锁在 60 FPS clock.tick(60) for event in pygame.event.get(): if event.type pygame.QUIT: running False # 用 get_fps() 观察实际帧率调试性能时很有用 # print(clock.get_fps()) pygame.display.flip() pygame.quit()clock.tick(60)的返回值是距上一帧的毫秒数它做的是“限速”而不是“精确对齐”——如果这一帧逻辑跑了 20ms它只再等 13ms 左右凑够 16.6ms如果逻辑本身超过 16.6ms它不会补等帧率自然掉下去。教程提到一般人能接受的最低 FPS 约 30流畅要 60 以上这个判断在 2D 小游戏里基本成立。get_time()拿上一帧耗时get_fps()算平均帧率做性能排查时把这两个值打到控制台比凭感觉判断卡顿靠谱。4.2 事件类型与键盘鼠标处理事件是玩家和程序交互的通道。教程列了事件类型和处理方法核心是pygame.event.get()取队列、按event.type分支。键盘用event.key判断具体按键鼠标用event.pos拿坐标、event.button判断左右中键。import pygame pygame.init() screen pygame.display.set_mode((400, 400)) running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: # KEYDOWN 是按下瞬间触发一次适合做“开火”“跳跃” if event.key pygame.K_SPACE: print(space pressed at, pygame.time.get_ticks()) elif event.type pygame.MOUSEBUTTONDOWN: # button 1 左键 2 中键 3 右键pos 是窗口内坐标 if event.button 1: print(left click at, event.pos) pygame.display.flip() pygame.quit()这里有个容易混淆的点KEYDOWN只在按下那一刻触发一次按住不放不会连续触发除非系统重复率介入所以做“持续移动”不能靠 KEYDOWN要用pygame.key.get_pressed()在每帧轮询按键状态。教程在事件章节没展开这一点但实际写移动逻辑时绕不开。鼠标的event.pos是相对窗口左上角的坐标如果窗口有缩放或全屏坐标换算要额外处理。4.3 Rect 的定位与碰撞判定Rect 是 Pygame 里表示矩形区域的轻量对象有 left、top、right、bottom、center、centerx、centery、width、height 等属性改这些属性会联动更新其他值。教程用图解讲了位置关系实际最有价值的是它的碰撞检测方法。import pygame # 两个矩形参数是 (left, top, width, height) rect_a pygame.Rect(100, 100, 50, 50) rect_b pygame.Rect(120, 120, 50, 50) # colliderect 判断是否相交返回 bool print(rect_a.colliderect(rect_b)) # True # colliderect 之外还有 collidelist / collidelistall 批量判断 rects [pygame.Rect(0, 0, 10, 10), pygame.Rect(200, 200, 10, 10)] print(rect_a.collidelist(rects)) # 返回第一个相交的索引没有则 -1 # 用 center 属性做居中定位比手算左上角省事 rect_a.center (200, 200)colliderect是轴对齐矩形相交判断速度快但只适合矩形碰撞体圆形或像素级碰撞要另想办法。collidelist返回索引而不是对象批量检测时用它比循环 colliderect 简洁。注意 Rect 的 width/height 不能为负用inflate或move改尺寸位置时传负数会抛异常这是新手调碰撞体大小时常踩的坑。5. 避坑与排查Pygame 新手最容易翻车的五个地方5.1 窗口一闪而过或全黑现象运行程序后窗口闪一下就没了或者一直黑屏。原因通常是主循环里缺pygame.display.flip()或update()后台缓冲没翻到前台另一种是主循环被pygame.time.wait()之类的阻塞调用卡住事件队列积压。解决确认循环末尾有刷新调用把 wait 换成 Clock.tick 做限速别在循环里做长时间阻塞。5.2 中文显示成方块现象f.render(中文)出来一堆方框。原因是pygame.font.Font指定的字体文件不含中文字形或者路径写错回退到了默认字体。解决Windows 下用C:/Windows/Fonts/simhei.ttf或msyh.ttf跨平台用pygame.font.SysFont(simhei, 50)并在 render 前确认字体对象创建成功创建失败会抛pygame.error。5.3 图片加载后 blit 很慢现象贴图多了帧率明显下降。原因是image.load后没调convert()每次 blit 都在做像素格式转换。解决加载后立即.convert()带透明通道的 PNG 用.convert_alpha()后者保留 alpha 通道但比 convert 略慢按需选择。5.4 旋转后碰撞检测对不上现象图片旋转了但碰撞判定还在原来的矩形范围。原因是transform.rotate返回新 Surface尺寸变了而碰撞用的 Rect 还是旧的。解决旋转后重新get_rect()并同步更新位置或者用rotozoom时记录缩放比例换算碰撞体。5.5 退出时进程残留或报错现象点关闭后窗口没了但终端还挂着或者报pygame.error: video system not initialized。原因是只调了pygame.quit()没sys.exit()或者 quit 之后又调了 display 相关方法。解决QUIT 分支里pygame.quit()和sys.exit()成对写quit 之后不要再碰任何 pygame 接口。6. 从教程到可玩 Demo精灵碰撞的验证方法与一个收尾习惯教程最后落到精灵和碰撞检测这是把前面所有模块串起来的关键一步。精灵Sprite本质是带图像和 Rect 的对象Pygame 的pygame.sprite.Sprite提供了基础封装pygame.sprite.Group管理批量精灵spritecollide做组与组之间的碰撞检测。我一般用一个小 Demo 来验证整份教程学没学透一个受键盘控制的主角、若干随机移动的敌人、碰撞后打印提示并重置位置。import pygame import random pygame.init() screen pygame.display.set_mode((480, 360)) clock pygame.time.Clock() class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image pygame.Surface((30, 30)) self.image.fill((0, 200, 0)) self.rect self.image.get_rect(center(240, 180)) self.speed 4 def update(self, keys): # 每帧轮询按键状态实现持续移动 if keys[pygame.K_LEFT]: self.rect.x - self.speed if keys[pygame.K_RIGHT]: self.rect.x self.speed if keys[pygame.K_UP]: self.rect.y - self.speed if keys[pygame.K_DOWN]: self.rect.y self.speed class Enemy(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image pygame.Surface((20, 20)) self.image.fill((200, 0, 0)) self.rect self.image.get_rect( center(random.randint(20, 460), random.randint(20, 340))) player Player() enemies pygame.sprite.Group(Enemy() for _ in range(5)) all_sprites pygame.sprite.Group(player, *enemies) running True while running: clock.tick(60) for event in pygame.event.get(): if event.type pygame.QUIT: running False keys pygame.key.get_pressed() player.update(keys) # spritecollide 判断 player 与 enemies 组是否碰撞True 表示碰撞后移除敌人 hits pygame.sprite.spritecollide(player, enemies, True) if hits: print(hit, remaining:, len(enemies)) screen.fill((30, 30, 30)) all_sprites.draw(screen) pygame.display.flip() pygame.quit()这段代码把 Surface 创建、Rect 定位、事件轮询、Clock 限速、精灵组管理和碰撞检测全用上了。spritecollide(sprite, group, dokill)的第三个参数传 True 时碰撞到的精灵会从组里自动移除省去手动清理传 False 则保留适合做“扣血但不消失”的逻辑。all_sprites.draw(screen)内部就是对每个精灵做 blit顺序按加入组的先后后加入的盖在上面。验证方法很直接跑起来用方向键移动绿块去撞红块控制台打印剩余敌人数撞完五个说明碰撞链路通了。一个我踩过的坑pygame.key.get_pressed()返回的是当前帧的按键状态序列必须在事件循环之后、update 之前取取早了拿到的是上一帧状态移动会有延迟感。另外精灵的update方法签名要和调用方对齐Group 批量 update 时传参方式不同混用会报参数数量错误。从那以后我每次拿到一份新教程都会先照着它的第一个示例原样跑通再改一个参数看输出变化最后用一个小 Demo 把核心模块串一遍——这三步走完教程里哪些是能直接用的、哪些是省略了边界条件的心里就有数了。这份 Pygame 教程的价值在于把 2D 游戏开发的基础概念铺得够全示例也够短适合当起点但它对性能优化、跨平台字体、旋转碰撞这些边界讲得浅需要你按上面的避坑清单自己补。希望帮到你。本文还有配套的精品资源点击获取