ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Python+Pygame零基础烟花动画:粒子系统与游戏循环实战

Python+Pygame零基础烟花动画:粒子系统与游戏循环实战

简介:这份资源面向对Python图形编程感兴趣的初学者与爱好者,围绕Pygame库讲解如何从零搭建一个带音效的新年烟花动画程序,帮助读者理解粒子系统、图形界面渲染与事件循环等基础概念。压缩包内共1个docx文档,约16KB,以图文与代码示例结合的方式组织内容,便于边看边练。文档从环境准备讲起,逐步展开粒子类的属性设计、烟花发射与爆炸逻辑、颜色随机配置以及背景音乐的加载与循环播放,并给出可直接运行的完整代码,读者可据此调整粒子数量、发射频率与音乐路径,做出个性化的节日烟花展示。目前已有140人学习,适合希望以趣味项目入门Pygame、提升图形界面应用处理能力的开发者参考实践。

1. 零基础也能写烟花:Python 加 Pygame 到底能做出什么

你有没有过这种经历:刷到别人用代码做的粒子动画,满屏烟花炸开,底下评论都在喊“大佬”,你心里痒了一下,但马上又觉得“这肯定很难,我连 Python 都没装明白”。其实我第一次接触 Pygame 的时候也是这个反应,觉得图形界面、动画、音效这些东西离零基础的人特别远。但真相是,你只需要会一点点 Python 基础语法,就能让屏幕上出现属于你自己的烟花。

这份资源要带你做的事非常具体:用 Python 配合 Pygame 这个库,从零写出一段能运行的新年烟花动画。它会爆炸、会散开、会慢慢消失,还能配上背景音乐。你不需要先学什么高深数学,也不需要美术功底,代码里所有效果都是靠“粒子”这个小东西堆出来的。适合谁?适合刚学完 Python 基础、想找个看得见摸得着的项目练手的人;适合想给节日加点仪式感、又不想只会调库的人;也适合那些被“图形界面”四个字吓住、其实连窗口都没创建过的纯新手。这一章不写代码,只帮你把心里那层“我不行”的膜捅破。

2. 先搞懂它在干嘛:烟花不是画出来的,是“粒子”堆出来的

2.1 为什么值得学 Pygame:它把“画图”变成了“摆积木”

很多新手一听图形编程,脑子里浮现的是密密麻麻的坐标公式和颜色代码。Pygame 的好处恰恰是它不逼你从底层造轮子。你可以把它想象成一盒数字积木:屏幕是一个画布,你往上面放圆、放方块、放图片,每一帧擦掉重画,连续起来就是动画。烟花效果听起来玄学,拆开看就是一堆小圆点,每个小圆点有自己的位置、速度、颜色和寿命。你让它们从同一个点出发,朝不同方向飞,飞着飞着速度慢下来,寿命到了就消失——这就是爆炸。

我建议你先别管什么“粒子系统”这种术语。你就记住一句话:一个粒子就是一个带属性小圆点,烟花就是一百个小圆点同时往外跑。Pygame 负责把这些小圆点按你定的规则画到屏幕上,每秒画 60 次,你的眼睛就看到了流畅动画。音效部分更简单,加载一个音频文件,让它在程序启动时循环播放就行。你不需要懂音频编码,只需要告诉 Pygame 文件在哪。

2.2 为什么用“类”来写:不写类也能跑,但写了类你才看得懂

原始代码里出现了Particle类和Firework类。零基础的人看到class就头大,这很正常。但你换个角度想:如果不用类,你得为每一个小圆点单独写一堆变量,100 个粒子就是 100 套x1, y1, speed1, x2, y2, speed2...,写到第十个你就乱了。类的意义是把“一个粒子该有的东西”打包成一个模板,用的时候直接复制这个模板,改改初始值就行。

