如上图⽰,我们这⾥通过让画⾯上⼀个粒⼦分裂为X数量的粒⼦来模拟爆炸效果。粒⼦会发⽣“膨胀”,意思是它们会以恒速移动且相互之间的⾓度相等。这样就能让我们以⼀个向外膨胀的圆圈形式模拟出烟花绽放的画⾯。经过⼀定时间后,粒⼦会进⼊“⾃由落体”阶段,也就是由于重⼒因素它们开始坠落到地⾯,仿若绽放后熄灭的烟花。
基本知识:⽤Python和Tkinter设计烟花
这⾥不再⼀股脑把数学知识全丢出来,我们边写代码边说理论。⾸先,确保你安装和导⼊了 Tkinter ,它是Python的标准 GUI 库,⼴泛应⽤于各种各样的项⽬和程序开发,在Python中使⽤ Tkinter 可以快速的创建 GUI 应⽤程序。
import tkinter as tk
from PIL import Image, ImageTk
from time import time, sleep
from random import choice, uniform, randint
from math import sin, cos, radians
除了Tkinter之外,为了能让界⾯有漂亮的背景,我们也导⼊PIL⽤于图像处理,以及导⼊其它⼀些包,⽐如time,random和math。它们能让我们更容易的控制烟花粒⼦的运动轨迹。
Tkinter应⽤的基本设置如下:
root = tk.Tk()
为了能初始化Tkinter,我们必须创建⼀个Tk()根部件(root widget),它是⼀个窗⼝,带有标题栏和由窗⼝管理器提供的其它装饰物。该根部件必须在我们创建其它⼩部件之前就创建完毕,⽽且只能有⼀个根部件。
w = tk.Label(root, text="Hello Tkinter!")
这⼀⾏代码包含了Label部件。该Label调⽤中的第⼀个参数就是⽗窗⼝的名字,即我们这⾥⽤的“根”。关键字参数“text”指明显⽰的⽂字内容。你也可以调⽤其它⼩部件:Button,Canvas等等。
w.pack()
root.mainloop()
接下来的这两⾏代码很重要。这⾥的打包⽅法是告诉Tkinter调整窗⼝⼤⼩以适应所⽤的⼩部件。窗⼝直到我们进⼊Tkinter事件循环,被root.mainloop()调⽤时才会出现。在我们关闭窗⼝前,脚本会⼀直在停留在事件循环。
将烟花绽放转译成代码
现在我们设计⼀个对象,表⽰烟花事件中的每个粒⼦。每个粒⼦都会有⼀些重要的属性,⽀配了它的外观和移动状况:⼤⼩,颜⾊,位置,速度等等。
'''
particles 类
粒⼦在空中随机⽣成随机,变成⼀个圈、下坠、消失
属性:
- id: 粒⼦的id
- x, y: 粒⼦的坐标
-
vx, vy: 在坐标的变化速度
- total: 总数
- age: 粒⼦存在的时长
- color: 颜⾊
- cv: 画布
- lifespan: 最⾼存在时长
'''
class part:
def __init__(self, cv, idx, total, explosion_speed, x=0., y=0., vx = 0., vy = 0., size=2., color = 'red', lifespan = 2, **kwargs):
self.id = idx
self.x = x
self.y = y
self.initial_speed = explosion_speed
self.vx = vx
self.vy = vy
self.age = lor = color
self.cv = cv
self.cid = ate_oval(
x - size, y - size, x + size,
y + size, lor)
self.lifespan = lifespan
如果我们回过头想想最开始的想法,就会意识到必须确保每个烟花绽放的所有粒⼦必须经过3个不同的阶段,即“膨胀”“坠落”和“消失”。所以我们向粒⼦类中再添加⼀些运动函数,如下所⽰:
def update(self, dt):
# 粒⼦膨胀if self.alive() pand():
move_x = cos(radians(self.id*al))*self.initial_speed
move_y = sin(radians(self.id*al))*self.initial_speed
self.vx = move_x/(float(dt)*1000)
self.vy = move_y/(float(dt)*1000)
ve(self.cid, move_x, move_y)
# 以⾃由落体坠落
elif self.alive():
move_x = cos(radians(self.id*al))
# we technically don't need to update x, y because move will do the job
ve(self.cid, self.vx + move_x, self.vy+GRAVITY*dt)
self.vy += GRAVITY*dt
# 如果粒⼦的⽣命周期已过,就将其移除
elif self.cid is not None:
cv.delete(self.cid)
self.cid = None
当然,这也意味着我们必须定义每个粒⼦绽放多久、坠落多久。这部分需要我们多尝试⼀些参数,才能达到最佳视觉效果。
# 定义膨胀效果的时间帧
def expand (self):
return self.age <= 1.2
# 检查粒⼦是否仍在⽣命周期内
def alive(self):
return self.age <= self.lifespan
使⽤Tkinter模拟
现在我们将粒⼦的移动概念化,不过很明显,⼀个烟花不能只有⼀个粒⼦,⼀场烟花秀也不能只有⼀个烟花。我们下⼀步就是让Python和Tkinter以我们可控的⽅式向天上连续“发射”粒⼦。
到了这⾥,我们需要从操作⼀个粒⼦升级为在屏幕上展现多个烟花及每个烟花中的多个粒⼦。
我们的解决思路如下:创建⼀列列表,每个⼦列表是⼀个烟花,其包含⼀列粒⼦。每个列表中的例⼦有相同的x,y坐标、⼤⼩、颜⾊、初始速度。
numb_explode = randint(6,10)
# 为所有模拟烟花绽放的全部粒⼦创建⼀列列表
for point in range(numb_explode):
objects = []
x_cordi = randint(50,550)
y_cordi = randint(50, 150)
size = uniform (0.5,3)
color = choice(colors)
explosion_speed = uniform(0.2, 1)
total_particles = randint(10,50)
for i in range(1,total_particles):
r = part(cv, idx = i, total = total_particles, explosion_speed = explosion_speed, x = x_cordi, y = y_cordi,
color=color, size = size, lifespan = uniform(0.6,1.75))
objects.append(r)
explode_points.append(objects)
我们下⼀步就是确保定期更新粒⼦的属性。这⾥我们设置让粒⼦每0.01秒更新它们的状态,在1.8秒之后停⽌更新(这意味着每个粒⼦的存在时间为1.6秒,其中1.2秒为“绽放”状态,0.4秒为“坠落”状态,0.2秒处于Tkinter将其完全移除前的边缘状态)。
total_time = .0
# 在1.8秒时间帧内保持更新
while total_time < 1.8:
sleep(0.01)
tnew = time()
t, dt = tnew, tnew - t
for point in explode_points:
random在python中的意思for part in point:
part.update(dt)
cv.update()
total_time += dt
现在,我们只需将最后两个gist合并为⼀个能被Tkinter调⽤的函数,就叫它simulate()吧。该函数会展⽰所有的数据项,并根据我们设置的时间更新每个数据项的属性。在我们的主代码中,我们会⽤⼀个alarm处理模块after()调⽤此函数,after()会等待⼀定的时间,然后再调⽤函数。
我们这⾥设置让Tkinter等待100个单位(1秒钟)再调取simulate。