您的位置:首页 > 其它

Canvas模拟粒子系统

2015-09-02 19:47 549 查看

置顶文章:《纯CSS打造银色MacBook Air(完整版)》

上一篇:《js实现黑客帝国二进制雨》

作者主页:myvin

博主QQ:851399101(点击QQ和博主发起临时会话)

span{ color:red; }

写在前面

用Canvas做了一个小例子系统,包括碰撞检测,随机喷发等一些特点,用到了canvas的径向、阴影一些属性。当然作为“系统”是夸大其词,大家看着玩就行。

效果1:从视口中心发射随机数量随机大小的金属小球,有边缘碰撞效果,当小球半径减小到一定数值或小球生命周期结束后,小球消失。

点击这里Codepen上查看并修改查看实时效果。

下面是效果图:



效果2:从视口随机位置发射随机数量随机大小的金属小球,有小球碰撞检测效果,有边缘碰撞效果,小球不消失。

点击这里Codepen上查看并修改查看实时效果。

下面是效果图:



编程思路

先简单说一下我的编程思路:

首先当然是建立一个canvas画布了

新建一个函数Func()

在上述Fanc()函数的原型里添加一些方法,这些方法包括以下三个:

初始化init方法:这里主要用来设置小球的半径、位置、速度、加速度和生命周期等相关变量,其中大部分都是随机的

绘制函数draw方法:这里主要是用来绘制金属小球,包括径向阴影等,该方法下包含一个update方法

更新小球动作的update方法:这里主要是用来更新小球的速度、位置、半径和生命周期等相关变量,还包括一个边缘碰撞检测和生命周期检测

一个产生一定范围的随机函数random

initAnimation函数:该函数用来循环产生小球,并把小球push进一个数组里

animation函数:该函数用来调用数组中每个小球的绘制方法draw(绘制方法draw中还包括update)

requestAnimationFrame循环

以上就是整个编程思路,大家可以简单遛一遍。

下面结合上面的步骤和相关程序来聊一聊整个小粒子系统。

效果一

canvas画布及一些初始变量

首先是一段简单的样式:

*{
margin:0 auto;
padding:0 auto;
}
body{
background: white;
overflow: hidden;
}

然后,定义了一张canvas画布,id是canvas:

<canvas id="canvas"></canvas>

再贴出一些初始化变量:

window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
var partical=[];
var c=document.getElementById("canvas");
var cx=c.getContext("2d");
var height=c.height=window.innerHeight;
var width=c.width=window.innerWidth;
var rMin=3,particalMax=random(20,40);
//cx.fillStyle="000000";
//cx.fillRect(0,0,width,height);

首先获取
requestAnimationFrame
partical
数组用来盛放例子,接下来就是获取
2d
绘制环境,并将视口的height和width作为的画布的height和width。

rMIn定义的是小球的半径最小值,即减小到多少小球消失,
particalMax
定义的是随机的产生的粒子的最大值,介于20到40之间,大家可以按照需要自己定义。下面是两行之前的注释掉的程序,懒得删了。

function random(min,max){
return Math.floor(Math.random()*(max-min+1)+min);
}

这是一个产生
min
max
之间数值的随机函数,很简单,不再赘述。

Func()函数及其方法

然后定义一个函数
Func()
tip:上述提到的方法都是在`Func()`的原型里面定义的。

init方法

首先是初始化方法,如下:

init: function(){
this.r=random(15,50);//半径
this.x=width/2;//x坐标
this.y=height/2;
this.vx=random(-10,10);//x方向速度
this.vy=random(-10,10);
this.g=0.1;//1;//重力加速度
this.life=1;//生命周期
this.lifeMax=random(60,120);
//partical.push(par);
},

因为是在
Func()
函数的
prototype
里面定义的,所以这里我们都用
this
来限制作用域。

这里例子喷发的位置定义为了视口的中心,粒子的速度有x方向和y方向的,正负均可,加速度请自行修改。生命周期初始值
this.life
定义为了1,下面有生命的递增操作,生命周期最大值设定为了60到120之间的一个随机值。

