news 2026/10/11 10:51:35

Python+Pygame实战:从零构建新年烟花粒子动画系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python+Pygame实战:从零构建新年烟花粒子动画系统

简介:面向Python初学者的Pygame图形编程实践资源,以新年烟花动画为项目载体,系统讲解从环境准备、pip安装Pygame到完整代码编写的过程。内容覆盖粒子系统构建、烟花发射与爆炸逻辑、颜色随机设置、背景音乐无限循环播放等关键知识点,适合希望掌握基本图形界面开发并动手练手的读者。压缩包内为1个docx文档,大小仅16KB,包含可直接运行的完整代码示例;文档对Particle粒子类的位置、速度、角度、生命周期等属性,Firework烟花类的发射与爆炸机制,以及基于事件循环的主程序结构均有逐段解析。同时,资源还额外说明了音乐文件路径替换、粒子数量、发射频率等参数调整方式,便于按实际场景二次修改。该资源已有138人学习,既可用于日常Pygame学习,也可在节庆场景中直接使用或扩展。

1. 新年烟花动画,说到底是一个粒子系统

跨年夜想给客厅的大屏换点气氛,视频素材找不到合适的,PPT又太死板。如果你手里正好有 Python,那么用 Python 和 Pygame 库创建新年烟花动画,是我能想到的最短实现路径:一个窗口、一个主循环、几百个会飞的光点,不到两百行代码就能在屏幕上炸开一簇新年快乐。第一次跑通时你可能觉得“就这?”,但当你看着一发光点拖着亮尾升到半空、炸成一团慢慢下坠的星火,成就感一点不少。这个效果的核心不是绘图,而是一个简化版粒子系统:烟花弹是一个粒子,爆炸后分裂成几百个子粒子,每帧移动、受重力拉扯、颜色和生命逐渐衰减。这篇文章就把这套系统拆开讲透,带你把参数调到好看,顺便避开我踩过的几个坑。

2. 先搞清楚烟花的本质:Pygame粒子系统、坐标系与主循环

写任何动画,第一步都是把现象拆成“每帧要算什么、要画什么”。烟花画面可以拆成两条线:一条是烟花弹上升时划出的亮线,另一条是爆炸后向四面八方飞散的光点。这两条线在电脑里都不是线,而是无数个“位置在不断变化”的粒子。把粒子的运动规则定好,动画自然就活了。

2.1 一次爆炸就是几百个粒子的出生、扩散与消亡

一个标准的烟花表演:发亮的弹体从屏幕下方逐渐上升,尾迹是一条慢慢缩短的亮线;到达高空后爆裂,出现一个迅速扩大的亮团,然后亮点各自飞散、减速、下落、变暗。这个过程中的“亮点”,在程序里就是粒子。我们不需要记录烟花的形状,只需要记录粒子的状态,每一帧把状态更新一次,然后画出来。

一个粒子最少需要四类数据:位置(x, y),决定它画在哪;速度(vx, vy),决定它下一帧往哪移动;重力(gravity),让速度每帧都改变一点,模拟真实下坠;生命周期(life),决定粒子还能活多少帧。把这四类数据放进一个类里,每个爆炸点生成一两百个这样的对象,动画就成立了一大半。我习惯把粒子类和烟花弹类分开写,因为它们的运动逻辑完全不同:烟花弹在上升,是“单个粒子 + 尾迹”;爆炸粒子在飞散,是“成百上千个独立光点”。

物理模型上,我们做的是最朴素的一阶欧拉积分:位置每次加上速度,速度每次加上重力。每帧重复一次,轨迹自然呈现出抛物线。这个模型在视觉上完全够用,因为烟花粒子没有碰撞、没有弹性、也不跟其他粒子相互作用,真正的物理引擎不会带来任何可见收益,只会增加调试成本。有人会把这次的物理量写成“以秒为单位”,然后发现换一台帧率不同的机器,烟花速度完全不一样——这就是“一帧等于一个时间步”和“真实时间步长”的差别。我在这类动画里一律采用前者:所有参数都按帧来写,简单、可控、可复现。

