小年快乐!!!
小年快乐!!!
小年快乐!!!
代码是作者呕心沥血肝出来的,蛀牙还是以学习tkinter图形绘制canvas使用为目的!
而且大都是学过的知识呢!!!还不快给身边的亲戚朋友也露两手!!!
每一根散落的头发都是为了成就今天的自己_(:з」∠)_
以下是所有的代码!只需要把一张背景图片和一个mp3的音频放到和代码同一目录下就ok!
先来看一下导入的模块有哪些:
import tkinter
import time
from random import *
from math import *
from PIL import Image, ImageTk
import threading
import pygame as py
最重要的随机数模块和tkinter模块就是今天的重要角色!
上两张图里面分别有两个重要元素,一个是炸开的烟花,一个是升起的光柱(不太明显就是了!)
也就是一个烟花要分成两个过程,一个是升起,一个是炸开,所以我们需要定义两个类去封装他们。
一个是升起的矩形那个类:
class Rect:
def __init__(self, root, x, y, v_y, r, fly_time, color, a=randint(250, 500)):
"""
:param root: 画布
:param x: 初始横坐标
:param y: 初始纵坐标
:param v_y: 初始速度
:param r: 矩形半径
:param fly_time: 飞行时间
:param color: 颜色
:param a: 加速度
"""
self.fly_time = fly_time
self.root = root
self.v_y = v_y
self.a = a
self.rect = self.root.create_rectangle(
x,
y,
x+r,
y+randint(5, 20),
fill=color,
outline=color
)
self.x = x
self.y = y
def fly(self):
all_time = 0
dt = 0
dy = 0
while all_time <= self.fly_time and self.v_y >= 0:
t1 = time.time()
self.root.move(self.rect, 0, -(self.v_y*dt - self.a*dt*dt / 2))
dy += self.v_y*dt - self.a*dt*dt / 2
self.v_y -= self.a*dt
time.sleep(0.05)
self.root.update()
if self.y - dy <= 0:
self.root.delete(self.rect)
self.rect = None
return 0, 0
t2 = time.time()
dt = t2 - t1
all_time += dt
self.root.delete(self.rect)
self.rect = None
return self.x, self.y - dy
我们定义了一个fly方法,当他被调用的时候将进行一次图像的移动和更新!
加速度a我们采用适当的数值(这取决于我们希望他速度下降的快慢),之后我们直接套用
x=vt-g*t*t/2 来计算出每一次移动之后的y值,因为我们把光柱的升起分成了很多分(可以看作多段的匀减速直线运动),所以我们需要计算每一段开始的初速度,在这里我们定义成v_y,直接用公式v=at就ok了。
最后就要判断一下光柱是否到达顶部,是的话就直接删掉了。如果没到达,返回最后一次运动的x值和y值,也就是炸开的初始位置啦!!!
之后我们定义第二个类,爆炸的粒子类:(这一段不是原创,因为我当时写的时候并没有写的很简洁,就采用了别人的代码,稍加修改,这部分代码在粒子运动上给出了很详细的解释!)
class Particles(object):
def __init__(self, root, id, total, explosion_speed, explosion_radius=1, x=0., y=0., vx=0., vy=0., size=2.,
color='red', lifespan=2., g=0.1):
self.id = id
self.x = x
self.y = y
self.initial_speed = explosion_speed
self.explosion_radius = explosion_radius
self.vx = vx
self.vy = vy
self.total = total
self.age = 0
self.color = color
self.root = root
# 画圆
self.cid = self.root.create_oval(
x - size, y - size, x + size,
y + size, fill=self.color)
self.lifespan = lifespan
self.g = g
def update(self, dt):
self.age += dt
# 粒子范围扩大
if self.alive() and self.expand():
# radians将角度转换为弧度
move_x = cos(radians(self.id * 360 / self.total)) * self.initial_speed
move_y = sin(radians(self.id * 360 / self.total)) * self.initial_speed
# 这里的move是用弧度控制粒子移动,而不是位置的变化
self.root.move(self.cid, move_x * self.explosion_radius, move_y * self.explosion_radius)
self.vx = move_x / (float(dt) * 1000)
# 以自由落体坠落
elif self.alive():
move_x = cos(radians(self.id * 360 / self.total))
self.root.move(self.cid, self.vx + move_x, self.vy + self.g * dt)
self.vy += self.g * dt
# 移除超过最高时长的粒子
elif self.cid is not None:
self.root.delete(self.cid)
self.cid = None
# 扩大的时间
def expand(self):
return self.age <= 0.8
# 粒子是否在最高存在时长内
def alive(self):
return self.age <= self.lifespan
定义完了两个类,我们就开始给他们找对象喽!
首先我们把这一个烟花过程实例化:
def base_th(root, HEIGHT, WIDTH):
while 1:
colors = ['red', 'blue', 'yellow', 'white', 'green', 'orange', 'purple', 'seagreen', 'indigo', 'cornflowerblue']
objects = []
# randint随机生一个整数int类型
x_cordi = randint(0.05 * WIDTH, 0.95 * WIDTH)
y_cordi = randint(0.45 * HEIGHT, 0.75 * HEIGHT)
# uniform在范围内随机生成浮点数
speed = uniform(0.2, 2.)
# 每一个烟花每一个粒子的大小
size = uniform(1, 3)
# choice可以从任何序列,比如list列表中,选取一个随机的元素返回,可以用于字符串、列表、元组等。
color = choice(colors)
# 爆炸速度(每0.01秒移动的距离速度)
explosion_speed = uniform(0.05, 0.2)
# 爆炸半径
explosion_radius = randint(5, 15)
# 总粒子数(当前屏幕上可能出现的初始粒子数)
total_particles = randint(20, 50)
"""升柱"""
rect = Rect(root=root, x=x_cordi, y=y_cordi, v_y=randint(250, 500), r=randint(1, 2),
fly_time=randint(1, 4), color=color)
x, y = rect.fly()
t = time.time()
if x != 0 and y != 0:
# 产生一个烟花
for i in range(1, total_particles):
# 一个粒子产生一个对象,不进行任何操作
r = Particles(root=root, id=i, total=total_particles,
explosion_speed=explosion_speed,
explosion_radius=explosion_radius,
x=x, y=y, vx=speed, vy=speed, color=color, size=size,
lifespan=abs(uniform(0.2, 1.75)))
objects.append(r)
total_time = .0
# 1.8s内一直扩大
while total_time < 1.8:
# 每0.01秒更新一次每一个粒子的状态
time.sleep(0.01)
tnew = time.time()
# dt是一个粒子前后两次移动的耗时
t, dt = tnew, tnew - t
for item in objects:
# 烟花的每一个粒子,每一个粒子都有自己的行动轨迹
item.update(dt)
root.update()
total_time += dt
注意,我写的代码有瑕疵,他每一次只能升起一个烟花,本来是想用多线程实现多个烟花实例同时显示的,但是tkinter的消息队列不是普通的循环,我们只能一个一个的执行了(哎)!
之后,我们在加点小曲:
def music_():
py.mixer.init()
py.mixer.music.load(r'0.mp3')
py.mixer.music.set_volume(0.5)
py.mixer.music.play()
最后就只剩一些修修补补的工作了!!!
def close():
global top
"""退出程序、关闭窗口"""
# 创建销毁窗口 和不加的区别:运行中的程序不加销毁程序直接结束会报错!
# 原理:点击关闭的时候执行close函数,注意,函数后面不加括号
top.protocol("WM_DELETE_WINDOW", close)
# 安全退出程序(执行完当前的消息循环在退出)
top.quit()
if __name__ == '__main__':
top = tkinter.Tk()
image = Image.open("img.png")
photo = ImageTk.PhotoImage(image)
# 控价大小即窗体大小
root = tkinter.Canvas(top, height=photo.height(), width=photo.width())
# anchor锚点
# n 北
# s 南
# w 西
# e 东
# center 中心
# nw 西北
# ne 东北
# sw 西南
# se 东南
root.create_image(0, 0, image=photo, anchor='nw')
root.pack()
height = photo.height()
width = photo.width()
music_()
th1 = threading.Thread(base_th(root, height, width))
th2 = threading.Thread(close())
th1.start()
th2.start()
top.mainloop()
成功完成!!!虽然效果不是很理想,但是也学到了很多东西的说吧!