天天敲代码的朋友,有没有想过代码也可以变得很酷炫又浪漫?今天就教大家用Python模拟出绽放的烟花庆祝昨晚法国队夺冠,工作之余也可以随时让程序为自己放一场烟花秀。

这个有趣的小项目并不复杂,只需一点可视化技巧,100余行Python代码和程序库Tkinter,最后我们就能达到下面这个效果:

学完本教程后,你也能做出这样的烟花秀。

整体梳理概念

我们的整个理念比较简单。

如上图示,我们这里通过让画面上一个粒子分裂为X数量的粒子来模拟爆炸效果。粒子会发生“膨胀”,意思是它们会以恒速移动且相互之间的角度相等。这样就能让我们以一个向外膨胀的圆圈形式模拟出烟花绽放的画面。经过一定时间后,粒子会进入“自由落体”阶段,也就是由于重力因素它们开始坠落到地面,仿若绽放后熄灭的烟花。

用Python和Tkinter设计烟花:基本知识

这里不再一股脑把数学知识全丢出来,我们边写代码边说理论。首先,确保你安装和导入了

1.  
2.  
import tkinter as tk
3.  
from PIL import Image, ImageTk
4.  
from time import time, sleep
5.  
from random import choice, uniform, randint
6.  
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等等。

  1.  
    w.pack()
  2.  
    root.mainloop()

接下来的这两行代码很重要。这里的打包方法是告诉Tkinter调整窗口大小以适应所用的小部件。窗口直到我们进入Tkinter事件循环,被root.mainloop()调用时才会出现。在我们关闭窗口前,脚本会一直在停留在事件循环。

将烟花绽放转译成代码

现在我们设计一个对象,表示烟花事件中的每个粒子。每个粒子都会有一些重要的属性,支配了它的外观和移动状况:大小,颜色,位置,速度等等。

1.  
2.  
Generic class for particles
3.   
4.  
particles are emitted almost randomly on the sky, forming a round of circle (a star) before falling and getting removed
5.  
from canvas
6.   
7.  
Attributes:
8.  
 - id: identifier of a particular particle in a star
9.  
 - x, y: x,y-coordinate of a star (point of explosion)
10.  
 - vx, vy: speed of particle in x, y coordinate
11.  
 - total: total number of particle in a star
12.  
 - age: how long has the particle last on canvas
13.  
 - color: self-explantory
14.  
 - cv: canvas
15.  
 - lifespan: how long a particle will last on canvas
16.  
 - intial_speed: speed of particle at explosion
17.   
18.  
'''
19.  
class part:
20.  
def __init__(self, cv, idx, total, explosion_speed, x=0., y=0., vx = 0., vy = 0., size=2., color = 'red', lifespan = 2, **kwargs):
21.  
self.id = idx
22.  
self.x = x
23.  
self.y = y
24.  
self.initial_speed = explosion_speed
25.  
self.vx = vx
26.  
self.vy = vy
27.  
self.total = total
28.  
self.age = 0
29.  
self.color = color
30.  
self.cv = cv
31.  
self.cid = self.cv.create_oval(
32.  
x - size, y - size, x + size,
33.  
y + size, fill=self.color)
34.  
self.lifespan = lifespan

如果我们回过头想想最开始的想法,就会意识到必须确保每个烟花绽放的所有粒子必须经过3个不同的阶段,即“膨胀”“坠落”和“消失”。 所以我们向粒子类中再添加一些运动函数,如下所示:

1.  
2.  
# 粒子膨胀
3.  
if self.alive() and self.expand():
4.  
move_x = cos(radians(self.id*360/self.total))*self.initial_speed
5.  
move_y = sin(radians(self.id*360/self.total))*self.initial_speed
6.  
self.vx = move_x/(float(dt)*1000)
7.  
self.vy = move_y/(float(dt)*1000)
8.  
self.cv.move(self.cid, move_x, move_y)
9.   
10.  
# 以自由落体坠落
11.  
elif self.alive():
12.  
move_x = cos(radians(self.id*360/self.total))
13.  
# we technically don't need to update x, y because move will do the job
14.  
self.cv.move(self.cid, self.vx + move_x, self.vy+GRAVITY*dt)
15.  
self.vy += GRAVITY*dt
16.   
17.  
# 如果粒子的生命周期已过,就将其移除
18.  
elif self.cid is not None:
19.  
cv.delete(self.cid)
20.  
self.cid = None

当然,这也意味着我们必须定义每个粒子绽放多久、坠落多久。这部分需要我们多尝试一些参数,才能达到最佳视觉效果。

1.  
2.  
def expand (self):
3.  
return self.age <= 1.2
4.   
5.  
# 检查粒子是否仍在生命周期内
6.  
def alive(self):
7.  
return self.age <= self.lifespan

使用Tkinter模拟

现在我们将粒子的移动概念化,不过很明显,一个烟花不能只有一个粒子,一场烟花秀也不能只有一个烟花。我们下一步就是让Python和Tkinter以我们可控的方式向天上连续“发射”粒子。

到了这里,我们需要从操作一个粒子升级为在屏幕上展现多个烟花及每个烟花中的多个粒子。

我们的解决思路如下:创建一列列表,每个子列表是一个烟花,其包含一列粒子。每个列表中的例子有相同的x,y坐标、大小、颜色、初始速度。

1.  
2.  
# 为所有模拟烟花绽放的全部粒子创建一列列表
3.  
for point in range(numb_explode):
4.  
objects = []
5.  
x_cordi = randint(50,550)
6.  
y_cordi = randint(50, 150) 
7.  
size = uniform (0.5,3)
8.  
color = choice(colors)
9.  
explosion_speed = uniform(0.2, 1)
10.  
total_particles = randint(10,50)
11.  
for i in range(1,total_particles):
12.  
r = part(cv, idx = i, total = total_particles, explosion_speed = explosion_speed, x = x_cordi, y = y_cordi,
13.  
color=color, size = size, lifespan = uniform(0.6,1.75))
14.  
objects.append(r)
15.  
explode_points.append(objects)

我们下一步就是确保定期更新粒子的属性。这里我们设置让粒子每0.01秒更新它们的状态,在1.8秒之后停止更新(这意味着每个粒子的存在时间为1.6秒,其中1.2秒为“绽放”状态,0.4秒为“坠落”状态,0.2秒处于Tkinter将其完全移除前的边缘状态)。

1.  
2.  
# 在1.8秒时间帧内保持更新
3.  
while total_time < 1.8:
4.  
sleep(0.01)
5.  
tnew = time()
6.  
t, dt = tnew, tnew - t
7.  
for point in explode_points:
8.  
for part in point:
9.  
part.update(dt)
10.  
cv.update()
11.  
total_time += dt

 

我们这里设置让Tkinter等待100个单位(1秒钟)再调取simulate。

1.  
2.  
root = tk.Tk()
3.  
cv = tk.Canvas(root, height=600, width=600)
4.  
# 绘制一个黑色背景
5.  
cv.create_rectangle(0, 0, 600, 600, fill="black")
6.  
cv.pack()
7.   
8.  
root.protocol("WM_DELETE_WINDOW", close)
9.  
# 在1秒后才开始调用stimulate()
10.  
root.after(100, simulate, cv)
11.  
root.mainloop()

好了,这样我们就用Python代码放了一场烟花秀:
源码:::

python 生日快乐 python生日快乐祝烟花_Image

python 生日快乐 python生日快乐祝烟花_python 生日快乐_02

'''
FIREWORKS SIMULATION WITH TKINTER
'''
import tkinter as tk
#from tkinter import messagebox
#from tkinter import PhotoImage
from PIL import Image, ImageTk
from time import time, sleep
from random import choice, uniform, randint
from math import sin, cos, radians

# gravity, act as our constant g, you can experiment by changing it
GRAVITY = 0.05
# list of color, can choose randomly or use as a queue (FIFO)
colors = ['red', 'blue', 'yellow', 'white', 'green', 'orange', 'purple', 'seagreen','indigo', 'cornflowerblue']

'''
Generic class for particles
particles are emitted almost randomly on the sky, forming a round of circle (a star) before falling and getting removed
from canvas
Attributes:
    - id: identifier of a particular particle in a star
    - x, y: x,y-coordinate of a star (point of explosion)
    - vx, vy: speed of particle in x, y coordinate
    - total: total number of particle in a star
    - age: how long has the particle last on canvas
    - color: self-explantory
    - cv: canvas
    - lifespan: how long a particle will last on canvas