顺便说一个选型判断:Pygame 自带了 Sprite 精灵类,但我不建议用它来写烟花粒子。Sprite 擅长管理碰撞、动画帧、分组渲染这类游戏逻辑,而烟花粒子既没有碰撞也没有动画帧,唯一的逻辑就是“更新一个位置、画一个圆”。用普通 Python 类维护位置、速度、生命三个字段,代码更直接,调试时一眼能看到头。项目规模没到需要场景树的程度,就别让框架反过来约束你的代码。

2.2 Pygame主循环的最小骨架:让窗口每秒稳定刷新60次

写 Pygame 动画之前,先把窗口和主循环搭好。窗口大小我一般用 1280x720,宽屏比例足够让烟花飞得开;如果屏幕是 16:10,也可以设成 1280x800。最小可运行骨架如下:

import pygame WIDTH, HEIGHT = 1280, 720 FPS = 60 pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("New Year Fireworks") clock = pygame.time.Clock() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False screen.fill((0, 0, 0)) # 之后所有粒子的更新和绘制都会放在这里 pygame.display.flip() clock.tick(FPS) pygame.quit()

这个骨架的逻辑分三段:事件、更新、绘制。事件处理负责响应鼠标、键盘和关闭窗口;更新阶段计算“这一帧世界发生了什么变化”;绘制阶段把变化结果画到屏幕上。事件处理放在最前面,是因为用户操作要优先于游戏逻辑,否则关闭窗口时可能会卡顿一下才退出。

clock.tick(FPS)是让动画匀速的关键,它会尽量让主循环每秒执行 60 次,每次间隔大约 16 毫秒。FPS 取 60 是甜点位:低于 45 帧,粒子的移动会明显顿挫;高于 120 帧,视觉提升很小,CPU 却白白多烧一倍。pygame.display.flip()负责把绘制好的画面一次性呈现到窗口上,而前面所有draw操作都是在后台缓冲上作画,顺序颠倒会出现闪屏或画面撕裂。

Pygame 的坐标系必须提前记住:原点在窗口左上角,x 向右为正,y 向下为正。所以“向上飞”的初速度必须是负值,比如vy = -12。我见过不少人第一次写 Pygame 把烟花写成了下坠的流星,几乎都是在这里栽的。记住这条,后面调参数时会省很多事。

3. 把烟花拆成发射与爆炸:Pygame核心代码直接跑起来

这一章进入正题。我会把烟花拆成三个阶段:烟花弹上升、爆炸生成粒子、粒子飞散消亡。分三个类写完,再组装进主循环。代码不追求炫技,只求你能看明白每一行在干嘛。

3.1 烟花弹的升空阶段:用一串上行的轨迹制造“蓄力”感

烟花弹是整场表演的前奏。它从屏幕底部附近生成,以负的 y 方向速度向上飞,同时受重力影响逐渐减速。当竖直方向的速度从负变成非负,说明它到了最高点,此时触发爆炸。实现如下:

import math import random import pygame class Firework: def __init__(self, x, y): self.x = x self.y = y self.vx = random.uniform(-1, 1) # 水平方向轻微漂移 self.vy = random.uniform(-14, -11) # 向上飞,y轴向下所以为负 self.gravity = 0.15 self.trail = [] # 保存最近十几帧的位置 self.exploded = False def update(self): self.vy += self.gravity self.x += self.vx self.y += self.vy self.trail.append((self.x, self.y)) if len(self.trail) > 12: self.trail.pop(0) if self.vy >= 0 and not self.exploded: self.exploded = True def draw(self, screen): for i, pos in enumerate(self.trail): radius = max(1, i // 3) # 越早的轨迹点越小 pygame.draw.circle(screen, (255, 220, 180), (int(pos[0]), int(pos[1])), radius)

这段代码里有三个关键设计。第一,vy初始在 -14 到 -11 之间,配合 0.15 的重力,能让烟花弹在屏幕中上部爆炸。你可以心算一下:初速度 -14,每帧加 0.15,大约 93 帧后速度归零,再叠加剩下位置移动,正好落在窗口上部三分之一区域。第二,尾迹只保留 12 个点,每个点画成半径逐渐缩小的圆,视觉上形成一条前亮后暗的短光轨。第三,爆炸的触发条件不是“到达某固定 y 坐标”,而是“竖直速度由负变正”,这样以后调整重力或初速度时,爆炸点会自动跟着弹道最高点走,不用反复改一个魔法数字。

