Python?Pygame實(shí)現(xiàn)可控制的煙花游戲
自控?zé)熁ㄉ?實(shí)現(xiàn)效果描述效果代碼地址解析main.pycore.pyfireworks.py 寫(xiě)在最后
實(shí)現(xiàn)效果描述
這大過(guò)年的不弄點(diǎn)有意思的怎么行呢?可以考慮用編程實(shí)現(xiàn)一個(gè)煙花升空-爆炸-絢麗地效果。隨機(jī)的煙花也玩習(xí)慣了,這次我們用pygame實(shí)現(xiàn)用戶鼠標(biāo)點(diǎn)擊屏幕實(shí)現(xiàn)放煙花并在指定高度綻放~
效果
鼠標(biāo)點(diǎn)擊時(shí),煙花點(diǎn)會(huì)從屏幕底部正中心發(fā)射至鼠標(biāo)點(diǎn)擊點(diǎn)處并綻放出隨機(jī)的顏色,煙花顆粒符合物理規(guī)律,看上去很和諧,并且會(huì)隨機(jī)消失做出閃爍的效果。
那么一起來(lái)看看是如何實(shí)現(xiàn)的吧~
代碼地址
https://gitee.com/DogMonkeys/daily-script/tree/master/fireworks 直接訪問(wèn)即可
解析
main.py
import core def main(): core.init() while True: core.loop() if __name__ == "__main__": main()
很簡(jiǎn)單,調(diào)用core模塊中的初始化函數(shù)和主循環(huán)函數(shù)
core.py
import pygame from fireworks import Fireworks g_fireworks = None # 煙花主類 def init(): global g_fireworks pygame.init() screen = pygame.display.set_mode([800, 600]) pygame.mouse.set_visible(1) pygame.display.set_caption("煙花") g_fireworks = Fireworks(screen) clock = pygame.time.Clock() def loop(): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() else: g_fireworks.run(event) clock.tick(24) pygame.display.update()
這里為了簡(jiǎn)化,直接使用全局變量g_fireworks保存游戲運(yùn)行主類。
初始化函數(shù)中,調(diào)用pygame庫(kù)提供的初始化各項(xiàng)參數(shù)方法,包括設(shè)定窗口大小和標(biāo)題,設(shè)定鼠標(biāo)可見(jiàn),等等
然后設(shè)定全局變量clock用來(lái)控制全局的幀率。(我也不知道為啥要弄一個(gè)global,但是當(dāng)時(shí)就這么寫(xiě)的懶得改了反正沒(méi)啥大問(wèn)題,這部分的源碼未經(jīng)優(yōu)化,許多地方都可以改得讓其更順暢,更符合標(biāo)準(zhǔn)。)
最后是loop主循環(huán)函數(shù),不斷遍歷pygame事件表,如果有退出信號(hào)就退出(很正常,點(diǎn)擊“X”或者按alt+F4都會(huì)觸發(fā))。其他事件交給firework主類完成。
遍歷完事件后就進(jìn)行迭代更新工作,不用解釋,刷新屏幕和控制幀率
接下來(lái)最重要的部分來(lái)了,控制煙花的運(yùn)行!
fireworks.py
先貼出全部代碼吧:
import math import pygame from random import randint from math import sin, cos, radians, tan class Point: def __init__(self, idx, pos, screen, color) -> None: self.rad = radians(idx*6) self.x = pos[0] self.y = pos[1] self.t = 0 self.screen = screen self.color = color self.v0 = 2 # 初速度 self.limit = 51 def get_pos(self) -> list: self.t += 1 self.x += self.v0 * cos(self.rad) self.y -= self.v0 * sin(self.rad) - 0.08 * self.t return [self.x, self.y] def draw(self) -> None: if self.t >= 31: self.limit = randint(35, 50) if self.limit < self.t: return pygame.draw.circle(self.screen, self.color, self.get_pos(), 2) class Fireworks: def __init__(self, screen) -> None: self.screen = screen self.pos = [-1, -1] # 一次只能發(fā)射一個(gè)煙花,免得出問(wèn)題,刷新圖層蓋住就不好了 self.can_fire = True def run(self, event) -> None: if event.type == pygame.MOUSEBUTTONDOWN: if not self.can_fire: return self.pos = pygame.mouse.get_pos() self.fire_to() def fire_to(self) -> None: # 一個(gè)煙花放映過(guò)程中不允許另一個(gè)進(jìn)來(lái),不然會(huì)混亂 self.can_fire = False color = (randint(0, 255), randint(0, 255), randint(0, 255)) try: k = (600 - self.pos[1]) / (self.pos[0] - 400) except ZeroDivisionError: # x==400! # 指針指向屏幕最中間,除零錯(cuò) pass tmp_x, tmp_y = 400, 600 # 分類討論(可能有更好的算法,但這個(gè)最好理解) if self.pos[0] > 400: dX = 0.5 dY = - dX * k elif self.pos[0] == 400: dX = 0 dY = -0.5 else: dX = -0.5 dY = - dX * k v0 = 1 # 目標(biāo)速度,根據(jù)dX與dY求解dX'和dY' # 斜方向速度 dV = math.sqrt(dX**2 + dY**2) # 相似成比例求解 dX *= v0 / dV dY *= v0 / dV while round(tmp_y) != self.pos[1]: tmp_x += dX tmp_y += dY self.screen.fill((0, 0, 0)) pygame.draw.circle(self.screen, color, [tmp_x, tmp_y], 2) pygame.display.update() # 到達(dá)位置,炸開(kāi)! self.bomb(color) self.can_fire = True def bomb(self, color) -> None: self.screen.fill((0, 0, 0)) pygame.display.update() ps = [] for i in range(60): ps.append(Point(i, self.pos, self.screen, color)) for j in range(100): print(".") # 減緩爆炸 self.screen.fill((0, 0, 0)) for point in ps: point.draw() print(".") # 經(jīng)測(cè)試,還是太快了 pygame.display.update() self.screen.fill((0, 0, 0))
下面一部分一部分地分析
首先,導(dǎo)入要用到的庫(kù),包括數(shù)學(xué)庫(kù),隨機(jī)庫(kù)的隨機(jī)整數(shù)方法,數(shù)學(xué)庫(kù)的三角函數(shù)和弧度轉(zhuǎn)換,還有主角pygame。
Point
類先放放,先說(shuō)Fireworks
。首先初始化類成員,然后run方法提供對(duì)事件的判斷,如果鼠標(biāo)按下(pygame中,左右鍵都會(huì)觸發(fā)這個(gè)事件),就看看能不能發(fā)射(即現(xiàn)在有沒(méi)有煙花運(yùn)行中),如果不能就提前退出,免得繼續(xù)下面的運(yùn)算。
如果能,就獲取鼠標(biāo)坐標(biāo)并保存,然后調(diào)用發(fā)射的方法。
fire_to()
方法。首先調(diào)整狀態(tài)機(jī)使之不能再發(fā)射下一個(gè)煙花;然后獲取一個(gè)隨機(jī)的顏色,然后嘗試計(jì)算一下屏幕底部中點(diǎn)(400, 600)到目標(biāo)點(diǎn)的路徑斜率(pygame的坐標(biāo)系:原點(diǎn)在左上角,x,y分別以右和下為+)。這里需要使用try,因?yàn)橹本€垂直于x軸時(shí)斜率不存在,即tan(2/π)=∞ (勉強(qiáng)理解吧…)
然后,計(jì)算坐標(biāo)的增量(初步)
第一種情況,目標(biāo)C在底部中點(diǎn)A的右側(cè),即self.pos[0]>400,此時(shí)計(jì)算的k值實(shí)質(zhì)上是∠A的正切,應(yīng)該是正數(shù)(還是坐標(biāo)系的問(wèn)題,并非經(jīng)典笛卡爾坐標(biāo)系)。
因此煙花在x方向需要+方向運(yùn)動(dòng),y使用需要-方向。設(shè)定x方向增量dX為定值0.5.那么對(duì)應(yīng)的步長(zhǎng)dY可以根據(jù)tanA乘上dX算出來(lái)。注意需要加一個(gè)負(fù)號(hào),因?yàn)閐X=0.5>0, k>0.
第二種,垂直發(fā)射,只需要y軸負(fù)增量就夠了,定值-0.5吧
第三種,如圖,C在A左側(cè)
同理計(jì)算,只是把dX設(shè)為負(fù)值,dY的表達(dá)式不變,因?yàn)榇藭r(shí)直線AB傾角θ的正切是負(fù)值.(即k),所以乘出來(lái)dY仍為負(fù)值
計(jì)算完水平和豎直增量,如果直接測(cè)試會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,速度分布十分不均勻,我們想實(shí)現(xiàn)的效果是,煙花在A->C方向上速度恒定為v0=1
這個(gè)問(wèn)題可以用相似三角形解。首先根據(jù)dX和dY計(jì)算斜方向速度大小(勾股定理),然后根據(jù)v0成比例,等比縮放dX和dY即可。如下:
v0 = 1 # 目標(biāo)速度,根據(jù)dX與dY求解dX'和dY' # 斜方向速度 dV = math.sqrt(dX**2 + dY**2) # 相似成比例求解 dX *= v0 / dV dY *= v0 / dV
在后面,幾乎不怎需要數(shù)學(xué)計(jì)算:
while round(tmp_y) != self.pos[1]: tmp_x += dX tmp_y += dY self.screen.fill((0, 0, 0)) pygame.draw.circle(self.screen, color, [tmp_x, tmp_y], 2) pygame.display.update()
只需要解釋這個(gè)round,對(duì)tmp_y進(jìn)行四舍五入,這樣就可以保證他是個(gè)整數(shù),就可以正常根據(jù)slef.pos[1]的值合適的時(shí)候退出。
注意,這里不能用x比較,因?yàn)槿f(wàn)一 x==400?
接下來(lái),如果while退出,那么進(jìn)入bomb:
ps = [] for i in range(60): ps.append(Point(i, self.pos, self.screen, color)) for j in range(100): print(".") # 減緩爆炸 self.screen.fill((0, 0, 0)) for point in ps: point.draw() print(".") # 經(jīng)測(cè)試,還是太快了 pygame.display.update() self.screen.fill((0, 0, 0))
(省略了兩行很普通的代碼.)
首先構(gòu)建60個(gè)Point對(duì)象,分別存入索引,爆炸點(diǎn),屏幕,顏色
然后重復(fù)構(gòu)建100幀,每一幀都同時(shí)刷新60個(gè)顆粒的坐標(biāo)。這里有一個(gè)特殊用法,就是在大批量循環(huán)里加入一個(gè)print調(diào)用,這樣可以減緩虛擬機(jī)運(yùn)行,讓我們能夠看清煙花的效果,就是慢慢落下并消失(有更好的方法,懶),很直觀不是嗎(笑)
每一幀遍歷爆炸花并顯示,最后刷幀,很好理解。
(前方數(shù)學(xué)高能)
self.v0 = 2 # 初速度 self.limit = 51 def get_pos(self) -> list: self.t += 1 self.x += self.v0 * cos(self.rad) self.y -= self.v0 * sin(self.rad) - 0.08 * self.t return [self.x, self.y]
前面是初始化,后面是獲取當(dāng)前坐標(biāo).這可能是最不能理解的模塊了。
首先設(shè)定一個(gè)合適的初速度,那個(gè)limit后面講。然后,每次迭代把時(shí)間自增1,模擬斜拋運(yùn)動(dòng)中時(shí)間的變化。
我們可以把切線方向初速度v0正交分解成x和y方向兩個(gè)速度,其中直接帶入時(shí)間變化量deltaT=1(因?yàn)閟elf.t += 1),省的計(jì)算效率變慢。
根據(jù)水平位移公式:x=v[0]·cosθ·t,易證水平速度v1=v0cosθ
(由于math庫(kù)的三角函數(shù)要求傳參是弧度,在初始化預(yù)先轉(zhuǎn)換掉.)
縱向速度v2=v0sinθ-gt,相當(dāng)于把重力給的加速度給它消掉一部分,向量加法
這里經(jīng)過(guò)測(cè)試,取一個(gè)合適的重力加速度g=0.08(奇奇怪怪,但事實(shí)就是如此)
然后為了實(shí)現(xiàn)下落一部分時(shí)間就閃爍著消失,可以考慮讓每個(gè)顆粒自己隨機(jī)一個(gè)消失節(jié)點(diǎn),到了就不畫(huà),就能實(shí)現(xiàn)了。
def draw(self) -> None: if self.t >= 31: self.limit = randint(35, 50) if self.limit < self.t: return pygame.draw.circle(self.screen, self.color, self.get_pos(), 2)
相關(guān)代碼,設(shè)定消失可以開(kāi)始的閾值31,然后在35-50幀后消失,相關(guān)邏輯很好懂。
寫(xiě)在最后
感謝一直讀到最后!通過(guò)今天的小煙花游戲希望能提高各位的編程和數(shù)學(xué)能力!
到此這篇關(guān)于Python Pygame實(shí)現(xiàn)可控制的煙花游戲的文章就介紹到這了,更多相關(guān)Python Pygame煙花游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python批量修改文件夾及其子文件夾下的文件內(nèi)容
這篇文章主要為大家詳細(xì)介紹了python批量修改文件夾及其子文件夾下的文件內(nèi)容,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03python調(diào)用jenkinsAPI構(gòu)建jenkins,并傳遞參數(shù)的示例
這篇文章主要介紹了python調(diào)用jenkinsAPI構(gòu)建jenkins,并傳遞參數(shù)的示例,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下2020-12-12Python編程產(chǎn)生非均勻隨機(jī)數(shù)的幾種方法代碼分享
這篇文章主要介紹了Python編程產(chǎn)生非均勻隨機(jī)數(shù)的幾種方法代碼分享,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12Python使用20行代碼實(shí)現(xiàn)微信聊天機(jī)器人
這篇文章主要介紹了Python使用20行代碼實(shí)現(xiàn)微信聊天機(jī)器人,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06設(shè)置python3為默認(rèn)python的方法
我們知道在Windows下多版本共存的配置方法就是改可執(zhí)行文件的名字,配置環(huán)境變量。接下來(lái)通過(guò)本文給大家介紹設(shè)置python3為默認(rèn)python的方法,一起看看吧2018-10-10聽(tīng)歌識(shí)曲--用python實(shí)現(xiàn)一個(gè)音樂(lè)檢索器的功能
本篇文章中主要介紹了用python實(shí)現(xiàn)一個(gè)音樂(lè)檢索器,類似于QQ音樂(lè)的搖一搖識(shí)曲,有興趣的同學(xué)可以了解一下。2016-11-11