Particle类管三件事:自己在哪(x, y)、自己长什么样(颜色、大小)、自己怎么动(角度、速度、寿命)。Firework类管的是“一发烟花”:它先有一个上升的白点,等这个白点飞到一定高度,就原地炸出 100 个彩色粒子。主循环则像一个导演,每帧问所有烟花“你们更新了吗?画出来了吗?”,然后擦掉旧画面,画上新画面。你不需要一次看懂所有细节,先知道这三个角色各干各的,后面写代码时就不会迷路。

2.3 第一个能跑的例子:从空窗口到一朵烟花

在写完整烟花之前,你先跑通一个最小版本:打开一个黑色窗口,在中间画一个白色圆点,让它自己慢慢往上飞。这一步跑通了,后面加爆炸、加颜色、加音效都是在这个骨架上挂东西。

import pygame import random import math # 初始化 Pygame,任何 Pygame 程序的第一步 pygame.init() # 设置窗口大小:宽 800,高 600 screen_width, screen_height = 800, 600 screen = pygame.display.set_mode((screen_width, screen_height)) # 定义一个白色,RGB 三个值都是 255 WHITE = (255, 255, 255) BLACK = (0, 0, 0) # 创建一个最简单的粒子:位置在屏幕底部中间,速度向上 x = screen_width // 2 y = screen_height speed = 5 # 时钟对象,用来控制帧率 clock = pygame.time.Clock() running = True while running: # 处理关闭窗口事件 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 更新位置:y 减小就是往上飞 y -= speed # 填充背景,擦掉上一帧 screen.fill(BLACK) # 画一个半径 4 的白色圆点 pygame.draw.circle(screen, WHITE, (int(x), int(y)), 4) # 刷新屏幕,让画的东西显示出来 pygame.display.flip() # 限制每秒 60 帧,不然飞太快 clock.tick(60) pygame.quit()

这段代码里,pygame.init()是开机键,set_mode是开窗口,screen.fill(BLACK)是每帧擦黑板,pygame.draw.circle是画圆,pygame.display.flip()是把画好的东西推到你眼前。clock.tick(60)是节拍器,让循环每秒跑 60 次。你运行之后会看到一个白点从底部往上飘,飘出屏幕就没了。这就是烟花的“上升阶段”。把speed改大一点,它就飞得快;把半径改大,它就变胖。所有参数都是你说了算。

提示:如果运行报错说找不到 pygame,先在命令行里执行pip install pygame。装完之后再跑一次,窗口应该就出来了。

3. 把一朵烟花拆开写:粒子类、爆炸逻辑和主循环怎么配合

3.1 粒子类:给每个小圆点一张身份证

上一章的白点只有一个,所以用两个变量x和y就够了。但爆炸要同时管 100 个点,每个点的飞行方向、速度、颜色、寿命都不一样。这时候就需要Particle类来当模板。你把它理解成一张身份证:每个粒子出生时领一张,上面写着“我从哪出发、我往哪个角度飞、我飞多快、我什么颜色、我还能活多久”。

class Particle: def __init__(self, x, y, color): self.x = x self.y = y self.color = color # 粒子大小随机 4 到 7 像素 self.size = random.randint(4, 7) # 寿命初始 25 帧,每帧减 1 self.life = 25 # 飞行角度:0 到 2π 之间随机 self.angle = random.uniform(0, 2 * math.pi) # 飞行速度:2 到 5 之间随机 self.speed = random.uniform(2, 5) def move(self): # 根据角度和速度更新 x、y self.x += self.speed * math.cos(self.angle) self.y -= self.speed * math.sin(self.angle) # 每帧寿命减 1 self.life -= 1 # 速度乘以 0.95,模拟空气阻力,越飞越慢 self.speed *= 0.95 def draw(self): # 只有还活着的粒子才画出来 if self.life > 0: pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.size)

__init__是出生设置,move是每帧移动,draw是每帧画自己。math.cos和math.sin负责把角度换算成横向和纵向的移动量,你不需要手算三角函数,只要知道角度随机就是往四面八方飞。speed *= 0.95这一句是让粒子飞出去之后慢慢减速,看起来更自然,不然它们会像子弹一样直线飞走。life减到 0 以下就不再画,粒子就“消失”了。

