推薦:Python全棧教程篮撑!花了29980買的從入門(mén)到精通課程研儒,分享給大家
新年伊始患民,各位同學(xué)又給今年定下了什么小目標(biāo)呢羔挡?
本篇文章签财,我們帶給大家一個(gè)小案例:用 Python 制作一個(gè)炫酷煙花秀敬鬓,祝大家新年快樂(lè)淹朋,順便也提前送上春節(jié)祝福笙各。
開(kāi)始之前先看一下最終效果:
環(huán)境介紹:
語(yǔ)言:Python
庫(kù):Pygame
原理介紹
在介紹代碼之前,先介紹下 Pygame 繪制煙花的基本原理础芍,煙花從發(fā)射到綻放一共分為三個(gè)階段:
1杈抢,發(fā)射階段:在這一階段煙花的形狀是線性向上,通過(guò)設(shè)定一組大小不同仑性、顏色不同的點(diǎn)來(lái)模擬“向上發(fā)射” 的運(yùn)動(dòng)運(yùn)動(dòng)春感,運(yùn)動(dòng)過(guò)程中 5個(gè)點(diǎn)被賦予不同大小的加速度,隨著時(shí)間推移虏缸,后面的點(diǎn)會(huì)趕上前面的點(diǎn)鲫懒,最終所有點(diǎn)會(huì)匯聚在一起,處于 綻放準(zhǔn)備階段刽辙;
2窥岩,煙花綻放:煙花綻放這個(gè)階段,是由一個(gè)點(diǎn)分散多個(gè)點(diǎn)向不同方向發(fā)散宰缤,并且每個(gè)點(diǎn)的移動(dòng)軌跡可需要被記錄颂翼,目的是為了追蹤整個(gè)綻放軌跡。
3慨灭,煙花凋零朦乏,此階段負(fù)責(zé)描繪綻放后煙花的效果,綻放后的煙花氧骤,而在每一時(shí)刻點(diǎn)的下降速度和亮度(代碼中也叫透明度)是不一樣的呻疹,因此在代碼里,將煙花綻放后將每個(gè)點(diǎn)賦予兩個(gè)屬性:分別為重力向量和生命周期筹陵,來(lái)模擬煙花在不同時(shí)期時(shí)不同的展現(xiàn)效果刽锤,
代碼實(shí)操
代碼部分將煙花封裝為三個(gè)類:
Firework
: 煙花整體;
Particle
:煙花粒子(包含軌跡)
Trail
:煙花軌跡朦佩,本質(zhì)上是一個(gè)點(diǎn) 并思。
三個(gè)類之間的關(guān)系為:一個(gè)Firework 由多個(gè) Particle 構(gòu)成,而一個(gè) Particle 由多個(gè) Trail 構(gòu)成
首先設(shè)置全局變量语稠,例如重力向量宋彼,窗口大小,Trail 的顏色列表(多為灰色或白色)以及不同狀態(tài)下 Trail 之間間隔
gravity = vector(0, 0.3)
DISPLAY_WIDTH = DISPLAY_HEIGHT = 800
trail_colours = [(45, 45, 45), (60, 60, 60), (75, 75, 75), (125, 125, 125), (150, 150, 150)]
dynamic_offset = 1
static_offset = 3
創(chuàng)建 Trail 類仙畦,定義 show 方法繪制軌跡 输涕、get_pos 實(shí)時(shí)獲取軌跡坐標(biāo)
class Trail:
def __init__(self, n, size, dynamic):
self.pos_in_line = n
self.pos = vector(-10, -10)
self.dynamic = dynamic
if self.dynamic:
self.colour = trail_colours[n]
self.size = int(size - n / 2)
else:
self.colour = (255, 255, 200)
self.size = size - 2
if self.size < 0:
self.size = 0
def get_pos(self, x, y):
self.pos = vector(x, y)
def show(self, win):
pygame.draw.circle(win, self.colour, (int(self.pos.x), int(self.pos.y)), self.size)
Particle 類核心代碼
class Particle:
def __init__(self, x, y, firework, colour):
self.firework = firework
self.pos = vector(x, y)
self.origin = vector(x, y)
self.radius = 20
self.remove = False
self.explosion_radius = randint(5, 18)
self.life = 0
self.acc = vector(0, 0)
# trail variables
self.trails = [] # stores the particles trail objects
self.prev_posx = [-10] * 10 # stores the 10 last positions
self.prev_posy = [-10] * 10 # stores the 10 last positions
if self.firework:
self.vel = vector(0, -randint(17, 20))
self.size = 5
self.colour = colour
for i in range(5):
self.trails.append(Trail(i, self.size, True))
else:
self.vel = vector(uniform(-1, 1), uniform(-1, 1))
self.vel.x *= randint(7, self.explosion_radius + 2)
self.vel.y *= randint(7, self.explosion_radius + 2)
# 向量
self.size = randint(2, 4)
self.colour = choice(colour)
# 5 個(gè) tails總計(jì)
for i in range(5):
self.trails.append(Trail(i, self.size, False))
def apply_force(self, force):
self.acc += force
def move(self):
if not self.firework:
self.vel.x *= 0.8
self.vel.y *= 0.8
self.vel += self.acc
self.pos += self.vel
self.acc *= 0
if self.life == 0 and not self.firework: # check if particle is outside explosion radius
distance = math.sqrt((self.pos.x - self.origin.x) ** 2 + (self.pos.y - self.origin.y) ** 2)
if distance > self.explosion_radius:
self.remove = True
self.decay()
self.trail_update()
self.life += 1
def show(self, win):
pygame.draw.circle(win, (self.colour[0], self.colour[1], self.colour[2], 0), (int(self.pos.x), int(self.pos.y)),
self.size)
def decay(self): # random decay of the particles
if 50 > self.life > 10: # early stage their is a small chance of decay
ran = randint(0, 30)
if ran == 0:
self.remove = True
elif self.life > 50:
ran = randint(0, 5)
if ran == 0:
self.remove = True
Firework 類核心代碼
class Firework:
def __init__(self):
# 隨機(jī)顏色
self.colour = (randint(0, 255), randint(0, 255), randint(0, 255))
self.colours = (
(randint(0, 255), randint(0, 255), randint(0, 255)),
(randint(0, 255), randint(0, 255), randint(0, 255)),
(randint(0, 255), randint(0, 255), randint(0, 255)))
self.firework = Particle(randint(0, DISPLAY_WIDTH), DISPLAY_HEIGHT, True,
self.colour) # Creates the firework particle
self.exploded = False
self.particles = []
self.min_max_particles = vector(100, 225)
def update(self, win): # called every frame
if not self.exploded:
self.firework.apply_force(gravity)
self.firework.move()
for tf in self.firework.trails:
tf.show(win)
self.show(win)
if self.firework.vel.y >= 0:
self.exploded = True
self.explode()
else:
for particle in self.particles:
particle.apply_force(vector(gravity.x + uniform(-1, 1) / 20, gravity.y / 2 + (randint(1, 8) / 100)))
particle.move()
for t in particle.trails:
t.show(win)
particle.show(win)
def remove(self):
if self.exploded:
for p in self.particles:
if p.remove is True:
self.particles.remove(p)
if len(self.particles) == 0:
return True
else:
return False
最后,寫(xiě)一個(gè) main 方法來(lái)對(duì) pygame 環(huán)境進(jìn)行初始化议泵,例如背景圖片占贫,文字,設(shè)置頁(yè)面刷新間隔先口,程序中設(shè)置的每 60ms 刷新一次型奥。
pygame.display.set_caption("Fireworks in Pygame") # 標(biāo)題
background = pygame.image.load("img/1.png") # 背景
myfont = pygame.font.Font("img/simkai.ttf",80)
myfont1 = pygame.font.Font("img/simkai.ttf", 30)
testsurface = myfont.render("元旦快樂(lè)",False,(255,255,255))
testsurface1 = myfont1.render("By:小張Python", False, (255, 255, 255))
# pygame.image.load("")
win = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))
# win.blit(background)
clock = pygame.time.Clock()
fireworks = [Firework() for i in range(2)] # create the first fireworks
running = True
while running:
clock.tick(60)
win.fill((20, 20, 30)) # draw background
win.blit(background,(0,0))
win.blit(testsurface,(200,200))
win.blit(testsurface1, (300,200))
if randint(0, 20) == 1: # create new firework
fireworks.append(Firework())
update(win, fireworks)
另外程序中會(huì)對(duì)你的按鍵命令進(jìn)行監(jiān)控:
- 當(dāng)按下鍵 1時(shí) 瞳收,會(huì)立即生成一個(gè)新的 “煙花”;
- 當(dāng)按下鍵 2時(shí)厢汹,會(huì)同時(shí)生成 10 個(gè) “煙花”
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN: # Change game speed with number keys
if event.key == pygame.K_1: # 按下 1
fireworks.append(Firework())
if event.key == pygame.K_2: # 按下 2 加入10個(gè)煙花
for i in range(10):
fireworks.append(Firework())
小結(jié)
總的來(lái)說(shuō)螟深,整個(gè)小案例的代碼量不算很多,一共250行左右烫葬,但案例中涉及到較為復(fù)雜的繪制邏輯和抽象的類之間的封裝關(guān)系界弧,因此大家理解代碼相對(duì)會(huì)需要耗費(fèi)點(diǎn)時(shí)間。
本篇文章主要介紹了如何用 Pygame 來(lái)模擬一個(gè)煙花綻放過(guò)程搭综,核心內(nèi)容大致兩點(diǎn):第一垢箕,如何用繪制點(diǎn)的方式來(lái)模擬煙花綻放運(yùn)動(dòng)軌跡;第二兑巾,介紹Pygame 一些基礎(chǔ)用法:替換背景条获,繪制文字,更新?tīng)顟B(tài)等功能蒋歌。