過年了靡努,用Python放一場浪漫煙花秀坪圾!

天天敲代碼的朋友,有沒有想過代碼也可以變得很酷炫又浪漫惑朦?今天就教大家用Python模擬出綻放的煙花兽泄,工作之余也可以隨時(shí)讓程序?yàn)樽约悍乓粓鰺熁ㄐ恪?/p>

這個(gè)有趣的小項(xiàng)目并不復(fù)雜,只需一點(diǎn)可視化技巧漾月,100余行Python代碼和程序庫Tkinter病梢,最后我們就能達(dá)到下面這個(gè)效果:

我精心準(zhǔn)備了大量學(xué)習(xí)資料,加入千人交流群:923414804獲取大量學(xué)習(xí)資料與入門教程梁肿。

學(xué)完本教程后蜓陌,你也能做出這樣的煙花秀。

整體概念梳理

我們的整個(gè)理念比較簡單吩蔑。

img

如上圖示钮热,我們這里通過讓畫面上一個(gè)粒子分裂為X數(shù)量的粒子來模擬爆炸效果。粒子會(huì)發(fā)生“膨脹”烛芬,意思是它們會(huì)以恒速移動(dòng)且相互之間的角度相等隧期。這樣就能讓我們以一個(gè)向外膨脹的圓圈形式模擬出煙花綻放的畫面。經(jīng)過一定時(shí)間后赘娄,粒子會(huì)進(jìn)入“自由落體”階段厌秒,也就是由于重力因素它們開始?jí)嬄涞降孛妫氯艟`放后熄滅的煙花擅憔。

基本知識(shí):用Python和Tkinter設(shè)計(jì)煙花

這里不再一股腦把數(shù)學(xué)知識(shí)全丟出來鸵闪,我們邊寫代碼邊說理論。首先暑诸,確保你安裝和導(dǎo)入了Tkinter蚌讼,它是Python的標(biāo)準(zhǔn) GUI 庫,廣泛應(yīng)用于各種各樣的項(xiàng)目和程序開發(fā)个榕,在Python中使用 Tkinter 可以快速的創(chuàng)建 GUI 應(yīng)用程序篡石。

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之外,為了能讓界面有漂亮的背景西采,我們也導(dǎo)入PIL用于圖像處理凰萨,以及導(dǎo)入其它一些包,比如time,random和math胖眷。它們能讓我們更容易的控制煙花粒子的運(yùn)動(dòng)軌跡武通。

Tkinter應(yīng)用的基本設(shè)置如下:

root = tk.Tk()

為了能初始化Tkinter,我們必須創(chuàng)建一個(gè)Tk()根部件(root widget)珊搀,它是一個(gè)窗口冶忱,帶有標(biāo)題欄和由窗口管理器提供的其它裝飾物。該根部件必須在我們創(chuàng)建其它小部件之前就創(chuàng)建完畢境析,而且只能有一個(gè)根部件囚枪。

w = tk.Label(root, text="Hello Tkinter!")

這一行代碼包含了Label部件。該Label調(diào)用中的第一個(gè)參數(shù)就是父窗口的名字劳淆,即我們這里用的“根”链沼。關(guān)鍵字參數(shù)“text”指明顯示的文字內(nèi)容。你也可以調(diào)用其它小部件:Button沛鸵,Canvas等等括勺。

w.pack()
root.mainloop()

接下來的這兩行代碼很重要。這里的打包方法是告訴Tkinter調(diào)整窗口大小以適應(yīng)所用的小部件谒臼。窗口直到我們進(jìn)入Tkinter事件循環(huán)朝刊,被root.mainloop()調(diào)用時(shí)才會(huì)出現(xiàn)耀里。在我們關(guān)閉窗口前蜈缤,腳本會(huì)一直在停留在事件循環(huán)。

將煙花綻放轉(zhuǎn)譯成代碼

現(xiàn)在我們?cè)O(shè)計(jì)一個(gè)對(duì)象冯挎,表示煙花事件中的每個(gè)粒子底哥。每個(gè)粒子都會(huì)有一些重要的屬性,支配了它的外觀和移動(dòng)狀況:大小房官,顏色趾徽,位置,速度等等翰守。

'''
particles 類

粒子在空中隨機(jī)生成隨機(jī)孵奶,變成一個(gè)圈、下墜蜡峰、消失

屬性:
    - id: 粒子的id
    - x, y: 粒子的坐標(biāo)
    - vx, vy: 在坐標(biāo)的變化速度
    - total: 總數(shù)
    - age: 粒子存在的時(shí)長
    - color: 顏色
    - cv: 畫布
    - lifespan: 最高存在時(shí)長

'''
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

如果我們回過頭想想最開始的想法了袁,就會(huì)意識(shí)到必須確保每個(gè)煙花綻放的所有粒子必須經(jīng)過3個(gè)不同的階段,即“膨脹”“墜落”和“消失”湿颅。 所以我們向粒子類中再添加一些運(yùn)動(dòng)函數(shù)载绿,如下所示:

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

當(dāng)然油航,這也意味著我們必須定義每個(gè)粒子綻放多久崭庸、墜落多久。這部分需要我們多嘗試一些參數(shù),才能達(dá)到最佳視覺效果怕享。

# 定義膨脹效果的時(shí)間幀
def expand (self):
    return self.age <= 1.2

# 檢查粒子是否仍在生命周期內(nèi)
def alive(self):
    return self.age <= self.lifespan

使用Tkinter模擬

現(xiàn)在我們將粒子的移動(dòng)概念化执赡,不過很明顯,一個(gè)煙花不能只有一個(gè)粒子熬粗,一場煙花秀也不能只有一個(gè)煙花搀玖。我們下一步就是讓Python和Tkinter以我們可控的方式向天上連續(xù)“發(fā)射”粒子。

到了這里驻呐,我們需要從操作一個(gè)粒子升級(jí)為在屏幕上展現(xiàn)多個(gè)煙花及每個(gè)煙花中的多個(gè)粒子灌诅。

我們的解決思路如下:創(chuàng)建一列列表,每個(gè)子列表是一個(gè)煙花含末,其包含一列粒子猜拾。每個(gè)列表中的例子有相同的x,y坐標(biāo)、大小佣盒、顏色挎袜、初始速度。

numb_explode = randint(6,10)
# 為所有模擬煙花綻放的全部粒子創(chuàng)建一列列表
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)

我們下一步就是確保定期更新粒子的屬性肥惭。這里我們?cè)O(shè)置讓粒子每0.01秒更新它們的狀態(tài)盯仪,在1.8秒之后停止更新(這意味著每個(gè)粒子的存在時(shí)間為1.6秒,其中1.2秒為“綻放”狀態(tài)蜜葱,0.4秒為“墜落”狀態(tài)全景,0.2秒處于Tkinter將其完全移除前的邊緣狀態(tài))。

total_time = .0
# 在1.8秒時(shí)間幀內(nèi)保持更新
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

現(xiàn)在牵囤,我們只需將最后兩個(gè)gist合并為一個(gè)能被Tkinter調(diào)用的函數(shù)爸黄,就叫它simulate()吧。該函數(shù)會(huì)展示所有的數(shù)據(jù)項(xiàng)揭鳞,并根據(jù)我們?cè)O(shè)置的時(shí)間更新每個(gè)數(shù)據(jù)項(xiàng)的屬性炕贵。在我們的主代碼中,我們會(huì)用一個(gè)alarm處理模塊after()調(diào)用此函數(shù)野崇,after()會(huì)等待一定的時(shí)間称开,然后再調(diào)用函數(shù)。

我們這里設(shè)置讓Tkinter等待100個(gè)單位(1秒鐘)再調(diào)取simulate乓梨。

if __name__ == '__main__':
    root = tk.Tk()
    cv = tk.Canvas(root, height=600, width=600)
    # 繪制一個(gè)黑色背景
    cv.create_rectangle(0, 0, 600, 600, fill="black")
    cv.pack()

    root.protocol("WM_DELETE_WINDOW", close)
    # 在1秒后才開始調(diào)用stimulate()
    root.after(100, simulate, cv)
    root.mainloop()

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

img

本文只一個(gè)簡單版本,等進(jìn)一步熟悉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
# 顏色選項(xiàng)(隨機(jī)或者按順序)
colors = ['red', 'blue', 'yellow', 'white', 'green', 'orange', 'purple', 'seagreen', 'indigo', 'cornflowerblue']

'''
particles 類

粒子在空中隨機(jī)生成隨機(jī)狈惫,變成一個(gè)圈睛蛛、下墜鹦马、消失

屬性:
    - id: 粒子的id
    - x, y: 粒子的坐標(biāo)
    - vx, vy: 在坐標(biāo)的變化速度
    - total: 總數(shù)
    - age: 粒子存在的時(shí)長
    - color: 顏色
    - cv: 畫布
    - lifespan: 最高存在時(shí)長

'''


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

        # 粒子范圍擴(kuò)大
        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 job
            self.cv.move(self.cid, self.vx + move_x, self.vy + GRAVITY * dt)
            self.vy += GRAVITY * dt

        # 移除超過最高時(shí)長的粒子
        elif self.cid is not None:
            cv.delete(self.cid)
            self.cid = None

    # 擴(kuò)大的時(shí)間
    def expand (self):
        return self.age <= 1.2

    # 粒子是否在最高存在時(shí)長內(nèi)
    def alive(self):
        return self.age <= self.lifespan

'''
循環(huán)調(diào)用保持不停
'''
def simulate(cv):
    t = time()
    explode_points = []
    wait_time = randint(10, 100)
    numb_explode = randint(6, 10)
    # 創(chuàng)建一個(gè)所有粒子同時(shí)擴(kuò)大的二維列表
    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內(nèi)一直擴(kuò)大
    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
    # 循環(huán)調(diào)用
    root.after(wait_time, simulate, cv)


def close(*ignore):
    """退出程序、關(guān)閉窗口"""
    global root
    root.quit()


if __name__ == '__main__':
    root = tk.Tk()
    cv = tk.Canvas(root, height=400, width=600)
    # 選一個(gè)好看的背景會(huì)讓效果更驚艷忆肾!
    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()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荸频,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子客冈,更是在濱河造成了極大的恐慌旭从,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件场仲,死亡現(xiàn)場離奇詭異和悦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)渠缕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門鸽素,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亦鳞,你說我怎么就攤上這事馍忽。” “怎么了燕差?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵遭笋,是天一觀的道長。 經(jīng)常有香客問我徒探,道長瓦呼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任刹帕,我火速辦了婚禮吵血,結(jié)果婚禮上谎替,老公的妹妹穿的比我還像新娘偷溺。我一直安慰自己,他們只是感情好钱贯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布挫掏。 她就那樣靜靜地躺著,像睡著了一般秩命。 火紅的嫁衣襯著肌膚如雪尉共。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天弃锐,我揣著相機(jī)與錄音袄友,去河邊找鬼。 笑死霹菊,一個(gè)胖子當(dāng)著我的面吹牛剧蚣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼鸠按,長吁一口氣:“原來是場噩夢啊……” “哼礼搁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起目尖,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤馒吴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瑟曲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饮戳,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年洞拨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了莹捡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖但绕,靈堂內(nèi)的尸體忽然破棺而出尖飞,到底是詐尸還是另有隱情,我是刑警寧澤启泣,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站示辈,受9級(jí)特大地震影響寥茫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜矾麻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一纱耻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧险耀,春花似錦弄喘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贬派,卻和暖如春急但,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搞乏。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工波桩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人请敦。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓镐躲,卻偏偏與公主長得像柏卤,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匀油,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 原作者:http://www.380y.com/p951.htm Chardet字符編碼探測器缘缚,可以自動(dòng)檢測文本、...
    步_塵閱讀 7,054評(píng)論 1 13
  • 今天敌蚜,我給學(xué)生上書法課桥滨。 上課伊始,學(xué)生們已準(zhǔn)備好了筆和本弛车,安靜地等待著齐媒,六十多雙眼睛都看向我,有興...
    三門峽032許靜萍閱讀 605評(píng)論 0 0
  • 靈修記錄20171128 讀經(jīng) 你們年幼的纷跛,也要順服年長的喻括。就是你們眾人也都要以謙卑束腰,彼此順服贫奠;因?yàn)樯褡钃躜湴?..
    他不在這里閱讀 373評(píng)論 0 0
  • 目 錄下一章 “不好意思唬血,可以停一下嗎?”Henry禮貌性的插了一句話唤崭。 嗝拷恨。 少年愣神了一下,識(shí)時(shí)務(wù)者不為林俊杰...
    一枚少年閱讀 630評(píng)論 2 49
  • 20180312第12次彩虹隊(duì)分享內(nèi)容預(yù)告: 分享者:王亞男 分享主題:OmniFous 大家好谢肾!我是7組王亞...
    幸運(yùn)的王亞男閱讀 613評(píng)論 0 6