draw方法

draw方法如下:

draw:function(){
//cx.clearRect(0,0,width,height);//清除整个画布
//cx.fillStyle="rgba(0,0,0,0.3)";
//cx.fillRect(0,0,width,height);

var ball=cx.createRadialGradient(this.x,this.y-0.6*this.r,0,this.x,this.y-0.6*this.r,this.r);//径向过度效果
ball.addColorStop(0,'#ffffff');
ball.addColorStop(0.4,'rgba(120,120,120,1)');
ball.addColorStop(1,'rgba(20,20,20,0.9)');

cx.shadowOffsetX=0;//阴影效果
cx.shadowOffsetY=0.7*this.r;//70;
cx.shadowBlur=0.5*this.r;
cx.shadowColor='rgba(20,20,20,0.6)';

cx.fillStyle=ball;//"#ff0000";
cx.beginPath();
cx.arc(this.x,this.y,this.r,0,2*Math.PI,false);
cx.closePath();
cx.fill();

this.update();//调用更新

},

说一下该方法注释的地方。刚开始写的时候并没有把这些方法都写在原型里,而是分成了几个独立的函数,而且,开始用的也并不是金属小球,而是简单的有颜色填充的圆,刚开始的想法是喷发粒子的时候粒子会有尾巴阴影,所以就用上了
cx.fillStyle="rgba(0,0,0,0.3)";
这句代码,后来换成的类似金属的小球就把它注释掉了。

以上是我开始编程的一些想法。好了,说正题。

先来绘制一个有阴影效果的伪立体小球,说伪,是因为只是用径向效果来模拟的。

我们要的小球的效果是这样的:



为了体现立体的效果,用了三个径向过渡,白色,rgba(120,120,120,1)和rgba(20,20,20,0.9),因为只是简单试了试随便选的颜色,想要更好的效果请自行匹配颜色,在这里大家就先凑合着看吧。

在这里,我把径向的圆心坐标设定在了圆的偏上部,造成灯光从上面打下来的效果,这里圆心坐标是
this.x,this.y-0.6*this.r
,然后调用
createRadialGradient
方法来径向,再用上面的三种颜色作为径向结束边缘。这里的结束分别是0、0.4、1。

这样一个有点立体的小球算是绘制好了,接着我们来绘制小球的阴影。

cx.shadowOffsetX=0;//阴影效果
cx.shadowOffsetY=0.7*this.r;//70;
cx.shadowBlur=0.5*this.r;
cx.shadowColor='rgba(20,20,20,0.6)';

阴影只在y轴上,然后设置阴影大小和模糊半径即可。

上面只是一些状态,接下来就是真正地把这个立体有阴影的小球给绘制到浏览器上。

把径向的变量
ball
作为
fillStyle
的直,然后以
this.x``this.y
为圆心绘制出院圆即可。

最后把
update()
方法调用一下,即把小球路径等状态的更新也一并放在绘制方法
draw
里。

update方法

update方法如下:

update: function(){
this.vx=this.vx;
this.vy=this.vy+this.g;
this.x+=this.vx;
this.y+=this.vy;
this.life++;;
this.r*=0.99;
if((this.x>=width-this.r)||(this.x<=this.r)){//边缘碰撞检测
this.vx=-this.vx;
}
if((this.y>=height-this.r)||(this.y<=this.r)){
this.vy=-this.vy;
}
if((this.life>=this.lifeMax)||(this.r<=rMin)){//生命周期检测
this.init();
}

}

这个方法很简单,只是把位置速度等状态的变化写进去即可。

这里,水平速度vx保持不变,垂直分量的速度vy为上一次加上重力加速度
this.g
,幸好高中物理还记着点呢,哈哈。