trail.pop(0)是列表头部出队,复杂度 O(n),但长度只有 12,完全无感知。如果你把尾迹长度加到 50 以上,就要考虑用collections.deque了,这是个边界。上升阶段不建议给烟花弹画大圆,因为大圆会盖住尾迹,一个 3 像素的亮点加上一条渐隐轨迹,才是“蓄力感”的来源。

3.2 爆炸粒子:从同一点向各方向飞散,又被重力拽回地面

爆炸是烟花的灵魂。从爆炸点生成一两百个粒子,每个粒子的初速度方向随机、速度大小随机,然后受重力影响做抛物线运动。粒子类如下:

class Particle: def __init__(self, x, y): self.x = x self.y = y angle = random.uniform(0, math.tau) # 0 到 2π,全方向 speed = random.uniform(1, 9) self.vx = math.cos(angle) * speed self.vy = math.sin(angle) * speed - 1.5 # 整体稍向上,爆点更饱满 self.gravity = 0.12 self.life = random.randint(40, 80) self.max_life = self.life self.color = random.choice(COLORS) self.size = random.uniform(2.0, 4.5) def update(self): self.vy += self.gravity self.x += self.vx self.y += self.vy self.life -= 1 def draw(self, screen): ratio = self.life / self.max_life radius = max(1, int(self.size * ratio)) pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), radius)

random.uniform(0, math.tau)生成 0 到 2π 的全方向随机角度,然后通过cos和sin拆成水平与竖直初速度。这里不能偷懒地写“随机给 vx 和 vy 各取一个值”,那样粒子会聚集在四个对角方向,炸出来不是一朵花而是一个叉。速度范围 1 到 9 是经验值:低于 1,粒子聚成一团,没有爆炸的张力;高于 9,大量粒子两秒内飞出屏幕,中段就空了。

vy整体减掉 1.5 是个小技巧。爆炸瞬间的粒子云若完全随机,视觉中心会略微偏低,看起来像半朵花;把每个粒子的竖直初速度都抬一点,爆炸云会有一个更饱满的球状轮廓,也更接近真实烟花“先外扩、后下落”的节奏。life取 40 到 80 帧,对应 0.6 到 1.3 秒,这个区间刚好够看完整团烟花的展开;生命周期太长会拖出一片雾,太短则刚炸开就消失。颜色从预置色板里选,保证整体画面不会失控。

3.3 组装:把升空、爆炸和主循环接在一起

把前两节的类定义复制到文件里,再加上一组颜色常量和主循环,就是一个完整可运行的程序。组装部分的核心逻辑如下:

COLORS = [(255, 60, 60), (255, 180, 60), (240, 240, 120), (100, 220, 255), (180, 120, 255), (120, 255, 180)] def explode(x, y, particles): for _ in range(random.randint(180, 260)): particles.append(Particle(x, y)) fireworks = [] particles = [] running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 新烟花弹:同时最多 3 发,按泊松节奏生成 if len(fireworks) < 3 and random.random() < 0.08: fireworks.append(Firework( random.randint(100, WIDTH - 100), HEIGHT + 20 )) # 更新:先弹道,后粒子 for firework in fireworks[:]: firework.update() if firework.exploded: explode(firework.x, firework.y, particles) fireworks.remove(firework) for particle in particles[:]: particle.update() if particle.life <= 0: particles.remove(particle) # 绘制:清屏、弹道、粒子,顺序别反 screen.fill((0, 0, 0)) for firework in fireworks: firework.draw(screen) for particle in particles: particle.draw(screen) pygame.display.flip() clock.tick(FPS) pygame.quit()

