news 2026/9/14 7:34:34

pygame游戏源码拆解:安装、循环、碰撞检测与打包实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pygame游戏源码拆解:安装、循环、碰撞检测与打包实战

简介:一套基于pygame开发的Python小游戏源码合集,共收录13款经典休闲游戏,适合Python初学者、游戏开发入门者以及需要课堂或实战案例的师生。资源涵盖滑动拼图、四子棋、记忆配对、俄罗斯方块变体、翻转棋、贪吃蛇、宝石消除等趣味项目,代码整体简洁,便于理解游戏循环、事件处理、碰撞检测和简单AI等核心概念。压缩包内包含73个文件,除13个.py源码外,还配有45张png图片素材、7个wav与4个ogg音效、2个mid背景音乐、1个ttf字体以及关卡配置txt,整体仅1.18MB,轻量易下载。目前已有903人学习使用,且文件按数字前缀分模块组织,每个项目可独立运行,便于按编号逐个体验。下载后既能直接运行体验游戏效果,也能通过修改代码改变玩法、替换素材,无论是自学入门、课程设计还是兴趣仿写,都能获得直接可用的代码参考与完整素材配套。

1. pygame-games 里的 13 个游戏源码,一条线读完

pygame-games 这类仓库不少见,13 个游戏源码攒在一个目录里,贪吃蛇、乒乓球、飞机射击都有。新手面对十几个文件夹不知从哪看起,这篇把 13 个 pygame 游戏反复出现的骨架拆开讲:装环境、跑循环、处理事件、判断碰撞、改参数、打包,一条线走完。

pygame 不是引擎,只是把 SDL 封装成 Python 模块。每个游戏本质上是"初始化窗口 + 死循环 + 刷新画面",所以读源码先找主循环,再看循环里调了哪些函数,最后才是具体逻辑。这套读法练熟,仓库里任何一个游戏都能在半小时内跑通并改出自己的版本。

适合读这篇的人有三类:学 Python 找实战项目的新手,交课程设计的学生,以及要给团队带教 pygame 项目的工程师。文中命令和代码到本机即可复现,不依赖 IDE。

2. pygame 安装与环境验证:先跑通再读源码

2.1 安装 pygame 的三种方式与 wheel 构建失败处理

安装 pygame 最直接的方式是pip install pygame,实际执行时却常卡在两个点上:一是本机装了多个 Python,pip 装的库和编辑器选中的解释器不是同一个;二是 pip 走了源码构建,报出error: failed to build 'pygame' when getting requirements to build wheel。13 个游戏源码目录里相关的 issue 大多属于这两种,先清干净环境再谈读代码。

提示:优先用python -m pip install pygame而不是裸pip install,保证装进当前python命令对应的解释器。

三种安装方式里,省心的是直接装官方预编译 wheel,Windows 和 macOS 一条命令结束。Linux 上如果 pip 转入源码构建,通常是系统缺 SDL 开发包,常见做法是装好libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev后重试。不想动系统包,就强制只用预编译产物:

python -m pip install --only-binary :all: pygame

--only-binary :all:的含义是只接受 PyPI 上现成的二进制 wheel,不触发任何源码构建。连 wheel 都找不到时,多半是 Python 版本过新或过旧,换到 3.10 ~ 3.12 之间最省时间。装好之后用版本号验证:

python -m pygame --version
安装方式适用场景注意点
pip install pygameWindows/macOS 常规确认解释器对应关系
pip install --only-binary :all: pygameLinux 缺编译依赖Python 版本不宜过新
源码编译安装需要改 SDL 层行为需要完整编译工具链

能打印出版本号,说明导入路径没问题,后面再遇到ModuleNotFoundError: No module named 'pygame',先回头查这一节。

2.2 用最小 20 行代码验证游戏循环可用

装完库别急着打开任意一个游戏,先用最小循环验证环境。跑通了再进源码,出了状况也知道是环境问题还是代码问题。

import sys import pygame pygame.init() # 初始化所有 pygame 模块 screen = pygame.display.set_mode((640, 480)) # 创建 640x480 窗口 pygame.display.set_caption("minimal loop") # 窗口标题 clock = pygame.time.Clock() # 帧率控制对象 while True: for event in pygame.event.get(): # 每帧取一次事件队列 if event.type == pygame.QUIT: pygame.quit() sys.exit() screen.fill((30, 30, 30)) # 纯色刷掉上一帧画面 pygame.display.flip() # 把内存画面整体提交到窗口 clock.tick(60) # 限制帧率 60 FPS

