浪漫好看的烟花相信没有人不喜欢,文本我们就来做一个属于自己的烟花,动动鼠标就能给自己放一个。
最终效果抢先看:烟花效果
基本框架
首先基本的动画循环必不可少:
所有的动画都会在这个循环里进行更新。另外可以看到我们使用了半透明的颜色来填充画布,因为烟花的火焰在天上移动肯定会形成拖尾效果,拖尾的长度可以通过透明度来调整,透明度约大,拖尾越长,反之越短,你可以根据自己的喜好来调整。
然后又因为烟花是从底部往上发射的,所以为了方便计算我们把画布的坐标系改成笛卡尔坐标系:
最后再添加一个按钮,用来发射烟花:
到这里,基本的框架就完成了。
烟花
一个基本的烟花包含两部分:发射器、爆炸器,爆炸器会随着发射器发射到天上,发射器运动停止后爆炸器爆炸形成绚烂的烟花,所以我们创建一个烟花类来管理这两个部分:
color
用来存储烟花的颜色,严格来说是主颜色,可能会添加少量的其他颜色,如果没有指定会随机生成一个颜色,这个颜色会作为发射器和爆炸器的颜色。
然后添加了两个属性用来存储发射器和爆炸器。
最后添加了一个状态字段用来指示烟花当前所处的阶段。
接下来添加几个方法:
一个发射方法一个爆炸方法,update
方法用来更新动画,会在动画循环里进行调用:
定义了一个数组用来存放烟花列表,因为同一时间可能发射了多个烟花,然后在动画循环里遍历这个数组调用烟花的更新方法更新烟花动画。
当我们点击发射按钮时,只要新建一个烟花类,添加到烟花数组里,然后调用烟花的发射方法就可以了:
发射
发射就是火药被发射到天上,我们使用一个圆形来代表火药,填充颜色就是烟花的颜色,火药上天是一个先加速后减速的过程,如果你数学和物理学的比较好,可以自己来计算这个速度和加速度,这里我们直接使用缓动函数,因此还需要定义发射器的起始位置、结束位置、发射持续时间等等属性。那么一个基本的发射器类如下:
各种数值都设置为一定范围内的随机值,接下来是对应的方法:
start
方法里记录发射时的时间。
update
方法用于更新动画,使用了easeOutCubic
缓动函数来实现逐渐减速的动画效果,并且会返回一个对象,里面保存了当前发射器的位置,因为后续的动画可能会依赖这个位置,以及一个发射是否结束的标志,烟花类可以根据这个标志判断是否进入爆炸阶段。
draw
方法用于绘制发射器,可以看到我们绘制了两个圆,一个大圆填充的是烟花的颜色,另外还在大圆上面绘制了一个半径一半的小圆,填充为白色,这样会显示出发射器中心比较亮的效果。
考虑到绘制圆是一个很通用的逻辑,所以我们提取成了一个公共函数drawCircle
:
接下来完善一下烟花类,加上发射和更新的逻辑:
在launch
方法里创建了一个发射器实例,然后调用了发射器的start
方法,并且将当前状态设置为launching
,代表发射中。
在update
方法里判断当前状态是否是发射中,是的话就调用发射器的更新方法,并且根据返回值判断发射器是否发射结束,结束了就更新状态为bursting
,并开启爆炸,当然爆炸的逻辑我们还没写。
现在点击发射按钮应该就能看到发射效果了:
我们设想的中心最亮、拖尾效果都有了,但是最后阶段过于清晰明亮了,理论上来说此时应该已经快消失了才对,所以我们需要给发射器加上透明度的变化:
我们让透明度随着动画的进度逐渐减小,另外因为y
有可能会大于ty
,所以透明度可能计算出小数,所以要判断一下:
可以看到现在效果好了很多,但还是不够,实际上发射器划过的地方或多或少都会留下一些碎片,我们称之为痕迹碎片,现在划过不留痕,显然不够合理,痕迹碎片我们也用圆形来表示,给它一个初始位置,然后就随着重力下落,颜色的话跟随烟花的颜色,碎片透明度肯定不会太高,所以给一个小一点的随机数,最重要的是一会就会消失,所以给一个短一点的生存时间,时间到了就让它消失,也就是不再绘制它。这样我们的痕迹碎片类如下:
重力加速度默认为0.98px/s2
,接下来是它的开始及更新方法:
基本框架和前面的是一样的,start
方法记录开始时间,update
方法更新及绘制,y
坐标会随着重力逐渐减小,代表下落,使用的是自由落体的公式:
最后当时间超过了该碎片的生存时间就返回true
,代表碎片消失了。
接下来要做的就是在发射器发射的过程中随机掉落一些痕迹碎片,但是有一个问题,这个碎片存储在烟花类上,还是发射器类上呢,其实都可以,但是存储在烟花类上会更好一点,因为碎片和发射器其实并没有强关联,有可能发射器已经消失了,但是碎片还没有,并且后面爆炸时也会产生痕迹碎片,所以一起保存到烟花类上会更方便。
使用随机数来实现随机掉落,另外当发射器透明度小于0.1
就不再掉落了,因为此时速度已经很慢,位移很小,如果继续掉落同一位置会聚集很多碎片,不是很好看。另外还给x
坐标添加了一点随机量,否则碎片都在同一条直线上不够自然。
接着在烟花类上增加添加和更新碎片的方法:
首先新增了一个数组来保存痕迹碎片,然后就是碎片实例的创建、启动、收集以及更新,如果某个碎片已经消失了,那么从碎片列表里把它删除。
最后在烟花的更新方法里调用痕迹碎片的更新方法即可:
现在再来看一下效果:
可以看到效果还是很不错的,发射器部分到这里就结束了,接下来就是爆炸效果了。
爆炸
爆炸其实是很多碎片从同一个初始点向四周运动,这���碎片和前面的痕迹碎片有一点不一样,前面的痕迹碎片只有y
方向的速度,这里的碎片x
和y
方向都存在速度,另外也存在一些特有的功能,所以我们会新增一个爆炸碎片类,以及一个用来管理碎片类的爆炸器类,先看爆炸碎片类:
字段比较多,但是基本上和前面的痕迹碎片类差不多,主要是多了一个angle
角度,因为我们说了爆炸碎片是从同一个点向四周发散的,也就是360度,所以我们随机一个角度值, 然后再给它一个随机的速度,速度最大值和最小值相差的比较大,这样才能达到从圆心到最外层都分布了碎片的效果,否则所有碎片都接近的速度,最后就是一个圆环运动的效果了。
有了角度和速度就可以根据三角函数计算出水平和垂直方向的速度。
同样,我们还设置了碎片的重力加速度默认为0.98px/s2
、生存时间为一个比较短的时间。
接下来是碎片的开始以及更新方法:
start
方法依旧只记录一个开始时间。
update
方法会更新碎片的位置,以及绘制碎片。水平位置只根据水平速度更新,垂直位置会叠加一个重力加速度,公式如下:
v0
代表初始速度,g
代表重力加速度,t
代表运动开始以来持续的时间。
这里的透明度设置和之前的碎片不一样,我们设置为当碎片已经走过了生命的70%
以后才开始应用透明度属性,这是考虑到爆炸的时候碎片是非常亮的,没有必要一开始就加上透明度。
爆炸碎片类到这里就结束了,其实也很简单,接下来是管理爆炸碎片的爆炸器类:
定义了爆炸器的初始位置、颜色,以及爆炸碎片的数量,碎片实例会存储到debrisList
数组里。
同样还是开始以及更新的方法:
start
方法里主要是创建指定数量的碎片实例,并且调用碎片的开始方法,以及收集到碎片数组里。
update
方法用来更新碎片动画,如果某个碎片消失了就从数组里删除,当所有碎片都消失了,代表这个爆炸器动画全部结束。
爆炸器和爆炸碎片都有了,接下来就可以在烟花类里使用了:
首先在爆炸方法里创建了一个爆炸器的实例,传入了发射器最后位置坐标,以及烟花的颜色,然后调用开始方法即可。
在更新方法里添加了一个bursting
状态的分支,调用爆炸器的更新方法,当爆炸器动画结束,会设置烟花状态为end
,代表这个烟花放完了。
现在看一下效果:
已经挺像那么回事了,一个最简单的烟花到这里其实就已经完成了,不过我们还可以再继续完善一下。
痕迹碎片
爆炸碎片划过地方肯定也会留下一些痕迹,我们可以通过创建痕迹碎片来实现:
新增了一个debrisCount
来设置爆炸碎片能产生的痕迹碎片数量,为什么要限制呢,主要是因为爆炸碎片数量可能已经比较大,如果某个爆炸碎片继续产生很多的痕迹碎片,那么痕迹碎片的数量可能会非常庞大,会对动画造成卡顿,所以通过限制数量来使动画不至于太卡。
痕迹碎片的x
和y
坐标都添加了一定的随机值,另外半径和重力都减小了,否则痕迹碎片会喧宾夺主。
二次爆炸
二次爆炸也就是爆炸的碎片还会再产生一次爆炸,一般在爆炸碎片消失的瞬间进行,这个实现其实也很简单,只要在爆炸碎片消失的位置再创建一些爆炸碎片进行爆炸即可:
给爆炸器类新增了两个属性,一个代表是否要进行二次爆炸,默认当爆炸碎片数量小于100时进行,另一个代表是否是第一次爆炸,主要是传递给爆炸碎片类,用于判断该碎片是否需要二次爆炸的行为。
我们二次爆炸也是通过调用start
方法完成的,只要指定二次爆炸碎片的初始位置为爆炸碎片的消失位置即可,二次爆炸的碎片数量和速度都设置的非常小,否则就跟正常的爆炸没有区别了。
在创建爆炸碎片实例时会传入该碎片是否要二次爆炸的设置,主要是根据是否是第一次爆炸来设置,如果你想进行三次爆炸,那么也阔以设置成一个计数器,一定要记得更新标志或计数器,否则会产生死循环。
到这里,一个相对完整的烟花就完成了,接下来,享受自己的烟花吧。
总结
本文我们从头实现了一个简单的烟花效果,需要说明的是其中用到了很多魔法数字,也就是各种数值、范围,其实都来源于测试,你完全可以修改它们查看对应的效果,另外你也可以在目前烟花的基础上继续实现更多的烟花效果。
完整源码请移步:Firework。