每天敲代碼的朋友,有沒有想過代碼也能夠變得很酷炫又浪漫?今天就教你們用Python模擬出綻開的煙花慶祝昨晚法國隊奪冠,工做之餘也能夠隨時讓程序爲本身放一場煙花秀。python
這個有趣的小項目並不複雜,只需一點可視化技巧,100餘行Python代碼和程序庫Tkinter,最後咱們就能達到下面這個效果:canvas
學完本教程後,你也能作出這樣的煙花秀。微信
咱們的整個理念比較簡單。app
如上圖示,咱們這裏經過讓畫面上一個粒子分裂爲X數量的粒子來模擬爆炸效果。粒子會發生「膨脹」,意思是它們會以恆速移動且相互之間的角度相等。這樣就能讓咱們以一個向外膨脹的圓圈形式模擬出煙花綻開的畫面。通過必定時間後,粒子會進入「自由落體」階段,也就是因爲重力因素它們開始墜落到地面,仿若綻開後熄滅的煙花。dom
這裏再也不一股腦把數學知識全丟出來,咱們邊寫代碼邊說理論。首先,確保你安裝和導入了Tkinter,它是Python的標準 GUI 庫,普遍應用於各類各樣的項目和程序開發,在Python中使用 Tkinter 能夠快速的建立 GUI 應用程序。ide
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應用的基本設置以下:oop
root = tk.Tk() 複製代碼
爲了能初始化Tkinter,咱們必須建立一個Tk()根部件(root widget),它是一個窗口,帶有標題欄和由窗口管理器提供的其它裝飾物。該根部件必須在咱們建立其它小部件以前就建立完畢,並且只能有一個根部件。學習
w = tk.Label(root, text="Hello Tkinter!") 複製代碼
這一行代碼包含了Label部件。該Label調用中的第一個參數就是父窗口的名字,即咱們這裏用的「根」。關鍵字參數「text」指明顯示的文字內容。你也能夠調用其它小部件:Button,Canvas等等。spa
w.pack() root.mainloop() 複製代碼
接下來的這兩行代碼很重要。這裏的打包方法是告訴Tkinter調整窗口大小以適應所用的小部件。窗口直到咱們進入Tkinter事件循環,被root.mainloop()調用時纔會出現。在咱們關閉窗口前,腳本會一直在停留在事件循環。
如今咱們設計一個對象,表示煙花事件中的每一個粒子。每一個粒子都會有一些重要的屬性,支配了它的外觀和移動情況:大小,顏色,位置,速度等等。
''' 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 - intial_speed: speed of particle at explosion ''' 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 複製代碼
若是咱們回過頭想一想最開始的想法,就會意識到必須確保每一個煙花綻開的全部粒子必須通過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 job self.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 複製代碼
如今咱們將粒子的移動概念化,不過很明顯,一個煙花不能只有一個粒子,一場煙花秀也不能只有一個煙花。咱們下一步就是讓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代碼放了一場煙花秀:
本文只是基本版本,等你進一步熟悉Tkinter後,還能夠添加更多顏色更漂亮的背景照片,讓代碼爲你綻開更美的煙花!
我有一個微信公衆號,常常會分享一些python技術相關的乾貨;若是你喜歡個人分享,能夠用微信搜索「python語言學習」關注
歡迎你們加入千人交流答疑裙:699+749+852