pygame.init()会把显示、音频、字体等模块一次性初始化,省去逐个 init 的麻烦。set_mode返回的 Surface 是后续所有绘制的目标;flip()负责双缓冲切换,避免画面闪烁。clock.tick(60)把循环控制在每秒 60 次以内,这个值也是 13 个游戏源码里最常见的帧率设定。

如果报pygame.error: video system not initialized,说明pygame.init()没执行成功,通常是显示驱动或虚拟环境的问题;窗口一闪而过,检查pygame.quit()后面是否跟着sys.exit()

2.3 帧率、坐标与方向:游戏循环三要素

pygame 没有强制性的更新框架,所有逻辑都在 while 循环里手写。读 13 个游戏源码抓住三个要素就能快速定位一段代码的意图。帧率:clock.tick(fps)的参数决定游戏速度,fps 调高同样的逻辑跑得更快。坐标:pygame 的 (0,0) 在窗口左上角,x 向右增、y 向下增,和数学课的坐标系相反,第一次写移动逻辑最容易在这里把方向搞反。方向:物体移动本质是每帧改变坐标,方向向量 (-1, 0) 表示向左。

x, y = 320, 240 speed_x, speed_y = 3, 3 # 每帧位移量,也就是移动速度 while running: x += speed_x y += speed_y # 碰到左右边界就反向 if x <= 0 or x >= 640 - 40: speed_x = -speed_x

这段是乒乓球游戏里球体运动的核心:不考虑加速度,只是每帧坐标加上速度向量,边界判断后取反。13 个游戏里十有八九能搜到相似代码,区别只在边界值和物体尺寸。记住这个模式,后面改速度、改弹跳逻辑都有明确的下手位置。

3. 13 个游戏源码的通用骨架:精灵、事件与碰撞

3.1 pygame.sprite.Sprite 与 Group 的协作方式

打开任意一个游戏目录,最常见结构是一个主文件放游戏循环,一个或多个类文件定义角色。角色类基本都继承pygame.sprite.Sprite,因为 Sprite 提供了与 Group 配合所需的接口。飞机射击类的玩家、子弹、敌机,全都是 Sprite 子类。