3.2 烟花类:先上升,再爆炸

有了粒子模板,接下来写Firework类。它管两件事:第一,先造一个白色粒子当“引线”,让它从屏幕底部往上飞;第二,当这个白点飞到一定高度,就原地生成 100 个彩色粒子,然后把白点删掉。这个“先上升后爆炸”的逻辑,就是烟花的核心节奏。

class Firework: def __init__(self): self.particles = [] # 爆炸颜色从红、绿、蓝里随机选一个 self.explosion_color = random.choice([(255, 0, 0), (0, 255, 0), (0, 0, 255)]) def launch(self): # 在屏幕底部随机横向位置,生成一个白色上升粒子 self.particles.append( Particle(random.randint(0, screen_width), screen_height, WHITE)) def explode(self): # 以当前白点的位置为中心,炸出 100 个彩色粒子 for _ in range(100): self.particles.append( Particle(self.particles[0].x, self.particles[0].y, self.explosion_color)) # 删掉作为引线的白点 del self.particles[0] def update(self): # 如果没有任何粒子,这发烟花结束 if not self.particles: return False for particle in self.particles[:]: particle.move() particle.draw() # 寿命用完就移除 if particle.life <= 0: self.particles.remove(particle) # 如果只剩引线白点,并且它飞到了屏幕上半部分,就爆炸 if len(self.particles) == 1 and self.particles[0].y < 100: self.explode() return True

launch只造一个白点,explode一口气造 100 个彩色点,update每帧负责移动、画图、清理死掉的粒子,并判断引线是否该炸。self.particles[:]是复制一份列表再遍历,这样你在循环里删除元素不会出错。y < 100是爆炸高度,数字越小炸得越高,你可以改成 200 让它低一点,或者改成 50 让它更高。

3.3 主循环:让烟花自己冒出来

最后把主循环补全。主循环要做四件事:处理退出和按键事件、更新所有烟花、随机生成新烟花、刷新屏幕。原始代码里有一个random.random() < 0.05的判断,意思是每一帧有 5% 的概率自动放一朵新烟花。你也可以按空格键手动加一朵。

fireworks = [Firework()] clock = pygame.time.Clock() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: # 按空格键手动加一朵烟花 fireworks.append(Firework()) screen.fill(BLACK) for firework in fireworks[:]: if not firework.update(): fireworks.remove(firework) # 每帧 5% 概率自动生成新烟花 if random.random() < 0.05: fireworks.append(Firework()) fireworks[-1].launch() pygame.display.flip() clock.tick(60) pygame.quit()

fireworks[:]同样是复制列表再遍历,避免删除时出错。fireworks[-1].launch()是让刚加进来的烟花先发射引线。你运行之后会看到黑色夜空里不断有白点上升,然后炸成红绿蓝的粒子雨。如果觉得烟花太少,把0.05改成0.1;如果觉得太多,改成0.02。所有参数都在你手里。

3.4 加上音效:一行代码让气氛到位

音效部分比你想的简单。你只需要准备一个 mp3 文件,放在项目文件夹里,然后在pygame.init()之后加两行:

pygame.mixer.music.load('new_year_music.mp3') pygame.mixer.music.play(-1)

load是告诉 Pygame 文件在哪,play(-1)里的-1表示无限循环。如果文件不在当前目录,就写完整路径。如果运行时报错说音频设备忙,先确认没有其他程序占用声卡,或者把pygame.mixer.init()单独写一行放在pygame.init()后面。音效不是必须的,但加上之后,烟花炸开的瞬间配合音乐,感觉完全不一样。

4. 新手最容易翻车的几个地方:避坑与排查

4.1 窗口一闪而过或者直接卡死

现象:运行代码后窗口闪一下就没了,或者一直黑屏转圈。原因:最常见的是主循环里忘了写pygame.event.get(),导致系统事件堆积,窗口无响应。另一个可能是while循环里没有clock.tick(),帧率跑到几千,电脑扛不住。解决:检查主循环里有没有事件处理,有没有clock.tick(60)。如果窗口闪退,在pygame.quit()之前加一句input()或者用try/except把报错打印出来。