random.random() < 0.08是一种泊松式生成节奏:不是每帧都出,而是每帧有 8% 概率生成一发,平均约 12 帧出一发;配合len(fireworks) < 3的上限,能保证画面上同时只有两三发在飞,有层次但不拥挤。烟花弹的出生点放在HEIGHT + 20,也就是屏幕底部下方 20 像素,这样它一出现就已经在向上运动,不会有从底边冒出来的突兀感。

两个循环都用了fireworks[:]和particles[:]副本迭代,这是为了在遍历时安全删除元素。如果直接for firework in fireworks然后remove,被删除元素的下一个元素会跳过,导致部分烟花弹不爆炸、部分粒子提前消失。这个坑很隐蔽,跑起来才会发现“怎么烟花越来越少”。

绘制顺序也值得说:先画尾迹,再画粒子。如果反过来,爆炸粒子会盖住烟花弹的尾迹,视觉上层次就乱了。另外,这个版本的清屏是纯黑覆盖,出来的效果是“干净”但略显生硬;第 4 章会换成半透明残影,画面立刻会有光晕感。

4. 画面好不好看,全在绘制细节与性能取舍

能跑起来只算第一步,好不好看才是这类程序的价值所在。这一章解决两件事:一是在粒子数量爆炸时不掉帧,二是用最小的代价让烟花看起来有“光”而不是一堆色块。

4.1 粒子上限与对象回收:别让一帧画5000个圆

每个粒子在屏幕上就是一个圆,Pygame 画一个圆的性能消耗不低,几千个同时出现,再好的机器也会卡。烟花的生成节奏决定了粒子数量天然是脉冲式的:一发爆炸瞬间新增一两百个,连续几发后旧粒子还没消亡,总数量可能突破四位数。我一般会在爆炸入口做一个总量控制:

MAX_PARTICLES = 2000 def explode(x, y, particles): count = random.randint(180, 260) if len(particles) + count > MAX_PARTICLES: count = max(0, MAX_PARTICLES - len(particles)) for _ in range(count): particles.append(Particle(x, y))

这个控制的思想是:宁可少生成一些粒子,也不让一帧的绘制量超过机器的承受能力。2000 是经验值,在多数集成显卡上都能稳住 60 帧;如果你在比较老的机器上运行,可以降成 1200。想更讲究,可以用对象池提前预分配一批 Particle 对象反复使用,但在这个量级下没必要——Python 列表的新建和回收足够快,真正吃掉性能的是每个粒子的draw调用,而不是对象本身。

性能排查还有一条硬规矩:不要在draw()方法里创建任何临时对象。有人为了让粒子更好看,每帧在 draw 里新建一个Surface来做半透明叠加,结果帧率直接从 60 掉到 20。pygame.draw.circle这类直接绘制函数是最快的,所有颜色和半径都应在 draw 之前算好,draw 内部只做一件事:把圆画上去。

4.2 残影技术:让烟花有光晕而不是光点

纯黑清屏的问题是:每个粒子只存在一帧,视觉上就是一个个离散的小圆点,连不成光轨。真实烟花给人“炸开一团光”的感觉,是因为人的视觉对亮光有短暂残留效应。程序里可以主动模拟这种残留:不清空屏幕,而是每次盖上一层半透明黑,让旧帧画面以较低亮度保留下来,新粒子叠加上去,形成连续的光轨。

overlay = pygame.Surface((WIDTH, HEIGHT)) overlay.set_alpha(30) # 30 是残影强度,越小拖尾越长 overlay.fill((0, 0, 0)) # 在主循环内,取代 screen.fill((0, 0, 0)) screen.blit(overlay, (0, 0))

原理很简单:屏幕没有完全复位,而是被一层带透明度的黑色覆盖,旧帧整体变暗一些,但没消失;下一帧新粒子再画上去,视觉上就多了一层柔和的光晕。set_alpha(30)是控制残影长短的关键参数:值越小,旧帧保留得越久,拖尾越长;值越大,残影越短,画面越干净。我一般从 30 起步,往 40 方向调会更利落,往 20 方向调会更梦幻,这个参数没有绝对正确,调它的时候盯着烟花密集段的画面,不糊就好。

这里有个常见的翻车点,直接写出来提醒:

