[python]代码库
# -*- coding: utf-8 -*-
import math, random, time
import threading
import tkinter as tk
import re
# import uuid
Fireworks = []
maxFireworks = 8
height, width = 600, 600
class firework(object):
def __init__(self, color, speed, width, height):
# uid=uuid.uuid1()
self.radius = random.randint(2, 4) # 粒子半径为2~4像素
self.color = color # 粒子颜色
self.speed = speed # speed是1.5-3.5秒
self.status = 0 # 在烟花未爆炸的情况下,status=0;爆炸后,status>=1;当status>100时,烟花的生命期终止
self.nParticle = random.randint(20, 30) # 粒子数量
self.center = [random.randint(0, width - 1), random.randint(0, height - 1)] # 烟花随机中心坐标
self.oneParticle = [] # 原始粒子坐标(100%状态时)
self.rotTheta = random.uniform(0, 2 * math.pi) # 椭圆平面旋转角
# 椭圆参数方程:x=a*cos(theta),y=b*sin(theta)
# ellipsePara=[a,b]
self.ellipsePara = [random.randint(30, 40), random.randint(20, 30)]
theta = 2 * math.pi / self.nParticle
for i in range(self.nParticle):
t = random.uniform(-1.0 / 16, 1.0 / 16) # 产生一个 [-1/16,1/16) 的随机数
x, y = self.ellipsePara[0] * math.cos(theta * i + t), self.ellipsePara[1] * math.sin(
theta * i + t) # 椭圆参数方程
xx, yy = x * math.cos(self.rotTheta) - y * math.sin(self.rotTheta), y * math.cos(
self.rotTheta) + x * math.sin(self.rotTheta) # 平面旋转方程
self.oneParticle.append([xx, yy])
self.curParticle = self.oneParticle[0:] # 当前粒子坐标
self.thread = threading.Thread(target=self.extend) # 建立线程对象
def extend(self): # 粒子群状态变化函数线程
for i in range(100):
self.status += 1 # 更新状态标识
self.curParticle = [[one[0] * self.status / 100, one[1] * self.status / 100] for one in
self.oneParticle] # 更新粒子群坐标
time.sleep(self.speed / 50)
def explode(self):
self.thread.setDaemon(True) # 把现程设为守护线程
self.thread.start() # 启动线程
def __repr__(self):
return ('color:{color}\n'
'speed:{speed}\n'
'number of particle: {np}\n'
'center:[{cx} , {cy}]\n'
'ellipse:a={ea} , b={eb}\n'
'particle:\n{p}\n'
).format(color=self.color, speed=self.speed, np=self.nParticle, cx=self.center[0], cy=self.center[1],
p=str(self.oneParticle), ea=self.ellipsePara[0], eb=self.ellipsePara[1])
def colorChange(fire):
rgb = re.findall(r'(.{2})', fire.color[1:])
cs = fire.status
f = lambda x, c: hex(int(int(x, 16) * (100 - c) / 30))[2:] # 当粒子寿命到70%时,颜色开始线性衰减
if cs > 70:
ccr, ccg, ccb = f(rgb[0], cs), f(rgb[1], cs), f(rgb[2], cs)
else:
ccr, ccg, ccb = rgb[0], rgb[1], rgb[2]
return '#{0:0>2}{1:0>2}{2:0>2}'.format(ccr, ccg, ccb)
def appendFirework(n=1): # 递归生成烟花对象
if n > maxFireworks or len(Fireworks) > maxFireworks:
pass
elif n == 1:
cl = '#{0:0>6}'.format(hex(int(random.randint(0, 16777215)))[2:]) # 产生一个0~16777215(0xFFFFFF)的随机数,作为随机颜色
a = firework(cl, random.uniform(1.5, 3.5), width, height)
Fireworks.append({'particle': a, 'points': []}) # 建立粒子显示列表,‘particle’为一个烟花对象,‘points’为每一个粒子显示时的对象变量集
a.explode()
else:
appendFirework()
appendFirework(n - 1)
def show(c):
for p in Fireworks: # 每次刷新显示,先把已有的所以粒子全部删除
for pp in p['points']:
c.delete(pp)
for p in Fireworks: # 根据每个烟花对象,计算其中每个粒子的显示对象
oneP = p['particle']
if oneP.status == 100: # 状态标识为100,说明烟花寿命结束
Fireworks.remove(p) # 移出当前烟花
appendFirework() # 新增一个烟花
continue
else:
li = [[int(cp[0] * 2) + oneP.center[0], int(cp[1] * 2) + oneP.center[1]] for cp in
oneP.curParticle] # 把中心为原点的椭圆平移到随机圆心坐标上
color = colorChange(oneP) # 根据烟花当前状态计算当前颜色
for pp in li:
p['points'].append(
c.create_oval(pp[0] - oneP.radius, pp[1] - oneP.radius, pp[0] + oneP.radius, pp[1] + oneP.radius,
fill=color)) # 绘制烟花每个粒子
root.after(50, show, c) # 回调,每50ms刷新一次
if __name__ == '__main__':
appendFirework(maxFireworks)
root = tk.Tk()
cv = tk.Canvas(root, height=height, width=width)
cv.create_rectangle(0, 0, width, height, fill="black")
cv.pack()
root.after(50, show, cv)
root.mainloop()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
在这里插入图片描述
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 = 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
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 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
'''
循环调用保持不停
'''
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=1200, width=800)
# 选一个好看的背景会让效果更惊艳!
image = Image.open("./image.png")
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()
[代码运行效果截图]