当前位置: 首页 > Web前端 > HTML5

【画布流浪(9)】粒子动画

时间:2023-04-05 21:24:46 HTML5

示例代码托管于:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原博文目录华为云社区地址:【你重要的打怪前端升级指南】[TOC]1.粒子效果粒子效果一般是指密集的点阵效果,并不是canvas独有的。这个词经常出现在AE、cocos2d、Unity相关的教程中,并提供了一个方便的编辑插件,让用户可以轻松制作烟花、流星、光晕等动态效果,看起来非常炫酷。如果你接触过Three.js,你会发现三维空间中的点阵效果看起来更加生动。粒子效果的本质还是一帧一帧的动画,所以我们仍然可以使用上一节提到的动画编程范式来实现。本节教程将实现如下粒子效果:这是笔者的第五个版本,长得还挺像的,本文将逐步实现这么炫酷的粒子动画,邀你一起来看看那些爆笑的开发过程中的问号黑脸时刻。2.开发中遇到的问题2.1卡顿要实现上面的动画,我们首先要做的就是建立一个静态的粒子点阵。构建过程并不复杂,无非就是在x和y方向上固定距离画点什么。如果我们将单个粒子定义为精灵而不是粒子群,那么按照上一节的开发范式,我们将在逐帧动画的执行函数步骤中更新粒子晶格的状态如下:functionstep(){...particles.map(particle=>{particle.update();particle.paint();})}但是当粒子矩阵移动时,画面变得巨大,视觉体验很差.实际上,每个精灵状态的update()方法只是javascript中的一些计算代码,执行速度非常快,而paint()方法会经过路径绘制和渲染两个阶段来绘制粒子。这个过程的高频执行会比较耗时,在舞台上元素数量少的时候不会有问题,但是在像粒子点阵这样有大量sprite元素的场景中,它很容易达到性能饱和。解决方案并不复杂。粒子平铺在绘图纸上。绘制顺序不会造成屏幕被覆盖。我们可以先画出所有粒子的路径(一个半径很小的圆),然后最后调用context的.stroke()方法一次性画出所有粒子的边缘,卡顿问题立马解决。就好像SPA框架先收集变化,对新旧DOM树进行diff操作,然后进行集中的DOM更新,以替代独立和分散的DOM操作带来的性能损失。2.2Trajectory静态粒子阵列搭建好后,希望从简单的特效入手,即鼠标移动到某个位置后,固定半径内的所有粒子都会沿径向爆炸,粒子会在初始位置沿鼠标点和Wired运动移动。不过效果如上图所示。虽然看起来很酷,但并不是我们期待的效果。这里只是一个低级的错误,就是step()中没有重绘canvas,canvas就像一张画纸,你画的东西都留在上面,直到你用背景色块覆盖再重绘,由于到基本的视觉暂留,高速重绘变成动画。2.3重置当我们可以模拟粒子沿着爆炸中心爆炸的效果时,我们需要考虑如何重置。起初,作者尝试使用弹簧模型来模拟粒子的行为,但出现了如上图所示的问题。一些粒子在初始点附近开始简谐振动。也很难通过设置最小复位距离来强制复位。如果该值太小,总会有无法捕捉到的点。如果值太大,重置效果会失真。实际上,用复位点作为弹簧模型的平衡点是有问题的,因为简谐振动在过中点时没有受力,但其速度达到最大值,使得逐帧间的位移帧动画变化很大,所以就会出现上述最小复位距离难以确定的问题。越接近真实效果,粒子力场模型就越复杂。如果有兴趣,可以自己搭建力场模型进行仿真。在本章的示例代码中,我们采用了一种简化的处理方式,即爆炸后,直接将粒子放置在较远的位置,并以线性递减的速度逼近其初始位置。离初始位置越近,速度越快。小,当它的距离小于最小复位距离时,它会回到原来的位置。2.4保护层爆炸的粒子可以重置后,最终要达到的效果就是保护环。你可以想象一个透明的球体被扔进水中的效果,但是水在围绕外围移动时无法穿透保护进入球体。笔者第一次建模后得到的效果如上图所示。所用模型为碰撞衰减模型,即将保护层视为钢体表面。当粒子在复位过程中进入保护层时,其速度矢量反转。方向,并乘以衰减系数,当离开保护层时,速度方向再次指向初始位置。那么这个模型有什么问题呢?其实从上面的动画可以看出,由于时间间隔的选择,两帧之间粒子移动的距离可能会很大,导致粒子突然穿透保护层;另一方面,当爆炸中心移动时,粒子复位时的速度方向可能与它与爆炸中心的连线不重合,简单地沿原方向反转速度显然是扭曲的。实际上,在保护层边界的处理中,需要对上述模型进行一些调整。让我们换个角度想一想。如果将防护罩展开成一个平面,那么粒子的运动轨迹就变得清晰了。如果爆炸的中心不动,那么粒子的重置其实就相当于垂直下落。如果爆炸中心与重置中心不重合,则小球的速度总能分解为径向和沿爆炸中心的切线方向,其运动性能与水平物体一致遇到回弹面时的初速度和垂直加速度。简化仿真过程。当小球即将与保护层发生碰撞时,其沿爆炸中心径向方向的速度可以直接清除,只保留切向速度。这样,当粒子撞击到保护层而无法回到原来的位置时,它会沿着保护层的表面运动,这样粒子就不会穿透保护层(更简化的模拟策略是在示例代码中使用,下面会提到)。2.5二维向量类在图形计算中,向量的使用频率极高。将应用于计算距离或判断点、线、面之间的关系等场景。Canvas只是一个画布,Relationships,距离等都需要通过人工计算得到。如果不封装常用的向量运算,代码中会塞满各种代码,比如用Math进行模运算。理解难度也很高,所以我们需要创建一个二维向量类,将向量取模、取反、加减等常用操作挂载在原型链上,让代码本身更有意义。下面是一个常见的二维向量类的实现。您可以根据自己的需要进行修改。在下面的例子中,我们也将直接使用这个类://二维向量类定义Vector2=function(x,y){this.x=x;这个.y=y;};Vector2.prototype={copy:function(){returnnewVector2(this.x,this.y);},length:function(){returnMath.sqrt(this.x*this.x+this.y*this.y);},sqrLength:function(){returnthis.x*this.x+this.y*this.y;},归一化:function(){varinv=1/this.length();返回新的Vector2(this.x*inv,this.y*inv);},否定:function(){returnnewVector2(-this.x,-this.y);},添加:function(v){returnnewVector2(this.x+v.x,this.y+v.y);},subtract:function(v){returnnewVector2(this.x-v.x,this.y-v.y);},multiply:function(f){returnnewVector2(this.x*f,this.y*f);},除法:函数(f){varinvf=1/f;返回新Vector2(this.x*invf,this.y*invf);},dot:function(v){returnthis.x*v.x+this.y*v.y;}};3.本节关键代码的实现和解释分片来解释,完整的示例代码可以从【我的github仓库】获取3.1粒子类的update方法/*方法中涉及的位置相关属性都是向量类Vector2的实例*所以可以调用原型链方法进行向量计算*/update(){letnextPos;//模拟下一个落地点constdisV=this.pos0.subtract(this.pos);//当前位置到返回点的向量constdisL=disV.length();//当前位置到返回点的距离初始点//1。计算速度(设置最小Velocity,避免无限接近但无法返回场景),模拟下一个落地点this.velocity=disV.multiply(kv*disL