注意:直接screen.fill((0, 0, 0, 30))是无效的。主显示 Surface 没有 alpha 通道,fill 的第四个参数会被忽略,实际效果和纯黑清屏一模一样。残影必须走“独立 Surface + set_alpha + blit”这条路。

残影也不是越强越好。如果粒子数量较少、画面大部分时间是暗的,残影会把整块屏幕越拖越灰,最后变成一团黑雾。解决方式是把 alpha 降到 15 到 20,让旧帧更快退场;同时适当提高粒子的初始亮度,让新亮点压过旧影。

4.3 从“能动”到“好看”:尺寸、亮度与一张参数速查表

粒子绘制最常见的平庸效果是“全程一样大、一样亮”,看起来像一堆色块在移动。真正的烟花爆开时,中心先是一片亮白,然后才显露出它本来的颜色,最后逐渐熄灭。这个渐变不用复杂算法,只需要把颜色在“白色”和“本色”之间做插值:

def draw(self, screen): ratio = self.life / self.max_life # 1 到 0 的生命比例 radius = max(1, int(self.size * ratio * 3)) r = self.color[0] + int((255 - self.color[0]) * ratio) g = self.color[1] + int((255 - self.color[1]) * ratio) b = self.color[2] + int((255 - self.color[2]) * ratio) pygame.draw.circle(screen, (r, g, b), (int(self.x), int(self.y)), radius)

ratio从 1 降到 0 的过程中,粒子半径从大变小,颜色从接近白色渐变到预定色。这样爆炸瞬间是白热的亮点,飞行途中显露出红、金、蓝等本色,最后缩成小点消失。size乘 3 是扩大整个爆炸云层尺寸的快捷方式,想要更大的烟花就调这个系数,而不是去调粒子数量。

颜色插值有个细节:不要直接用“每帧 RGB 减固定值”的线性衰减,那样颜色会发灰,观感很差。从白往本色收敛,烟花的色彩饱和度保持得更好,这也是我用上述公式而不是简单减法的原因。

到这里,把整套参数汇总成一张速查表,方便你照着调:

参数建议范围作用
重力0.08 ~ 0.15上升减速与下落加速的强度
升空初速度-14 ~ -11决定爆炸高度,绝对值越大飞得越高
单发粒子数180 ~ 260爆炸密度,太高会拖帧率
爆炸速度范围1 ~ 9爆炸扩散半径,越大炸得越开
残影透明度15 ~ 40越小拖尾越长,越大画面越干净
粒子生命40 ~ 80 帧单发烟花的存续时间
粒子绘制放大2 ~ 4虚拟粒子尺寸,影响爆炸云大小

调参数时务必一次只动一个。改完至少看 30 秒连续画面,而不是盯着一帧截图判断,因为烟花是动态效果,截图会骗人。我自己的习惯是先把一组值固定下来,跑一整轮,再决定动哪个指标;边跑边改最容易陷入“越调越乱”的境地。

5. 常见问题与避坑:新手最容易翻车的4个现场

这一章写的都是我实际见过甚至踩过的坑。每一个都按“现象 → 原因 → 解决”的顺序展开,你可以直接对照自己的代码定位。

5.1 帧率掉到个位数:先数一下你的粒子数量

现象:程序刚启动很流畅,第二发烟花炸开后明显变卡,第三发之后动画变成幻灯片,CPU 风扇开始狂转。

原因:绝大多数时候不是代码逻辑问题,而是粒子的量超出了机器承受范围。烟花爆炸是一瞬间新增一两百个粒子,如果旧粒子还没走完生命周期,连续几发叠加,粒子总数可能超过 5000。update循环要遍历所有粒子,draw也要遍历所有粒子,量一大,每一帧的耗时直线上升。

解决:先确认是不是这个原因,再动手优化。在主循环里加一段临时侦探代码,每秒打印一次当前粒子数量:

last_print = 0 # 主循环内 now = pygame.time.get_ticks() if now - last_print > 1000: print("particles:", len(particles), "fireworks:", len(fireworks)) last_print = now