4.2 粒子飞出去不消失,越积越多

现象:烟花炸完之后粒子一直留在屏幕上,或者程序越跑越卡。原因:Particle的life没有减到 0 以下,或者Firework.update里没有把life <= 0的粒子移除。解决:确认move方法里有self.life -= 1,确认update里有if particle.life <= 0: self.particles.remove(particle)。如果还是卡,把爆炸粒子数从 100 降到 50,或者把clock.tick(60)改成clock.tick(30)。

4.3 音乐文件加载报错

现象:运行到pygame.mixer.music.load时提示找不到文件或格式不支持。原因:路径写错了,或者文件不是 mp3/ogg 格式。解决:把音乐文件和 Python 脚本放在同一个文件夹,文件名不要用中文和空格。如果还是不行,换一个标准的 mp3 文件试试。注意pygame.mixer对某些高码率文件支持不好,尽量用普通音质。

4.4 爆炸位置不对,烟花在屏幕外炸了

现象:只看到白点飞上去,没看到爆炸,或者爆炸在屏幕顶部外面。原因:explode的判断条件是self.particles[0].y < 100,如果你的屏幕高度不是 600,或者引线速度太快,白点可能在到达 100 之前就飞出屏幕了。解决:把y < 100改成y < screen_height // 3,让爆炸高度跟着屏幕走。或者把引线速度调慢,在launch里把Particle的速度参数改小。

4.5 按空格键没反应

现象:想手动加烟花,但按空格没效果。原因:事件判断写成了event.type == pygame.KEYDOWN但没检查event.key,或者窗口没有获得焦点。解决:确认代码是event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE。如果还是没反应,点一下窗口标题栏让它获得焦点,再按空格。

5. 下一步学什么:从一朵烟花到一场烟花秀

你现在已经跑通了最基础的版本:一个窗口、一朵烟花、爆炸粒子、可选音效。接下来怎么继续,我按自己的学习顺序给你一条路。第一步,把爆炸粒子数从 100 改成 200,看看电脑卡不卡,卡的话就学会用clock.tick降帧率。第二步,给Particle加一个gravity属性,在move里让self.y += gravity,粒子就会往下落,更像真实烟花。第三步,把爆炸颜色改成渐变,比如从黄色过渡到红色,方法是让粒子的颜色随寿命变化,在draw里根据self.life算 RGB 值。

再往后,你可以尝试一次发射多朵烟花,让它们在不同高度爆炸。原始代码里fireworks列表可以同时装很多朵,你只需要在launch时给每朵烟花一个随机的目标高度。音效方面,可以准备多个爆炸音效,在explode的时候用pygame.mixer.Sound播放,而不是一直循环背景音乐。如果你想让烟花有拖尾效果,可以在每帧画粒子之前,把上一帧的屏幕稍微变暗而不是完全涂黑,这样粒子移动会留下残影。

下面这张表是我建议的练习顺序和对应改动点,你可以照着一条条试:

练习目标改哪里预期效果
烟花更多主循环random.random() < 0.05改成0.1屏幕上同时有多朵烟花
爆炸更大explode里range(100)改成range(200)粒子更密,但可能掉帧
粒子下落Particle加self.gravity = 0.1,move里self.y += self.gravity爆炸后粒子往下飘
颜色渐变draw里根据self.life调整颜色值粒子从亮变暗
手动控制按空格加烟花,按方向键改爆炸高度互动感更强

我第一次写这个烟花的时候,粒子飞出去就再也不回来,屏幕上白点越积越多,最后卡成幻灯片。后来才发现是忘了在update里删掉寿命耗尽的粒子。从那以后我每次写粒子系统,都强制先写“出生、移动、死亡”这三步,再写画图。这个习惯帮我省了很多后悔药。希望你也从这朵小烟花开始,把图形编程的门推开。

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

RELATED READING

延伸阅读

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