桌面上有多个球在同时运动,怎么实现球之间不交叉,即碰撞?

编程技术  /  houtizong 发布于 3年前   145
稍微想了一下,然后解决了很多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中点时,擦除重绘。
完整代码如下
cssbody{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的存储时间的数组长度为30FRAME_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.pushvar 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,不然进入下一条逻辑,又把角度变回来了!这相当于两种情况只执行一种,用if else即可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 positionvar 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 particlenode.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();//重绘开始}})()

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!

留言需要登陆哦

技术博客集 - 网站简介:
前后端技术:
后端基于Hyperf2.1框架开发,前端使用Bootstrap可视化布局系统生成

网站主要作用:
1.编程技术分享及讨论交流,内置聊天系统;
2.测试交流框架问题,比如:Hyperf、Laravel、TP、beego;
3.本站数据是基于大数据采集等爬虫技术为基础助力分享知识,如有侵权请发邮件到站长邮箱,站长会尽快处理;
4.站长邮箱:[email protected];

      订阅博客周刊 去订阅

文章归档

文章标签

友情链接

Auther ·HouTiZong
侯体宗的博客
© 2020 zongscan.com
版权所有ICP证 : 粤ICP备20027696号
PHP交流群 也可以扫右边的二维码
侯体宗的博客