跑一次,如果峰值稳定在 2000 以内但还是卡,那问题就不在数量,而是某个draw内部创建了临时对象。如果峰值经常飙到 4000 以上,就用 4.1 节的MAX_PARTICLES做上限。这条判断逻辑很朴素:先量化,再下结论,不要瞎猜代码哪里慢。

5.2 残影没出现,屏幕反而越拖越黑:半透明覆盖的正确姿势

现象:明明用了“半透明黑色覆盖”想制造光晕,结果前几秒效果正常,30 秒后整个屏幕越来越黑,烟花只能在黑雾里若隐若现。

原因:常见两个。第一,直接调screen.fill((0, 0, 0, 30)),alpha 参数被忽略,屏幕被完全清空,残影根本没生效。第二,残影确实生效了,但每次覆盖都是把旧帧像素向黑色压一档,粒子数量少的时候,新亮点不足以抵消旧帧的衰减,暗色区域越积越多,屏幕就慢慢“脏”了。

解决:先确认用的是独立 Surface 方案,overlay.set_alpha(30)后再screen.blit(overlay, (0, 0))。如果残影生效但发黑,把set_alpha从 30 降到 18 左右,让旧帧退出更快。再看粒子颜色公式里的初始亮度系数是不是太低,建议爆炸瞬间颜色接近白色,而不是直接涂本色。视觉上的平衡是:新粒子亮度与旧帧衰减速度匹配,画面才既干净又有光晕。

5.3 颜色通道越界:闪烁、反色都从RGB超出0~255开始

现象:烟花炸开后运行十几秒,有些粒子颜色开始乱跳:红色粒子突然变蓝,白色粒子出现噪点,甚至整颗粒子颜色像“反色”一样诡异。

原因:颜色渐变计算里,color[0] + int((255 - color[0]) * ratio)严格来说不会越界,但很多人会改成“每帧颜色减 5”这类写法。RGB 的三个通道是 0 到 255 的整数,减过头变成负数或者加过头超过 255,Pygame 在不同绘制路径下对越界值的处理不一致,有的截断、有的取模,表现出来就是颜色相位错乱。

解决:所有颜色运算结果强制夹紧到 0 到 255,写一个通用小函数:

def clamp(value): return max(0, min(255, int(value)))

然后每个颜色分量的计算结果都过一遍clamp。这个坑最气人的地方是它不一定会报错,只是颜色乱跳,很容易让人误以为是随机数生成的问题。先把所有颜色计算都过一遍边界保护,再排查其他可能性。

5.4 烟花没升空就炸开:先检查初速度和状态切换

现象:烟花弹刚出现在屏幕底部就爆炸,或者干脆往下飞进地面,还有的是“炸了两遍”——同一发烟火出现两次爆炸效果,粒子数量翻倍。

原因:初速度方向写反了是第一个高频原因。Pygame 的 y 轴向下为正,向上飞必须是负的vy,写成正数就会看到烟花往下钻。第二个原因是状态判定位置不对:如果在速度更新之前就判断vy >= 0,那么第一帧初始速度还没被重力改变,负值会触发误判,烟花弹刚出生就被标记为“已爆炸”。第三个原因是没有加exploded守卫,烟花弹在上升过程中多次满足爆炸条件,反复调用explode。

解决:先把Firework的初速度打印出来确认是负数;然后把爆炸判定放在速度和位置都更新完之后,并且加上and not self.exploded的守卫:

def update(self): self.vy += self.gravity self.x += self.vx self.y += self.vy if self.vy >= 0 and not self.exploded: self.exploded = True

主循环里检测到exploded后就remove烟花弹对象,不要等到下一帧再处理。这三个原因往往是并列出现的:修正了初速度,还要确认判定顺序;加了判定顺序,还要确认爆炸只触发一次。按这个顺序排查,五分钟内能解决。

6. 让它能玩起来:点击引爆、文字烟花与长时间运行的收尾习惯

程序跑通之后,你会发现它适合放在角落循环播放,但还不够“好玩”。加两个小交互,烟花就从背景动画变成可以展示的小作品。

6.1 鼠标点击处生成烟花:把发射权交给看的人

在事件处理里加一个鼠标监听,点击左键就在点击位置爆炸,或者在点击位置生成一颗从底部升上来的烟花弹:

elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: # 方式一:点击处直接爆炸 # explode(event.pos[0], event.pos[1], particles) # 方式二:点击后从底部发射一颗,飞到点击处上方再炸 fireworks.append(Firework(event.pos[0], HEIGHT + 20))

方式一更直接,“指哪炸哪”,适合演示粒子效果;方式二保留了完整的上升尾迹,更有仪式感。我一般两种都留,用注释切换。鼠标交互加上之后,整个程序的定位就从“播放动画”变成了“互动的烟花画布”,跨年夜给小朋友玩,比看视频有意思得多。

6.2 文字烟花:把“2025”变成像素采样点

文字烟花是进阶玩法里性价比最高的一个:把文字渲染到一张临时 Surface 上,遍历像素,记下所有不透明点的位置;爆炸时从这些位置随机选一个作为粒子发射中心,文字就会被烟花一笔一笔“点亮”。

font = pygame.font.SysFont("simhei", 180, bold=True) text_surface = font.render("2025", True, (255, 255, 255)) points = [] for px in range(0, text_surface.get_width(), 2): for py in range(0, text_surface.get_height(), 2): if text_surface.get_at((px, py))[3] > 128: points.append((px, py))

range(_, _, 2)是每隔一个像素采样,控制总点数;整个遍历只在初始化时执行一次,不能每帧循环。使用概率:pygame.font.SysFont("simhei", ...)依赖本机装有该字体,如果没有,换成你系统里存在的中文字体即可。文字生成后,在主循环里每隔十几帧从points随机取一个坐标,在那个位置调用explode,画面上的字会被连续的烟花爆炸逐渐拼出来。

我自己的收尾习惯是:先把 6.1 和 6.2 的功能加上,然后让它循环运行一整晚,观察内存是否稳定增长、粒子列表是否正常回收。动画类程序最怕的不是效果难看,而是“看着能跑,跑两小时崩了”。跑通之后,我会把参数调回比较克制的范围——不要每帧都炸,留出呼吸感,烟花只有等一等再看才惊艳。希望帮到你。

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

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

DeepSeek实操指南:从注册到高级功能的完整提效路径

简介&#xff1a;面向广大科技爱好者、学生、研究人员及相关从业者的《DeepSeek新手宝典&#xff1a;从入门到精通的超详细指南》&#xff0c;以PDF文档形式系统讲解DeepSeek的注册登录、网页与移动端安装、界面要素及功能菜单&#xff0c;并重点演示智能问答、编程辅助、创意生…

作者头像 李华
网站建设 2026/10/11 10:49:31

全开源本地去水印系统源码:基于OpenCV与FFmpeg的实现

简介&#xff1a;这套去水印系统源码为全开源交付&#xff0c;所有解析与处理逻辑均在本地实现&#xff0c;不依赖第三方接口&#xff0c;适合个人站长、PHP开发者以及关注隐私的用户快速部署或二次开发。压缩包共21.36MB&#xff0c;内含165个文件&#xff0c;其中76个PHP文件…

作者头像 李华
网站建设 2026/10/11 10:46:46

AI芯片软硬件协同设计的第六环:破解语义坍塌

1. 为什么“AI芯片的软硬件设计”这个编号6特别值得深挖“AI芯片的软硬件设计 6”——看到这个标题&#xff0c;第一反应不是“又一篇泛泛而谈的科普”&#xff0c;而是&#xff1a;前5篇讲了什么&#xff1f;为什么这一篇要单独标号&#xff1f;它到底在序列中承担什么不可替代…

作者头像 李华
网站建设 2026/10/11 10:46:22

Oracle RMAN全能备份脚本:生产级分层设计与DG感知实践

简介&#xff1a;本资源是一套面向Oracle DBA及中高级数据库运维人员的RMAN自动化备份实战脚本集&#xff0c;聚焦企业级数据库安全防护核心需求&#xff0c;解决日常备份策略落地难、脚本零散、定时执行不规范等痛点。压缩包共5个Shell脚本&#xff08;.sh&#xff09;&#x…

作者头像 李华