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(完整版)》
相关文章推荐