
简介基于Python的烟花模拟代码以pygame为图形库实现动态烟花绽放面向Python初学者、对游戏动画或粒子效果感兴趣的开发者正好解决“如何用Python快速做一个可运行的放烟花程序”这类问题。代码采用主程序与粒子模块分离的结构在800×600窗口中随机生成发射数量、位置与颜色创建烟花对象并循环调用update和draw方法完整呈现从升空、爆炸到火花消散的视觉过程粒子运动融入重力、初速度等物理规则轨迹更真实也能帮助理解事件循环、对象状态更新和逐帧屏幕绘制等pygame核心概念。压缩包为zip格式共6个文件包含2个Python脚本分别承担主循环和粒子逻辑、一个pyc字节码缓存文件、一张背景图片、以及Git和macOS相关的辅助配置文件整体大小15.46MB结构简洁解压后可直接运行和调整参数。已有400人学习下载适合在此基础上继续添加音效、鼠标交互、多种烟花类型或多线程同屏绽放是一份实用性较强的pygame动画编程入门参考。1. 什么是 python 烟花代码一句话说清实现原理别把放烟花想成什么高深特效拆开看就三件事一颗“炮弹”从地面升空升到某个高度后停住然后炸开成几十上百个带颜色的小点这些小点受重力影响往下掉、慢慢变暗直到消失。python 烟花代码做的就是把这三件事用 turtle 画笔画出来用最基础的数学模拟物理运动不需要任何第三方游戏库装好 python 环境就能直接跑。适合刚学完基础语法想练手的人也适合给小朋友做图形化编程演示——你能改的只有参数配色、高度、粒子数量都在代码顶部集中定义改完刷新就是新效果。2. turtle 实现烟花的底层逻辑画笔模式、定时器与随机数2.1 为什么用 turtle 而不是 pygameturtle 是 python 标准库画线条和圆点时本质是控制一支“笔”在画布上挪动、落下、抬起来对做烟花这种“点集动画”非常够用。pygame 自然更强能处理图片、音效和大量粒子但引入它会让代码依赖加重对只想看个烟花效果的人不划算。常见做法是在一张黑色背景的画布上画彩色圆点每帧把上一帧的点擦掉再画新位置利用人眼视觉暂留形成动画——这正是 pygame 做粒子系统的老套路用 turtle 实现时只需对应三个 APIscreen.tracer(0)关闭自动刷新、turtle.dot(size, color)画圆点、screen.update()手动刷新。2.2 三个关键状态的建模烟花粒子在每一帧里需要维护的是位置(x, y)、速度(vx, vy)和生命周期(life)。位置决定画在哪速度决定下一帧怎么动生命周期决定它变暗和消失的节奏。代码里每个粒子就是一个小字典particle { x: 0, y: 0, # 当前位置 vx: 0, vy: 0, # 当前速度像素/帧 life: 50 # 剩余存活帧数每帧减 1 }运动方程是粒子系统的核心逻辑。现实中烟花爆炸后的碎片受重力和空气阻力影响前者让 vy 每帧匀速减小后者让 vx、vy 各自乘一个小于 1 的系数使速度逐渐衰减。代码在每帧更新时这样计算particle[vx] * 0.98 # 空气阻力速度每帧略有损失 particle[vy] particle[vy] * 0.98 - 0.3 # 重力vy 额外减小 particle[x] particle[vx] particle[y] particle[vy] particle[life] - 1这里有两个参数可以调0.98是阻力系数调大如 0.995粒子飘得更远调小如 0.95粒子很快停住0.3是重力加速度调大整个烟花坠落更快、更像下雨而不是开花调小则粒子在空中停留更久会有点像水母散开。选择字典而不是单独列表存各个属性是因为每个粒子的属性要一起访问和更新字典可读性最好后期扩展颜色、大小也方便。2.3 颜色生成绕开 turtle 的 RGB 限制turtle 的dot()方法接受颜色名或 RGB 三元组但默认模式是 0 到 1 的浮点数如果直接传 0 到 255 的整数值颜色会完全错乱这是一开始最容易翻车的地方。常见做法是先把画布颜色模式切到 255screen.colormode(255)然后生成烟火色。如果直接用随机 RGB得到的颜色普遍偏灰暗和真实烟花的鲜艳感差很远更接近真实效果的是 HSV 转 RGBH 色相随机取 0 到 360S、V 固定偏亮再转换回 RGB。这样整个烟花的各个粒子色相相近但略有不同视觉效果是“同一朵花有渐变层次”而不是扎眼的七彩乱闪。import colorsys import random def hsv_to_rgb_255(h, s, v): r, g, b colorsys.hsv_to_rgb(h / 360.0, s, v) return int(r * 255), int(g * 255), int(b * 255) h random.uniform(0, 360) # 随机主色调 color hsv_to_rgb_255(h, 0.9, 1.0) # 高饱和、高亮度参数说明H 是色相0 是红、120 是绿、240 是蓝随机取一个值作为整朵烟花的主色S 是饱和度0.9 保证颜色鲜艳不发灰V 是明度1.0 保证亮眼。每一帧还可以让粒子在生命值逐渐减少时把 V 跟着降低实现变暗渐隐的效果。3. 把烟花拆成粒子一粒一画的动画循环与内存管理3.1 粒子数组的初始化与爆炸一次烟花爆炸本质上就是一次性生成 60 到 120 个粒子方向均匀地铺满 360 度但每粒的速度大小要加一点随机抖动这样爆炸后的形状不是完美圆形而更像真实烟花那种边缘不规则的球状扩散。初始化弹道被写成一个独立函数def explode(x, y): particles [] for _ in range(80): # 粒子数量控制爆炸密度 angle random.uniform(0, 2 * 3.14159) # 均匀取一个角度 speed random.uniform(2.5, 6.0) # 速度抖动制造不规则形状 vx math.cos(angle) * speed vy math.sin(angle) * speed particles.append({ x: x, y: y, vx: vx, vy: vy, life: random.randint(40, 70) # 每粒寿命不同先后消失 }) return particlesrandom.uniform(2.5, 6.0)是粒子速度的上下界上界越高速粒子飞得越远一朵烟花的爆炸半径就越大random.randint(40, 70)让粒子不是同一帧消失而是依次变暗这样烟花的尾巴是逐步收掉的更像真的。3.2 主循环擦旧画新是动画的基本功烟花能“动”起来靠的是主循环不断做的三件事更新所有粒子位置、画在新位置、把上一帧的旧粒子从画布上移除。def draw_frame(particles): for p in particles: p[vx] * 0.98 p[vy] p[vy] * 0.98 - 0.3 p[x] p[vx] p[y] p[vy] p[life] - 1 alpha max(0, p[life] / 50.0) # 用剩余寿命计算亮度比例 r, g, b hsv_to_rgb_255(h, 0.9, alpha) turtle.dot(5, (r, g, b)) # 画粒子直径 5 像素这里有一个所有做动画的人都会踩的坑turtle 的 dot 不是“精灵”它是直接盖在画布上的永久墨迹不清除的话下一帧旧点还在画面会堆成一条混乱的轨迹。正常做法是每帧开始时用背景色填充一个全屏矩形把上一帧盖掉相当于擦黑板turtle.color(black) turtle.begin_fill() for _ in range(4): turtle.forward(screen_width) turtle.left(90) turtle.end_fill()擦屏和画帧交替进行速度要匹配得上否则烟花会闪。这里screen.tracer(0, 0)配合screen.update()手动刷新就能稳定在几十帧——不要让 turtle 的默认自动刷新机制接管它会每画一个粒子就同步一次屏幕80 个粒子一次爆炸就要刷新 80 次动画会被拖得一顿一顿的。3.3 内存和速度的平衡粒子是每帧全量更新、画完就释放的不存在长期累积的问题但要注意的是别用 turtle 的clear()全清画布它会把当前帧所有绘制痕迹都清掉包括背景导致你再画时要重新画整个背景开销凭空多出好几倍。正确做法是始终维护一张黑色全屏底作为背景垫底粒子死亡后直接丢弃其字典对象引用让 python 的垃圾回收自然处理。如果发现某个烟花代码跑久了变慢多半不是内存泄漏而是主循环里频繁创建临时对象、或每帧刷新画布的次数过多优化方向是减少循环里无意义的计算把颜色模式、笔宽度等不变的状态提前设定好。4. 单击放一发点击事件、坐标映射与多窗口适配4.1 点击坐标转画布坐标的陷阱turtle 的onscreenclick()回调会给两个参数分别是点击处的 x、y 坐标但坐标系方向要注意turtle 默认以画布中心为原点向右是 x 正方向向上是 y 正方向这和很多 GUI 框架的“左上角为原点”相反。点击画布上半部分得到正 y、下半部分得到负 y直接拿这个坐标给爆炸函数使用是没问题的因为它已经是 turtle 的逻辑坐标了。真正容易出错的是设置画布大小时用setup(width, height)传入的宽高是按像素算的而画布的逻辑坐标范围默认是 -400 到 400 或类似值两者不是一回事。常见做法是screen turtle.Screen() screen.setup(800, 600) # 窗口像素尺寸 screen.tracer(0, 0) screen.colormode(255) def launch(x, y): particles.extend(explode(x, y)) # 直接使用回调坐标 screen.update() screen.onscreenclick(launch)4.2 发射逻辑炮弹升空后再爆炸点击之后立刻爆炸虽然能出效果但真实烟花先升空再炸开的过程才是灵魂。更完整的做法是点击时生成一个炮弹粒子让它带着向上的初速度飞升到某个高度后再调用爆炸函数def launch(x, y): rockets.append({ x: x, y: y, vx: 0, vy: random.uniform(8, 12), # 上升速度 target_y: y random.randint(120, 260) # 目标爆炸高度 }) screen.update() def update_rockets(): for r in rockets[:]: r[vy] - 0.25 # 上升过程中减速 r[x] r[vx] r[y] r[vy] if r[vy] 0 or r[y] r[target_y]: particles.extend(explode(r[x], r[y])) rockets.remove(r)这个设计里关键参数是target_y决定了爆炸高度取 120 到 260 之间的随机值会让每次爆炸高低错落天空层次更丰满vy从 8 到 12 随机取配合重力逐帧减速炮弹就能在最高点附近自然“悬停”再炸开——实际上是 vy 减到 0 时触发爆炸效果和真烟花引信燃到顶点再炸完全一致。遍历rockets[:]而不是rockets是因为循环中要删除当前元素直接遍历原列表会跳过元素或报错。4.3 一个常见问题循环里更新动画要用定时器而不是 for 死循环写动画代码时新手最容易写一个while True来无限循环刷新这在 turtle 里会直接卡死事件响应点击窗口没反应因为onscreenclick()事件需要主循环有空闲才能被处理。正确做法是screen.ontimer(loop, 30)设置每 30 毫秒调用一次主循环函数30 毫秒大约是 33 帧每秒人眼看着是流畅的def main_loop(): update_rockets() draw_frame(particles) screen.update() screen.ontimer(main_loop, 30) main_loop() screen.mainloop()ontimer是 turtle 提供的定时器接口回调自己main_loop再注册自己就相当于一个循环但它不会抢占事件循环点击和窗口缩放都能正常响应。帧间隔 30 毫秒是通用值若电脑性能偏弱可以改成 40 或 50 毫秒牺牲流畅度换稳定反之性能好的机器 16 到 20 毫秒也能跑但要注意 Flash 的花哨感不是靠帧率堆出来的30 毫秒已经足够。5. 避坑click 没反应、粒子堆成一团、动画越来越卡的三个常见问题做烟花代码最容易翻车的不是原理而是 turtle 的一些固有行为不搞清楚看效果就像见了鬼。以下三条是我拆这个项目时实际踩过的坑按“现象 → 原因 → 解决”写清楚。现象一点击屏幕后毫无反应控制台也没有任何报错。原因常见于两处。一是screen.onscreenclick(launch)注册的launch函数写在screen.mainloop()之后——mainloop 一旦开启就会阻塞注册代码永远执行不到二是没有把点击函数绑定到 screen 对象上而是绑定到了某个 turtle 实例上turtle 实例是有自己独立坐标系的绑定错对象事件根本不会触发。另外在tracer(0)关闭自动刷新的情况下点击回调里如果不手动调用screen.update()画面不刷新看起来就像没反应。解决把onscreenclick注册放在mainloop之前统一用全局的screen对象绑定事件回调函数里完成粒子追加后立刻screen.update()强制刷新一帧。推荐把注册代码和主循环代码写在一起用注释分隔“事件注册区”和“动画循环区”。现象二烟花爆炸后粒子全部堆在一个地方不动或者东一个西一个乱飞。原因粒子的运动逻辑里如果对每个粒子都重新计算了角度、速度但画点时却错误地把所有粒子坐标画成了同一个 x、y——比如误用了炮弹自身的坐标或者代码里对particles列表做了切片复制新粒子追加到了副本而不是原列表导致主循环更新的是空列表。解决粒子数组只维护一份全局引用在explode()里return新列表、调用处用particles.extend()追加不要用particles.append(explode(...))这样把整个列表塞成嵌套结构。画点时逐粒子从自身 dict 里取坐标不准引用火箭的坐标。日志排查时可以在每帧打印len(particles)和第一个粒子的坐标看到数字不涨或坐标不变就能立刻定位。现象三运行几分钟后动画越来越卡最后甚至像幻灯片。原因最常见的是把已死亡的粒子留在了particles列表里每帧都在遍历和绘制大量life为负、坐标为负无穷的无效粒子另一个隐蔽原因是每帧刷新画布时用了全屏clear()导致 turtle 底层画布对象不断重建内存膨胀。turtle 本身不是为高性能粒子系统设计的粒子数量一旦上千刷新开销会线性增长。解决主循环每帧开头先做一次过滤只保留life 0的粒子同时不要用clear()用“画背景色矩形覆盖”的方式擦旧帧。过滤代码particles[:] [p for p in particles if p[life] 0] rockets[:] [r for r in rockets if r[vy] -1]注意particles[:] ...是原地修改列表而particles ...会创建一个新列表并把全局引用替换掉——如果主循环之外的代码还持有旧引用就会更新不到新列表所以一律用切片赋值原地过滤。6. 进阶玩法自定义发射方向、配色方案与时长控制基础版能跑通之后再往深走就是三件事让烟花朝指定方向炸、自定义每朵花的颜色主题、控制整场烟花的持续时长和节奏。这三个需求对应三个代码级的调整点。定向爆炸的实现思路其实不复杂给explode()加两个参数base_angle主方向和spread扩散范围粒子角度在base_angle - spread到base_angle spread之间取速度还是随机这样就会朝特定方向炸开。模拟一个“转角烟花”点击屏幕右侧则往左上方炸点击左侧则往右上方炸def explode(x, y, base_angle0, spread360): particles [] for _ in range(80): angle base_angle random.uniform(-spread / 2, spread / 2) speed random.uniform(2.5, 6.0) particles.append({...})配色方案可以预设几个主题类似“落日红橙”“深海蓝紫”“原野绿黄”每个主题是一个 (h_base, h_range) 元组色相在范围内取随机值themes { sunset: (10, 40), # 红橙色系 ocean: (200, 260), # 蓝紫色系 forest: (80, 160), # 绿黄色系 }点击时随机选一个主题传给explode()里的 h 计算位置这样每朵花都有自己的主色调不会全场都是同一个色。时长控制则是用一个全局计数器frame_count满多少帧自动停止发射新的火箭已爆炸的粒子仍然继续画完这样末尾烟花自然散去而不是瞬间消失def main_loop(): global frame_count frame_count 1 if frame_count total_frames and random.random() 0.2: launch(random.uniform(-300, 300), random.uniform(-100, 100)) # 已有的更新和绘制逻辑随机发射间隔 0.2 的概率每帧判定一次平均每 5 帧发射一发约 0.15 秒一次不会显得太密集也不会太冷清。把total_frames设成 1500约 45 秒播放完自动只剩零星粒子的余韵。从那以后我每次做这类动画 demo都会把发射频率、颜色主题、爆炸方向三件事全部收敛成顶部参数跑一遍再把参数给同伴让他们自己调颜色——这比教他们改代码省心太多。希望帮到你。本文还有配套的精品资源点击获取