天天看點

人生苦短,用Python來場煙花吧!!!

俗語有言,代碼如詩;我也有言,代碼如畫!今天就和大家一起探讨下如何用python點煙花,讓我們在工作之餘可以随時用程式為自己放一場煙花秀。做一個這樣有趣的小項目并不複雜,隻需一點可視化技巧,100餘行Python代碼和程式庫Tkinter,最後我們就能達到下面這個效果:

人生苦短,用Python來場煙花吧!!!

整體概念梳理

我們的整個理念比較簡單。

人生苦短,用Python來場煙花吧!!!
如上圖示,我們這裡通過讓畫面上一個粒子分裂為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.total = total

self.age = 0self.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
           

如果我們回過頭想想最開始的想法,就會意識到必須確定每個煙花綻放的所有粒子必須經過3個不同的階段,即“膨脹”“墜落”和“消失”。 是以我們向粒子類中再添加一些運動函數,如下所示:

def update(self, dt):
# 粒子膨脹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.vx = move_x/(float(dt)*1000)

self.vy = move_y/(float(dt)*1000)

self.cv.move(self.cid, move_x, move_y)

# 以自由落體墜落
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 jobself.cv.move(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:
for part in point:

part.update(dt)

cv.update()

total_time += dt
           

現在,我們隻需将最後兩個gist合并為一個能被Tkinter調用的函數,就叫它simulate()吧。該函數會展示所有的資料項,并根據我們設定的時間更新每個資料項的屬性。在我們的主代碼中,我們會用一個alarm處理子產品after()調用此函數,after()會等待一定的時間,然後再調用函數。

我們這裡設定讓Tkinter等待100個機關(1秒鐘)再調取simulate。

if __name__ == '__main__':

root = tk.Tk()

cv = tk.Canvas(root, height=600, width=600)
# 繪制一個黑色背景

cv.create_rectangle(0, 0, 600, 600, fill="black")

cv.pack()


root.protocol("WM_DELETE_WINDOW", close)
# 在1秒後才開始調用stimulate()

root.after(100, simulate, cv)

root.mainloop()
           

好了,這樣我們就用Python代碼放了一場煙花秀:

人生苦短,用Python來場煙花吧!!!

本文隻一個簡單版本,等進一步熟悉Tkinter後,還可以添加更多顔色更漂亮的背景照片,讓代碼為你綻放更美的煙花!

以下是全部代碼:

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

# 模拟重力

GRAVITY = 0.05
# 顔色選項(随機或者按順序)

colors = ['red', 'blue', 'yellow', 'white', 'green', 'orange', 'purple', 'seagreen', 'indigo', 'cornflowerblue']

'''

particles 類


粒子在空中随機生成随機,變成一個圈、下墜、消失


屬性:

- id: 粒子的id

- x, y: 粒子的坐标

- vx, vy: 在坐标的變化速度

- total: 總數

- age: 粒子存在的時長

- color: 顔色

- cv: 畫布

- lifespan: 最高存在時長


'''


class Particle: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 = 0self.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

# 粒子範圍擴大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)

# 以自由落體墜落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 jobself.cv.move(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


'''

循環調用保持不停

'''


def simulate(cv):

t = time()

explode_points = []

wait_time = randint(10, 100)

numb_explode = randint(6, 10)
# 建立一個所有粒子同時擴大的二維清單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 = Particle(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# 1.8s内一直擴大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
# 循環調用

root.after(wait_time, simulate, cv)


def close(*ignore):"""退出程式、關閉視窗"""global root

root.quit()


if __name__ == '__main__':

root = tk.Tk()

cv = tk.Canvas(root, height=400, width=600)
# 選一個好看的背景會讓效果更驚豔!

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()           

原文釋出時間為: 2018-11-28 本文作者:程式員共成長 本文來自雲栖社群合作夥伴“

程式員共成長

”,了解相關資訊可以關注“

程式員共成長

”。