桌面上有多个球在同时运动,如何实现球之间不交叉,即碰撞
桌面上有多个球在同时运动,怎么实现球之间不交叉,即碰撞?
稍微想了一下,然后解决了很多bug,最后终于把它实现了。其实原理很简单。在每改变一个小球的x y坐标后,遍历整个在dom树中的其他小球,看一下它们与当前小球的距离是否小于球半径的两倍?若小于说明下一次绘制该小球(设为a)前要把他的方向变为原来相反方向(与a要碰撞的小球设为b),即假如当前小球的距离小于球半径的两倍的话,马上改变当前小球方向。那么下一次绘制也是先绘制b,再绘制a,由于a的方向已经改变,所以距离越来越远,说明一下,可以考虑两种极端情况1.a b同向平行运动,这时假如要碰了(实际不会碰撞因为平行),a的速度方向变了,越来越远。2.a,b垂直运动马上相交,快碰撞时a方向改变,则越来越远。
写程序时可能出现的bug有
1方向相反直接写成angle=-angle;这是错的,因为一定要规定angle不能小于0,因为小球对于浏览器边框的检测是按照angle在0到360之间来检测的,一旦为0,出现对浏览器的越界则无法将其检测出来。
2,由于angle是存在于particle中的私有变量,而判断是否碰撞的检测函数define是需要改变angle的,又由于函数animate中的绘制函数
3当球碰到下边框时,若角度太小,接近于0或者太大接近180度时可能会无法出来,一直在边框上。所以应该有一定限制,我只限制了上边框,代码
取5度即0.08的阈值。一旦小于这个值取为0.1.下边框我没有处理。
4 左下角是我绘制的fps图,一旦绘制到#fps中点时,擦除重绘。
完整代码如下
稍微想了一下,然后解决了很多bug,最后终于把它实现了。其实原理很简单。在每改变一个小球的x y坐标后,遍历整个在dom树中的其他小球,看一下它们与当前小球的距离是否小于球半径的两倍?若小于说明下一次绘制该小球(设为a)前要把他的方向变为原来相反方向(与a要碰撞的小球设为b),即假如当前小球的距离小于球半径的两倍的话,马上改变当前小球方向。那么下一次绘制也是先绘制b,再绘制a,由于a的方向已经改变,所以距离越来越远,说明一下,可以考虑两种极端情况1.a b同向平行运动,这时假如要碰了(实际不会碰撞因为平行),a的速度方向变了,越来越远。2.a,b垂直运动马上相交,快碰撞时a方向改变,则越来越远。
写程序时可能出现的bug有
1方向相反直接写成angle=-angle;这是错的,因为一定要规定angle不能小于0,因为小球对于浏览器边框的检测是按照angle在0到360之间来检测的,一旦为0,出现对浏览器的越界则无法将其检测出来。
2,由于angle是存在于particle中的私有变量,而判断是否碰撞的检测函数define是需要改变angle的,又由于函数animate中的绘制函数
for(var particle=0;particle<particles.length ;particle++){ particles[particle].draw(timeDelta); particles[particle].define(); document.getElementsByClassName('tag')[0].className=''; }需要在绘制完一个点后,马上调用碰撞检测函数define检测该点所以需要把它放在animate当中来调用,particle与animate都是全局变量这就陷入了一个两难的境地,既要全局变量animate能调用define也要让define能够访问全局变量particle下的私有变量,这正符合闭包的定义,闭包是即可以访问私有变量也可以成为全局变量被其他全局变量调用。所以可以把define作为particle下的闭包。
3当球碰到下边框时,若角度太小,接近于0或者太大接近180度时可能会无法出来,一直在边框上。所以应该有一定限制,我只限制了上边框,代码
if(nexty<0){//if angle is between 3 o'clock and 9 o'clock if((angle-0.08)<=0) { angle=0.1; } if(angle+0.08>= Math.PI){ angle=Math.PI-0.1 } angle=Math.PI*2-angle; }
取5度即0.08的阈值。一旦小于这个值取为0.1.下边框我没有处理。
4 左下角是我绘制的fps图,一旦绘制到#fps中点时,擦除重绘。
完整代码如下
css body{ padding: 0px;margin: 0px; overflow: hidden; } span{ width: 16px;height: 16px;display: inline-block;border-radius: 50%;position: absolute; } #fps{ position: fixed;bottom: 10px;right: 10px;font-size: 30px;font-weight: bold; } html <div id="fps"></div> js引用的是color.js 它是一个1匿名函数 如下 (function(){ var MAX_PARTICLES = 50; var MAX_VELOCITY = 500; var PARTICLE_RADIUS = 8; var STAGE_WIDTH = 1024; var STAGE_HEIGHT = 768; var COLORS = ["#cc0000", "#ffcc00", "#aaff00", "#0099cc", "#194c99", "#661999"]; var FRAME_TIMES = []; var fpsArr=[]; var timer; var max=0; var min=60; var average=0; var time=new Date().getTime(); var particles = []; var wrap=document.createElement('div');//fps图表包含块初始化 wrap.className='wrap'; wrap.style.position='absolute'; wrap.style.bottom=10+'px'; wrap.style.left=10+'px'; wrap.style.width=500+'px'; wrap.style.height=120+'px'; document.body.appendChild(wrap); var labelx=1; init(); // debugger(); function init(){ STAGE_WIDTH=document.documentElement.clientWidth;//获取窗口可见宽高 STAGE_HEIGHT=document.documentElement.clientHeight; for(var i=0 ;i<MAX_PARTICLES;i++) particles.push( new particle() ); timer=setInterval(animate ,30); } function paint(fps){//绘制图表上的每一个点 if(labelx>wrap.offsetWidth/2){//一旦大于宽的一半擦掉重绘 var delDot=document.getElementsByClassName('dot'); while(delDot[0]) wrap.removeChild(delDot[0]); labelx=1; } var dot=document.createElement('div'); dot.className='dot'; dot.style.position='absolute'; dot.style.bottom=3*fps+'px'; dot.style.left=labelx+'px'; dot.style.backgroundColor='red'; dot.style.zIndex=-5; dot.style.width=2+'px'; dot.style.height=2+'px'; wrap.appendChild(dot); labelx+=2; } function animate(){//动画 function sum(arr){ var i=0; var sum=0; for(;i<arr.length;i++) sum+=arr[i]; return sum; } if(FRAME_TIMES.length>30) //保持计算fps的存储时间的数组长度为30 FRAME_TIMES.splice(0,1); var curr=new Date().getTime(); FRAME_TIMES.push(curr); // console.log(curr-FRAME_TIMES[FRAME_TIMES.length-2]); var fps=0; if(FRAME_TIMES.length>1) fps= 1000 / ((curr - FRAME_TIMES[0]) / (FRAME_TIMES.length - 1)); fps=Number(fps.toFixed(2)); paint(fps);//绘制fps图表 if(fpsArr.length>30) fpsArr.splice(0,1); fpsArr.push(fps);//存储每个fps方便计算平均值 // console.log(sum(fpsArr)) average=(sum(fpsArr)/fpsArr.length).toFixed(2);//计算平均值 if(fps>max) max=fps; if(fps&&fps<min) min=fps; //fpsArr.push var fshow=document.getElementById('fps'); fshow.innerHTML="average: "+average+ " max: "+max+" min: "+min+" fps: "+fps+'fps'; var timeDelta = curr - FRAME_TIMES[FRAME_TIMES.length - 2]; for(var particle=0;particle<particles.length ;particle++){//绘制每一个点(实际上是改变每一个小球的坐标) particles[particle].draw(timeDelta); particles[particle].define(); /*当前小球绘制完后马上进行碰撞检测*/ document.getElementsByClassName('tag')[0].className='';//检测完回来后置为空,以便下一个元素继续标记自己 } } function distance(a,b){ return Math.sqrt( Math.pow(a,2)+ Math.pow(b,2) ) } function particle(){//小球位置颜色速度角度初始化并添加到dom树 var velocity=(MAX_VELOCITY-MAX_VELOCITY/5)*Math.random()+MAX_VELOCITY/5; var x=STAGE_WIDTH/2-PARTICLE_RADIUS; var y=STAGE_HEIGHT/2-PARTICLE_RADIUS; var color=COLORS[Math.floor( COLORS.length*Math.random() )]; var node=document.createElement('span'); var angle=2*Math.PI*Math.random(); node.style.backgroundColor=color; node.style.left=x+'px'; node.style.top=y+'px'; document.body.appendChild(node); function define(){//碰撞检测 if(new Date().getTime()-time<5000) return var xx=x; var yy=y; var eles=document.getElementsByTagName('span'); //获取此时桌面上的所有小球,因为当前小球已经绘制完,故会连自己也会获取进来。而它自己与自己是永远相碰的故在draw中设置该小球的className,以便下面进行区分。 for(var i=0;i<eles.length;i++){ var dirtax=eles[i].offsetLeft-xx; var dirtay=eles[i].offsetTop-yy; if( distance( dirtax,dirtay) <16 && eles[i].className!='tag'){//检测是否是自己 if(angle>=0&&angle<Math.PI){//在0 到180时变成大于180度角,并且break,不然进入下一条逻辑,又把角度变回来了! angle=Math.PI+angle; break; } if(angle>=Math.PI&&angle<Math.PI*2){// angle=angle-Math.PI; break; } } } } function draw(){//每一个球坐标获取以及绘制 var nextx=x + velocity*Math.cos(angle)/40;//(x,y) the current positon . (nextx,nexty) the next position var nexty=y - velocity*Math.sin(angle)/40; // for(var i=0;i<10000000;i++){} if(nextx>=STAGE_WIDTH-PARTICLE_RADIUS*2-13 ){ // nextx=nextx-10; //if angle is between 3 o'clock and 12 o'clock if (angle>=0&&angle<Math.PI/2) angle=Math.PI-angle; //if angle is between 6 o'clock and 3 o'clock if(angle>3*Math.PI/2&&angle<Math.PI*2) angle=3*Math.PI-angle; } if(nexty<0){//if angle is between 3 o'clock and 9 o'clock if((angle-0.08)<=0) { angle=0.1; } if(angle+0.08>= Math.PI){ angle=Math.PI-0.1 } angle=Math.PI*2-angle; } if(nextx<0){ if(angle>=Math.PI&&angle<Math.PI*1.5)//if angle is between 9 o'clock and 6 o'clock angle=3*Math.PI-angle; if(angle>Math.PI/2&&angle<Math.PI)//if angle is between 12 o'clock and 9 o'clock angle=Math.PI-angle; } if(nexty>STAGE_HEIGHT-PARTICLE_RADIUS*2 -13)//if angle is between 9 o'clock and 3 o'clock angle=Math.PI*2-angle; //refresh the postition of the particle node.style.left=nextx+'px'; node.style.top=nexty+'px'; node.className='tag'; x=nextx; y=nexty; } return {//作为闭包返回。 draw:draw, define:define } } var t=[]; document.body.onresize=function(){//调整窗口大小时重新绘制 //alert('1') // console.log('1') // var current=new Date().getTime(); var spanlist=document.getElementsByTagName('span'); while(spanlist.length!=0){document.body.removeChild(spanlist[0])} FRAME_TIMES=[];//重置 clearInterval(timer);//清除定时器 max=0; min=60; particles=[];//将存闭包的particles清空 init();//重绘开始 } })()