【Canvas与艺术】模拟五个桌球在球桌上进行完全弹性碰撞
【关键点】
1.线性渐变色用于绘制台球桌绿面;
2.径向渐变色用于绘制立体感小球;
3.球与壁之间的碰撞检测及球与球之间的碰撞检测。
【成果图】
【代码】
<!DOCTYPE html> <html lang="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <head> <title>使用HTML5/Canvas模拟五个桌球在球桌上进行完全弹性碰撞</title> <style type="text/css"> .centerlize{ margin:0 auto; width:1200px; } </style> </head> <body onload="init();"> <div class="centerlize"> <canvas id="myCanvas" width="12px" height="12px" style="border:1px dotted black;"> 如果看到这段文字说您的浏览器尚不支持HTML5 Canvas,请更换浏览器再试. </canvas> </div> </body> </html> <script type="text/javascript"> <!-- /***************************************************************** * 将全体代码(从<!DOCTYPE到script>)拷贝下来,粘贴到文本编辑器中, * 另存为.html文件,再用chrome浏览器打开,就能看到实现效果。 ******************************************************************/ // canvas的绘图环境 var ctx; // 高宽 const WIDTH=875; const HEIGHT=485; // 舞台对象 var stage; //------------------------------- // 初始化 //------------------------------- function init(){ // 获得canvas对象 var canvas=document.getElementById('myCanvas'); canvas.width=WIDTH; canvas.height=HEIGHT; // 初始化canvas的绘图环境 ctx=canvas.getContext('2d'); ctx.translate(WIDTH/2,HEIGHT/2);// 原点平移到画布中央 // 准备 stage=new Stage(); stage.init(); // 开幕 animate(); } // 播放动画 function animate(){ stage.update(); stage.paintBg(ctx); stage.paintFg(ctx); // 循环 if(true){ //sleep(100); window.requestAnimationFrame(animate); } } // 舞台类 function Stage(){ this.balls=[]; // 初始化 this.init=function(){ this.balls=new Array(); var rebBall={"x":0,"y":0,"vx":0.8,"vy":0.1,"r":10,"color":"red"}; this.balls.push(rebBall); var blueBall={"x":-100,"y":100,"vx":0.5,"vy":0.5,"r":10,"color":"blue"}; this.balls.push(blueBall); var greenBall={"x":-100,"y":-100,"vx":0.3,"vy":0.9,"r":10,"color":"green"}; this.balls.push(greenBall); var blackBall={"x":-200,"y":-150,"vx":1.3,"vy":3.9,"r":10,"color":"black"}; this.balls.push(blackBall); var purpleBall={"x":200,"y":150,"vx":5.3,"vy":0.9,"r":10,"color":"purple"}; this.balls.push(purpleBall); } // 更新 this.update=function(){ for(var i=0;i<this.balls.length;i++){ var ball=this.balls[i]; // 球与上下壁的碰撞检测 if(ball.y-ball.r<=-224 || ball.y+ball.r>=224){ ball.vy*=-1; } // 球与左右壁的碰撞检测 if(ball.x-ball.r<=-419 || ball.x+ball.r>=419){ ball.vx*=-1; } // 小球之前的碰撞检测 for(var j=0;j<this.balls.length;j++){ if(i!=j){ var other=this.balls[j]; var distance=Math.sqrt( Math.pow(ball.x-other.x,2)+Math.pow(ball.y-other.y,2)); if(distance<=ball.r+other.r){ // 两个小球碰撞后交换横向和纵向速度,不考虑发生旋转 var vx=ball.vx; ball.vx=other.vx; other.vx=vx; var vy=ball.vy; ball.vy=other.vy; other.vy=vy; } } } // 球的运动,不考虑摩擦损耗 ball.x+=ball.vx; ball.y+=ball.vy; } } // 画背景 this.paintBg=function(ctx){ ctx.clearRect(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT);// 清屏 // 画桌球面板外轮廓 drawRoundRect(ctx,0,0,871,481,16); ctx.fillStyle="rgb(255,215,0)"; ctx.fill(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); // 画桌球面板内轮廓 drawRoundRect(ctx,0,0,838,448,8); var lgrd=ctx.createLinearGradient(-419,224,419,224); lgrd.addColorStop(0,"rgb(69,149,2)"); lgrd.addColorStop(1,"rgb(62,108,0)"); ctx.fillStyle=lgrd; ctx.fill(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); // 画六个扶手 // 上左 ctx.beginPath(); ctx.moveTo(-400,-240); ctx.lineTo(-20,-240); ctx.lineTo(-20,-224); ctx.lineTo(-400,-224); ctx.closePath(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); ctx.fillStyle="rgb(109,72,2)"; ctx.fill(); // 上右 ctx.beginPath(); ctx.moveTo(20,-240); ctx.lineTo(400,-240); ctx.lineTo(400,-224); ctx.lineTo(20,-224); ctx.closePath(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); ctx.fillStyle="rgb(109,72,2)"; ctx.fill(); // 下左 ctx.beginPath(); ctx.moveTo(-400,224); ctx.lineTo(-20,224); ctx.lineTo(-20,240); ctx.lineTo(-400,240); ctx.closePath(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); ctx.fillStyle="rgb(109,72,2)"; ctx.fill(); // 下右 ctx.beginPath(); ctx.moveTo(20,224); ctx.lineTo(400,224); ctx.lineTo(400,240); ctx.lineTo(20,240); ctx.closePath(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); ctx.fillStyle="rgb(109,72,2)"; ctx.fill(); // 左 ctx.beginPath(); ctx.moveTo(-435,-206); ctx.lineTo(-419,-206); ctx.lineTo(-419,204); ctx.lineTo(-435,204); ctx.closePath(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); ctx.fillStyle="rgb(109,72,2)"; ctx.fill(); //ctx.fillStyle="rgb(109,72,2)";// 右 //ctx.fillRect(419.5,-206,16,410); ctx.beginPath(); ctx.moveTo(419.5,-206); ctx.lineTo(435.5,-206); ctx.lineTo(435.5,204); ctx.lineTo(419.5,204); ctx.closePath(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); ctx.fillStyle="rgb(109,72,2)"; ctx.fill(); // 罚球线 ctx.beginPath(); ctx.moveTo(228,-224); ctx.lineTo(228,224); ctx.lineWidth=0.5; ctx.strokeStyle="white"; ctx.stroke(); // 罚球圆 ctx.beginPath(); ctx.arc(228,0,100,-Math.PI/2,Math.PI/2,false); ctx.lineWidth=0.5; ctx.strokeStyle="white"; ctx.stroke(); // 上罚球点 ctx.beginPath(); ctx.arc(228,-100,2,0,Math.PI*2,false); ctx.closePath(); ctx.fillStyle="white"; ctx.fill(); // 中罚球点 ctx.beginPath(); ctx.arc(228,0,2,0,Math.PI*2,false); ctx.closePath(); ctx.fillStyle="white"; ctx.fill(); // 下罚球点 ctx.beginPath(); ctx.arc(228,+100,2,0,Math.PI*2,false); ctx.closePath(); ctx.fillStyle="white"; ctx.fill(); // 画三个模拟小球 //drawColorBall(ctx,0,0,10,"red"); //drawColorBall(ctx,-100,0,10,"yellow"); //drawColorBall(ctx,100,0,10,"blue"); writeText(ctx,WIDTH/2-60,HEIGHT/2-6,"逆火原创","8px consolas","white");// 版权 } // 画前景 this.paintFg=function(ctx){ // 这里就是在球的当前位置把球画出来即可 for(var i=0;i<this.balls.length;i++){ var ball=this.balls[i]; drawColorBall(ctx,ball.x,ball.y,ball.r,ball.color); } } } /*---------------------------------------------------------- 函数:用于绘制立体感彩色球 ctx:绘图上下文 x:球中心横坐标 y:球中心纵坐标 r:球半径 color:颜色 ----------------------------------------------------------*/ function drawColorBall(ctx,x,y,r,color){ ctx.beginPath(); ctx.arc(x,y,r,0,Math.PI*2,false); ctx.closePath(); var gnt=ctx.createRadialGradient(x+r/2,y-r/2,r/60,x,y,r); gnt.addColorStop(0,"white"); gnt.addColorStop(0.9,color); gnt.addColorStop(1,"rgba(0,0,0,0)"); ctx.fillStyle=gnt; ctx.fill(); } /*---------------------------------------------------------- 函数:用于绘制圆角矩形 ctx:绘图上下文 x:矩形中心横坐标 y:矩形中心纵坐标 width:矩形宽 height:矩形高 radius:圆角半径 ----------------------------------------------------------*/ function drawRoundRect(ctx,x,y,width,height,radius){ ctx.beginPath(); ctx.moveTo(x-width/2+radius,y-height/2); ctx.lineTo(x+width/2-radius,y-height/2); ctx.arcTo(x+width/2,y-height/2,x+width/2,y-height/2+radius,radius); ctx.lineTo(x+width/2,y-height/2+radius); ctx.lineTo(x+width/2,y+height/2-radius); ctx.arcTo(x+width/2,y+height/2,x+width/2-radius,y+height/2,radius); ctx.lineTo(x+width/2-radius,y+height/2); ctx.lineTo(x-width/2+radius,y+height/2); ctx.arcTo(x-width/2,y+height/2,x-width/2,y+height/2-radius,radius); ctx.lineTo(x-width/2,y+height/2-radius); ctx.lineTo(x-width/2,y-height/2+radius); ctx.arcTo(x-width/2,y-height/2,x-width/2+radius,y-height/2,radius); ctx.closePath(); } /*---------------------------------------------------------- 函数:书写文字 ctx:绘图上下文 x:横坐标 y:纵坐标 text:文字 font:字体 color:颜色 ----------------------------------------------------------*/ function writeText(ctx,x,y,text,font,color){ ctx.save(); ctx.textBaseline="bottom"; ctx.textAlign="center"; ctx.font = font; ctx.fillStyle=color; ctx.fillText(text,x,y); ctx.restore(); } /*---------------------------------------------------------- 函数:创建一个二维坐标点 x:横坐标 y:纵坐标 Pt即Point ----------------------------------------------------------*/ function createPt(x,y){ var retval={}; retval.x=x; retval.y=y; return retval; } /*---------------------------------------------------------- 函数:延时若干毫秒 milliseconds:毫秒数 ----------------------------------------------------------*/ function sleep(milliSeconds) { const date = Date.now(); let currDate = null; while (currDate - date < milliSeconds) { currDate = Date.now(); } } /*------------------------------------------------------------- 刘公岛北洋海军博物馆最佳留言:甲午颓风依旧,莫笑北洋当年。 --------------------------------------------------------------*/ //--> </script>
END