import pygame class Player(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image = pygame.Surface((40, 30)) # 先造一个色块代替图片 self.image.fill((0, 128, 255)) self.rect = self.image.get_rect() self.rect.topleft = (x, y) def update(self): # 每帧由 Group 批量调用的方法,写移动逻辑 keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.rect.x -= 5

Sprite 子类里两个必须量:self.image决定画什么,self.rect决定画在哪、碰没碰到。update()不是自动执行的,只有把实例加进 Group 后调用group.update()才会批量调用。新手容易漏掉把 Sprite 加进 Group,结果 new 出来的角色在屏幕上完全不可见。

all_sprites = pygame.sprite.Group() player = Player(300, 400) all_sprites.add(player) while running: all_sprites.update() # 批量更新所有精灵的逻辑 all_sprites.draw(screen) # 按各自 rect 把 image 画上屏幕

draw(screen)内部遍历每个 Sprite,用各自的rect坐标把imageblit 到屏幕上。Group 不只是容器,还是更新和绘制的调度器;13 个游戏源码里凡是出现all_sprites命名的地方,基本都是这个套路。

3.2 事件循环里那几行不变的代码

游戏循环里除了更新逻辑,还有一段处理输入的事件代码。键盘按下、鼠标移动、窗口关闭这类外部输入被 pygame 排队,pygame.event.get()一次性取回全部事件。

for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: shoot()

KEYDOWN适合处理"按一下触发一次"的动作,比如发射子弹;pygame.key.get_pressed()适合"按住不松"的连续动作,比如方向键移动。13 个游戏源码里两种混用很常见,读的时候注意区分:连续移动用 get_pressed,单次跳跃和发射用事件。常用事件按下表对照:

事件类型触发时机常见用途
pygame.QUIT点击窗口关闭退出主循环
pygame.KEYDOWN按键按下跳跃、切换、发射
pygame.MOUSEBUTTONDOWN鼠标按键拼图、菜单点击
pygame.USEREVENT自定义定时事件倒计时、道具生成

自定义事件在 13 个游戏里也常见,比如道具每隔 2 秒生成一次,用pygame.time.set_timer(pygame.USEREVENT + 1, 2000)注册定时事件,再在事件循环里判断event.type == pygame.USEREVENT + 1

3.3 碰撞检测的三种写法和适用场景

pygame 的碰撞检测默认基于rect矩形,对 13 个这类小游戏精度足够。最常用的是pygame.sprite.spritecollide,返回碰撞列表或把碰撞对象从 Group 里移除:

hits = pygame.sprite.spritecollide(player, enemies, True) if hits: score += len(hits)

第三个参数dokill=True表示碰撞后从 enemies 组直接移除,子弹打中敌机正好用这个特性,省一段手写列表删除。如果要区分矩形四边的碰撞方向,比如乒乓球要判断球撞到拍子的哪一面,用两个rect做逐边判断:

if ball.rect.colliderect(paddle.rect): if ball.rect.right > paddle.rect.left and ball.rect.left < paddle.rect.right: speed_x = -speed_x

更高精度是pygame.mask.from_surface(image)转成掩码后做像素级碰撞,适合三角形、蛋形这类矩形误差大的物体,但每帧计算成本高。13 个游戏源码里用到 mask 的比例很低,多数场景 spritecollide 加 colliderect 就够了;对象是透明背景 PNG 时矩形判定的误差通常可以接受,不必为了精度牺牲帧率。

4. 拆一个游戏改一个游戏:参数、素材与关卡改造

4.1 从 13 个游戏里选第一个改造对象

选改造对象的原则不是看画面,而是看代码行数。贪吃蛇和乒乓球这类交互简单的,主文件通常在 200 行以内,逻辑集中在移动和边界判断,适合第一次练手;飞机射击类多了子弹、敌机刷新和碰撞分组,建议放到第二遍读。选定的另一个判据是看目录里有没有 assets 或 images 文件夹:有素材目录的,说明贴图资源分离,改造空间更大;纯代码画色的,逻辑集中但视觉效果提升有限。

拿到游戏目录后第一步永远是先跑一次原始版本,记录两个数:窗口尺寸和帧率。窗口尺寸决定你后面所有坐标和素材尺寸的参考基准,帧率决定参数改动后速度变化的观感。这两条记下来,改造前后才有对比依据。

4.2 改速度、改颜色、改碰撞范围的参数清单

13 个游戏源码风格不统一,但速度、颜色这类可调参数基本都定义在文件顶部常量区,或者构造函数里。把顶部常量改掉,游戏行为立刻变化。一份通用参数清单,可以直接用于贪吃蛇和乒乓球的改造:

常量/参数典型值改大的效果改小的效果
FPS60整体变快、操作更灵敏动作变卡顿
SPEED / VELOCITY3~5每帧位移变大、更难操控更好操控、节奏变慢
蛇身 BLOCK_SIZE20蛇更粗、碰撞判定更宽松更细、需要更精确走位
BALL_RADIUS10球更大、与拍子碰撞更早球变小、容易从边缘漏过
初始方向向量(-1, 0)起始方向改变同上

改参数时有一个隐藏联动:碰撞范围由rect决定,而rect由图片或色块尺寸决定。只改 BALL_RADIUS 而不同步改生成图片的代码,会出现"画出来变大但碰撞没变大"。正确做法是把半径作为唯一数据源:

BALL_RADIUS = 15 ball_image = pygame.Surface((BALL_RADIUS * 2, BALL_RADIUS * 2)) ball_rect = ball_image.get_rect()

所有逻辑读同一个常量,改一处就行。13 个游戏里不少地方把尺寸写死成 (20, 20),改成这种写法顺带消除了魔数,是改造里最值得做的一步。

关卡改造也是从这里切入:给分数设阈值,每过一个阈值就把 SPEED 往上抬。比如在更新函数里写speed = base_speed + score // 500,分数每涨 500,速度就加一档,关卡难度递进不需要重写逻辑,只是把一个常量变成表达式。

4.3 换素材的路径与格式问题

把色块换成 PNG 图片是改造最直观的一步。pygame 加载图片常用两条 API:pygame.image.load(path)pygame.transform.scale(image, size)。路径容易踩坑的地方在于相对路径和工作目录有关,从 IDE 双击运行和用命令行python main.py运行时,当前目录可能不一致,导致 FileNotFoundError。13 个游戏源码里如果有 assets 目录,通常硬编码了相对路径,建议改成基于__file__拼路径:

import os import pygame BASE_DIR = os.path.dirname(os.path.abspath(__file__)) img_path = os.path.join(BASE_DIR, "assets", "player.png") player_img = pygame.image.load(img_path).convert_alpha()

convert_alpha()把图片转成带 alpha 通道的 Surface,透明背景才能正确显示;不加这行,PNG 的透明区域会变成黑色方块。配合pygame.transform.scale可以把任意分辨率素材缩到目标尺寸,比如把 512x512 的敌人图缩成 40x40,保证原有判断逻辑不用动。

提示:不要用中文文件名和带空格的文件名,pygame 的 loader 对跨平台路径的处理并不都友好,换成 player.png 这种命名能省去大部分路径问题。

5. 帧率与打包:pygame 游戏交付前的几件小事

5.1 用内置手段定位掉帧原因

13 个游戏源码跑起来之后,最常被问的是"为什么我的机器上有点卡"。先别怀疑电脑,在游戏循环里加一个帧率计数器,看真实 FPS 和目标差多少。最直接的方式是用clock.get_fps()配合字体渲染:

font = pygame.font.Font(None, 36) fps_text = font.render(f"FPS: {clock.get_fps():.1f}", True, (255, 255, 255)) screen.blit(fps_text, (10, 10))

clock.get_fps()返回最近一秒的平均帧率,比手动计数稳定。如果显示值和目标相差很远,瓶颈通常集中在两类代码:密集的pygame.mask.from_surface像素碰撞,以及每帧都在调pygame.image.load加载素材。前者改成初始化时算好 mask 并复用,后者把加载全部移到循环外,通常立竿见影。13 个这种规模的小游戏,把这两点改掉,帧率回到 60 基本没有悬念。

5.2 PyInstaller 打包 pygame 的注意点

游戏做完要发给别人玩,常见做法是 PyInstaller 打包。基础命令是:

pyinstaller --onefile --windowed --name my_game main.py

--windowed去掉运行时黑色控制台窗口,--onefile打出单文件,代价是启动时解压到临时目录稍慢。打包后最常见的报错是找不到图片和字体,原因是BASE_DIR指向了临时解压目录。处理方式是运行时优先用sys._MEIPASS定位资源:

if hasattr(sys, "_MEIPASS"): BASE_DIR = sys._MEIPASS else: BASE_DIR = os.path.dirname(os.path.abspath(__file__))

配合--add-data "assets;assets"(Windows 下分号,Linux/macOS 下冒号)把素材目录打进包。打包完的 exe 建议在干净机器上试跑一次,pygame 的 SDL 运行库会随包分发,一般不需要对方装 Python;如果对方机器缺显卡驱动,可能直接闪退,先让对方更新驱动,再排查代码。这一节做扎实,13 个游戏源码就能变成 13 个可分发的小作品,整条流程闭环。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 7:34:17

HTML炫酷背景动态效果实战:CSS、Canvas、WebGL选型与性能优化

简介&#xff1a;面向网页设计师与前端开发者的HTML/CSS/JS背景特效合集&#xff0c;可快速应用于企业官网、个人博客、登录页面及活动展示页&#xff0c;解决网页视觉单调的问题。压缩包内含30个文件&#xff0c;以21个可直接运行的HTML演示页为主&#xff0c;辅以5个JavaScri…

作者头像 李华
网站建设 2026/9/14 7:33:08

断点调试Spring Cloud FeignClient配置链路:从注解到HTTP客户端

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 7:26:31

C++线程池从原理到实践:队列选型、参数调优与避坑指南

1. 为什么要自己写线程池——一次线上事故引发的思考先讲个真事。前两年我在做一个高并发的消息推送服务&#xff0c;初期QPS并不高&#xff0c;想着“先跑起来再说”&#xff0c;每个请求来就直接std::thread创建一个线程处理。单测、压测在小流量下也看不出毛病&#xff0c;就…

作者头像 李华