'''
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.total = total
        self.age = 0
        self.color = color
        self.cv = cv
        self.cid = self.cv.create_oval(
            x - size, y - size, x + size,
            y + size, fill=self.color)
        self.lifespan = lifespan

    def update(self, dt):
        self.age += dt

        # particle expansions
        if self.alive() and self.expand():
            move_x = cos(radians(self.id*360/self.total))*self.initial_speed
            move_y = sin(radians(self.id*360/self.total))*self.initial_speed
            self.cv.move(self.cid, move_x, move_y)
            self.vx = move_x/(float(dt)*1000)

        # falling down in projectile motion
        elif self.alive():
            move_x = cos(radians(self.id*360/self.total))
            # we technically don't need to update x, y because move will do the job
            self.cv.move(self.cid, self.vx + move_x, self.vy+GRAVITY*dt)
            self.vy += GRAVITY*dt

        # remove article if it is over the lifespan
        elif self.cid is not None:
            cv.delete(self.cid)
            self.cid = None

    # define time frame for expansion
    def expand (self):
        return self.age <= 1.2

    # check if particle is still alive in lifespan
    def alive(self):
        return self.age <= self.lifespan

'''
Firework simulation loop:
Recursively call to repeatedly emit new fireworks on canvas
a list of list (list of stars, each of which is a list of particles)
is created and drawn on canvas at every call, 
via update protocol inside each 'part' object 
'''
def simulate(cv):
    t = time()
    explode_points = []
    wait_time = randint(10,100)
    numb_explode = randint(6,10)
    # create list of list of all particles in all simultaneous explosion
    for point in range(numb_explode):
        objects = []
        x_cordi = randint(50,550)
        y_cordi = randint(50, 150)
        speed = uniform (0.5, 1.5)          
        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, 
                vx = speed, vy = speed, color=color, size = size, lifespan = uniform(0.6,1.75))
            objects.append(r)
        explode_points.append(objects)

    total_time = .0
    # keeps undate within a timeframe of 1.8 second
    while total_time < 1.8:
        sleep(0.01)
        tnew = time()
        t, dt = tnew, tnew - t
        for point in explode_points:
            for item in point:
                item.update(dt)
        cv.update()
        total_time += dt
    # recursive call to continue adding new explosion on canvas
    root.after(wait_time, simulate, cv)

def close(*ignore):
    """Stops simulation loop and closes the window."""
    global root
    root.quit()
    
if __name__ == '__main__':
    root = tk.Tk()
    cv = tk.Canvas(root, height=600, width=600)
    # use a nice background image
    image = Image.open("image.jpg")
    photo = ImageTk.PhotoImage(image)
    cv.create_image(0, 0, image=photo, anchor='nw')

    cv.pack()
    root.protocol("WM_DELETE_WINDOW", close)

    root.after(100, simulate, cv)

    root.mainloop()

View Code