接下来,小球的位置x、y在这里只是简单加上了相应的速度分量,显然这是不符合物理定理的,但是这里只是简单的示意,为了简单我就先这样写了,不服的小伙伴可以自行修改。修改的话点击这里codepen上在线修改观察实时效果就行了。然后生命周期我这里也只是简单的递增一,为了使得小球的半径不至于变化的太快,这里
this.r*=0.99


然后就是边缘碰撞检测和生命周期检测了,如下:。

if((this.x>=width-this.r)||(this.x<=this.r)){//边缘碰撞检测
this.vx=-this.vx;
}
if((this.y>=height-this.r)||(this.y<=this.r)){
this.vy=-this.vy;
}
if((this.life>=this.lifeMax)||(this.r<=rMin)){//生命周期检测
this.init();
}

因为小球也是有大小半径的,所以检测视口边缘的时候都分别减去小球的半径,碰撞上之后,速度即为方向,如上。

至于生命周期,只要生命值递增到设定的最大值
lifeMax
,或者小球大小半径减小到设定的最小值
rMIn
,这时再初始化出一个小球来,并push到数组尾部。

initAnimation函数和animation函数

initAnimation函数和animation函数用来调用上述相应的方法,并用
requestAnimationFrame
循环。

initAnimation函数

function initAnimation(){
for(var i=0;i<particalMax;i++){
setTimeout(function(){
var par=new Func();
par.init();
partical.push(par);
},i*20);
}
}

定义一个Func()的实例par,initAnimation函数循环初始化小球,并依次推入数组,为了防止产生过多的小球,这里的时间间隔设置为
i*20
,即越往后越慢。

animation函数

function animation(){
cx.fillStyle="rgba(190,190,190,1)";
cx.fillRect(0,0,width,height);//清除画布
for(var i in partical){
partical[i].draw();

/*for(var j=0;j<i;j++){
if(Math.sqrt(Math.pow(partical[i].x-partical[j].x,2)+Math.pow(partical[i].y-partical[j].y,2))<=(partical[i].r+partical[j].r)){
partical[i].vx=-partical[i].vx;
partical[i].vy=-partical[i].vy;
partical[j].vx=-partical[j].vx;
partical[j].vy=-partical[j].vy;
}
}*/

}
window.requestAnimationFrame(animation);
}

在animation函数函数中,每次调用都必须先清除画布,然后遍历绘制小球(绘制函数中有调用update方法)。然后调用
window.requestAnimationFrame(animation)
api循环即可。

点击这里Codepen上查看并修改查看实时效果一。

效果二

有了效果一,效果二也非常容易实现。首先最主要的部分就是小球与小球之间的碰撞检测。在animation函数中有一段被注释的程序,很容易看出来,这段被注释掉的程序段就是小球与小球之间的碰撞检测。

for(var j=0;j<i;j++){
if(Math.sqrt(Math.pow(partical[i].x-partical[j].x,2)+Math.pow(partical[i].y-partical[j].y,2))<=(partical[i].r+partical[j].r)){
partical[i].vx=-partical[i].vx;
partical[i].vy=-partical[i].vy;
partical[j].vx=-partical[j].vx;
partical[j].vy=-partical[j].vy;
}
}

在遍历绘制出一个小球之后,再遍历刚画出的这个小球之前的小球(后面的小球还没有绘制出来,先不用检测),然后遍历检测两个小球之间的距离,直角三角形判断圆心之间距离即可,这里调用的Math对象的平方和开方方法,不再赘述。

然后因为我们是从视口的随机位置来出现小球,所以,
this.x=random(0,width-this.r)
,同样,
this.y=random(0,height-this.r)
,因为球太多的话会比较乱,所以这里
particalMax
设置小点,
particalMax=random(6,10)
。最后小球的半径和生命周期是固定不变的,所以这里在
update
函数里,将
this.life++
this.r*=0.99
删除即可。到这里效果二就出现了。

点击这里Codepen上查看并修改查看实时效果二。

小结

明天阅兵,不知道还会有多少小伙伴坐在电脑前刷着博客或写着博客~~

