python中用turtle来绘画出烟花效果
– Fireworks = [] :定义了一个空列表,用于存储所有的烟花对象,后续创建的烟花实例都会添加到这个列表中,方便统一管理和操作这些烟花的状态、展示等。
– maxFireworks = 8 :设定了一个最大值,表示同时存在的烟花数量上限,限制了最多能有8个烟花处于活动状态或者被创建出来展示等情况。
– height, width = 600, 900 :定义了整个展示区域(比如图形界面窗口等)的高度和宽度,单位应该是像素,用于确定烟花产生以及其粒子散布的范围边界。
1 、烟花对象属性初始化部分– self.radius :通过 random.randint 函数随机确定每个烟花粒子的半径,范围在2到5像素之间,决定了粒子在图形展示中的大小。
– self.color :直接使用传入的参数来设置烟花粒子的颜色属性,用于后续绘制粒子时确定颜色。
– self.speed :接收传入的速度参数,这个速度参数关联着烟花粒子状态变化的节奏,是一个在0.3到1.0秒之间影响时间间隔的关键因素,用于控制粒子扩散等状态更新的快慢。
– self.status :初始化为0,表示烟花当前处于未爆炸状态,后续随着烟花爆炸和粒子扩散,这个值会不断增加,当超过100时意味着烟花的整个生命周期结束,不再进行更新等操作了。
– self.nParticle :随机生成烟花所包含的粒子数量,范围在20到30个之间,确定了烟花爆炸后散开的粒子规模。
– self.center :通过随机选择坐标的方式确定烟花在整个展示区域内的中心位置,坐标范围基于传入的 width 和 height 参数限定,确保在展示区域内随机产生。
– self.oneParticle :初始化一个空列表,用于后续存储烟花在原始(也就是状态为100%,未开始扩散前)情况下各个粒子的坐标,是后续计算和更新粒子坐标的重要基础数据。
– self.rotTheta :随机生成一个在0到 2 * math.pi 之间的角度值,用于表示椭圆平面旋转角,在计算粒子基于椭圆参数方程且考虑旋转后的实际坐标时会用到这个角度。
2、计算原始粒子坐标部分
– 首先随机确定椭圆的长半轴 a 和短半轴 b 参数(存储在 self.ellipsePara 列表中,范围分别在30到40以及20到30之间),用于椭圆参数方程来计算粒子坐标。
– 然后根据粒子数量 self.nParticle 计算出角度间隔 theta ,通过循环针对每个粒子进行坐标计算:
– 先在椭圆参数方程基础上添加一个很小的随机角度偏移 t (范围在 -1/16 到 1/16 之间)来计算出未旋转前基于椭圆的 x 和 y 坐标。
– 接着利用平面旋转方程,结合之前随机生成的旋转角 self.rotTheta ,将 x 和 y 坐标进行旋转,得到实际的粒子坐标 xx 和 yy ,并添加到 self.oneParticle 列表中,这样就完成了所有原始粒子坐标的计算。
3、当前粒子坐标及线程相关部分
– self.curParticle :初始化为 self.oneParticle 的副本(通过切片 [0:] 实现复制),用于记录当前时刻烟花粒子的实际坐标,随着烟花状态变化(爆炸后粒子扩散等情况),这个坐标列表会不断更新。
– self.thread :创建一个线程对象,指定线程执行的目标函数为 self.extend ,也就是后续通过这个线程独立去运行 extend 函数来更新烟花粒子的状态,实现和其他部分代码并行执行的效果,比如可以在图形界面更新展示的同时,这个线程在后台不断更新粒子状态。
4. extend 方法部分
– 这是作为线程执行的函数,用于更新烟花粒子的状态。
– 通过一个循环100次来逐步更新烟花状态(每次循环增加 self.status 的值,模拟烟花从爆炸开始到逐渐扩散直至结束的过程),每次循环中:
– 先更新 self.status ,表示烟花状态的推进,从初始的未爆炸状态0开始,每次加1,当超过100时整个烟花生命周期结束。
– 接着更新 self.curParticle 坐标列表,通过将原始粒子坐标( self.oneParticle 中的每个坐标对)按照当前状态占比( self.status / 100 )进行缩放,实现粒子从原始位置逐渐扩散开来的效果,模拟烟花爆炸后粒子散开的动态过程。
– 最后通过 time.sleep 函数按照 self.speed 参数控制的节奏进行休眠,使得粒子状态更新按照一定的时间间隔进行,从而在视觉上呈现出合理的动态效果,比如 self.speed 越大,粒子状态变化越快,烟花散开的速度就越快。
5.explode 方法部分和__repr__ 方法部分
– .explode 方法用于触发烟花的爆炸效果,也就是启动管理粒子状态变化的线程。
– 首先将线程设置为守护线程,意味着当主线程(比如图形界面的主循环等)结束时,这个守护线程也会自动结束,不会阻止程序的正常退出。然后调用 self.thread.start() 启动线程,让之前定义的 extend 函数开始在线程中执行,从而开始更新烟花粒子的状态,模拟烟花爆炸散开的过程。
– __repr__ 方法用于定义对象的字符串表示形式,方便在调试等场景中直观地查看对象的关键属性信息。
– 目前这个方法返回的字符串只是简单地包含了烟花对象的 color 和 speed 属性的表示形式,不过代码似乎没有写完,正常应该把其他重要属性也合理地添加进来展示,更完整地呈现烟花对象的状态信息,便于查看和调试。
6、colorChange 函数解析
– 功能概述:这个函数主要用于根据烟花粒子的当前状态来改变其颜色,实现颜色随粒子“寿命”变化的效果,模拟烟花绽放过程中颜色的动态变化。
– 具体步骤:
– 提取RGB颜色值:首先使用正则表达式 re.findall(r'(.{2})', fire.color[1:]) 从传入的 fire 对象的 color 属性中提取十六进制颜色表示的RGB分量(去掉开头的 # 符号后,每两位一组表示一个分量),将其存储在 rgb 列表中,例如 ["FF", "00", "00"] 这样表示红色的RGB值情况。
– 定义颜色变换函数:通过 lambda 表达式定义了一个匿名函数 f ,其作用是根据传入的十六进制颜色分量字符串 x 和表示粒子当前状态的参数 c ,按照特定规则(这里是当粒子寿命到70%后,颜色开始线性衰减,通过 int(int(x, 16) * (100 – c) / 30) 计算新的十六进制颜色分量值,也就是将原来的分量值按照与剩余寿命相关的比例进行换算,再转换回十六进制并取后两位作为新的值)来更新颜色分量。
– 根据状态确定颜色分量:根据烟花粒子的当前状态 cs 判断是否大于70,如果大于70,就调用 f 函数分别对提取到的RGB三个分量进行更新,得到新的颜色分量 ccr 、 ccg 、 ccb ;如果状态小于等于70,就直接使用原始的RGB分量作为新的分量,保持颜色不变。
– 构建并返回新颜色字符串:最后使用字符串格式化 return '#{0:0>2}{1:0>2}{2:0>2}'.format(ccr, ccg, ccb) 将更新后的颜色分量重新组合成十六进制颜色字符串(确保每个分量都是两位,不足两位在前面补0)并返回,用于后续绘制粒子时设置其颜色。
7、appendFirework 函数解析
– 功能概述:用于创建并添加烟花对象到 Fireworks 列表中,支持通过递归的方式按照指定数量生成烟花,同时控制烟花数量不超过设定的最大值 maxFireworks 。
– 具体步骤:
– 数量判断与限制:首先判断传入的参数 n 和当前 Fireworks 列表中的烟花数量,如果 n 大于 maxFireworks 或者 Fireworks 列表中的烟花数量已经超过 maxFireworks ,则直接使用 pass 语句跳过,不进行任何操作,以此来限制烟花的总数量。
– 创建单个烟花对象( n == 1 时):当 n 等于1时,执行以下操作:
– 生成随机颜色:通过 random.randint 生成一个0到16777215(对应十六进制的 0xFFFFFF ,也就是所有颜色的取值范围)之间的随机整数,然后将其转换为十六进制字符串,并格式化为6位(不足6位在前面补0)的形式,作为烟花的颜色赋值给 cl ,以此生成一个随机颜色。
– 创建烟花实例并添加到列表:使用生成的随机颜色 cl ,再结合随机生成的速度 random.uniform(0.3, 1.0) 以及全局的 width 和 height 参数创建一个 firework 类的实例 a ,接着将一个包含 'particle': a (表示烟花对象本身)和 'points': [] (用于后续存储每个粒子绘制时相关对象变量的空列表)的字典添加到 Fireworks 列表中,这样就完成了一个烟花对象的创建与添加操作。
– 触发烟花爆炸:调用 a.explode() 方法,启动之前在 firework 类中定义的线程,让烟花开始进入爆炸散开的状态变化过程。
– 递归生成多个烟花对象( n > 1 时):当 n 大于1时,通过两次递归调用 appendFirework 函数来逐步减少要生成的烟花数量,第一次调用 appendFirework() 相当于先创建一个烟花对象(因为不传参数时默认 n = 1 ),第二次调用 appendFirework(n – 1) 则继续以减少后的数量递归创建剩下的烟花对象,直到最终达到要生成的 n 个烟花对象为止。
8.show 函数解析
– 功能概述:这个函数主要负责在图形界面(通过传入的 c 参数代表的 Canvas 对象,也就是画布)上更新显示烟花的状态,包括删除旧的粒子显示、根据当前烟花状态绘制新的粒子以及处理烟花生命周期结束和新增烟花等情况,并且通过回调机制实现定时刷新画面,展示动态的烟花效果。
– 具体步骤:
– 清除旧粒子显示:通过两层嵌套循环遍历 Fireworks 列表中的每个烟花对象( p )及其对应的粒子显示对象列表( p['points'] ),然后调用 c.delete(pp) 方法将每个粒子对应的图形对象从画布上删除,这样做是为了在每次刷新显示时清除上一时刻的粒子画面,准备绘制新的粒子状态。
– 更新烟花粒子显示:再次遍历 Fireworks 列表中的每个烟花对象,对于每个烟花对象(通过 p['particle'] 获取对应的 firework 类实例 oneP )进行以下操作:
– 处理烟花生命周期结束情况:如果烟花的 status 属性等于100,表示烟花的生命周期结束了,这时先从 Fireworks 列表中移除这个烟花对象( Fireworks.remove(p) ),然后调用 appendFirework() 函数新增一个烟花,以保持画面上有持续的烟花展示效果,之后使用 continue 跳过当前循环,不再进行该烟花的粒子绘制操作。
– 计算粒子坐标并绘制粒子(烟花未结束时):如果烟花还未结束生命周期,先通过列表推导式 li = [[int(cp[0] * 2) + oneP.center[0], int(cp[1] * 2) + oneP.center[1]] for cp in oneP.curParticle] 将以烟花中心为原点的椭圆上的粒子坐标(存储在 oneP.curParticle 中)进行平移,转换到基于烟花随机生成的中心坐标( oneP.center )的实际显示坐标,得到一个新的坐标列表 li 。接着调用 colorChange(oneP) 函数根据烟花当前状态获取粒子的当前颜色 color ,最后通过内层循环遍历 li 中的每个坐标对 pp ,使用 c.create_oval 方法在画布上绘制一个以该坐标为中心、半径为 oneP.radius 的圆形(代表一个粒子),并将绘制后返回的图形对象添加到当前烟花对象的 p['points'] 列表中,用于下次刷新显示时能够删除这些粒子对象。
– 定时刷新回调:通过 root.after(40, show, c) 语句设置一个定时器回调,让 show 函数在40毫秒后再次被调用,传入相同的 c 参数(即画布对象),实现每隔40毫秒就刷新一次画面,从而展示出烟花粒子动态变化的连续效果。
9、 __main__ 部分解析
– 功能概述:这是程序的入口部分,主要完成初始化烟花对象、创建图形界面(包含画布设置和背景颜色设置等)以及启动定时刷新显示烟花效果的流程,使得整个程序能够正常运行并展示动态烟花画面。
– 具体步骤:
– 生成初始烟花对象:首先调用 appendFirework(maxFireworks) 函数,按照设定的最大烟花数量( maxFireworks )创建并启动相应数量的烟花,让它们进入爆炸散开的状态变化过程,为后续在图形界面上展示做好准备。
– 创建图形界面主窗口和画布:使用 tk.Tk() 创建一个 tkinter 库的主窗口对象 root ,然后通过 tk.Canvas(root, height=height, width=width) 创建一个指定高度( height )和宽度( width )的画布对象 cv ,并将其添加到主窗口中,接着使用 cv.create_rectangle(0, 0, width, height, fill="black") 在画布上绘制一个覆盖整个画布区域的黑色矩形,用于作为背景,模拟夜晚天空的效果。
– 启动定时刷新显示:通过 root.after(500, show, cv) 语句设置一个定时器,让 show 函数在500毫秒后开始第一次被调用,传入 cv 作为画布参数,之后 show 函数内部会通过不断的回调定时刷新画面,实现烟花效果的展示。最后调用 root.mainloop() 启动 tkinter 主窗口的事件循环,使得窗口能够持续显示并响应各种事件(如窗口关闭等),让整个程序保持运行状态,持续展示动态烟花画面,直到用户关闭窗口。
作者:七七凉