祝大家阅兵快乐~~

最后阅兵牛逼,中国牛逼!!

最后的最后附上整个程序,给一些不想点进去codepen上查看代码和效果一效果二的小伙伴们提供一个查看代码的平台。

Markup:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>canvas tests</title>
<style type="text/css">
*{ margin:0 auto; padding:0 auto; } body{ background: white; overflow: hidden; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>

<script type="text/javascript" src="C:/Users/Lenovo/Desktop/myvin博客文章/ceshi.js"></script>
</body>
</html>

JS:

    window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
var partical=[];
var c=document.getElementById("canvas");
var cx=c.getContext("2d");
var height=c.height=window.innerHeight;
var width=c.width=window.innerWidth;
var rMin=3,particalMax=random(20,40);
//cx.fillStyle="000000";
//cx.fillRect(0,0,width,height);
function random(min,max){
return Math.floor(Math.random()*(max-min+1)+min);
}
function Func(){};
Func.prototype={
init: function(){
this.r=150;//random(15,50);//半径
this.x=width/2;//x坐标
this.y=height/2;
this.vx=random(-10,10);//x方向速度
this.vy=random(-10,10);
this.g=0.1;//1;//重力加速度
this.life=1;//生命周期
this.lifeMax=random(60,120);
//partical.push(par);
},
draw:function(){
//cx.clearRect(0,0,width,height);//清除整个画布
//cx.fillStyle="rgba(0,0,0,0.3)";
//cx.fillRect(0,0,width,height);

var ball=cx.createRadialGradient(this.x,this.y-0.6*this.r,0,this.x,this.y-0.6*this.r,this.r);//径向过度效果
ball.addColorStop(0,'#ffffff');
ball.addColorStop(0.4,'rgba(120,120,120,1)');
ball.addColorStop(1,'rgba(0,0,0,0.9)');

cx.shadowOffsetX=0;//阴影效果
cx.shadowOffsetY=0.7*this.r;//70;
cx.shadowBlur=0.5*this.r;
cx.shadowColor='rgba(20,20,20,0.6)';

cx.fillStyle=ball;//"#ff0000";
cx.beginPath();
cx.arc(this.x,this.y,this.r,0,2*Math.PI,false);
cx.closePath();
cx.fill();

this.update();//调用更新

},
update: function(){
this.vx=this.vx;
this.vy=this.vy+this.g;
this.x+=this.vx;
this.y+=this.vy;
this.life++;;
this.r*=0.99;
if((this.x>=width-this.r)||(this.x<=this.r)){//边缘碰撞检测
this.vx=-this.vx;
}
if((this.y>=height-this.r)||(this.y<=this.r)){
this.vy=-this.vy;
}
if((this.life>=this.lifeMax)||(this.r<=rMin)){//生命周期检测
this.init();
}

}
}

function initAnimation(){
for(var i=0;i<particalMax;i++){
setTimeout(function(){
var par=new Func();
par.init();
partical.push(par);
},i*20);
}
}

function animation(){
cx.fillStyle="rgba(190,190,190,1)";
cx.fillRect(0,0,width,height);//清除画布
for(var i in partical){
partical[i].draw();

/*for(var j=0;j<i;j++){
if(Math.sqrt(Math.pow(partical[i].x-partical[j].x,2)+Math.pow(partical[i].y-partical[j].y,2))<=(partical[i].r+partical[j].r)){
partical[i].vx=-partical[i].vx;
partical[i].vy=-partical[i].vy;
partical[j].vx=-partical[j].vx;
partical[j].vy=-partical[j].vy;
}
}*/

}
window.requestAnimationFrame(animation);
}

initAnimation();
window.requestAnimationFrame(animation);


转载请记得说明作者和出处哦-.-

作者:myvin

原文出处:http://www.cnblogs.com/myvin/p/4779344.html

下一篇:《CSS绘制Android Robot》

置顶文章:《纯CSS打造银色MacBook Air(完整版)》

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: