Canvas做游戏实践分享(九)
反弹
反弹的处理原理很简单,在运动对象碰到边界后,我们将其放置到与边缘紧貼的位置,之后将其方向反向即可(因为边界是水平或竖直的,我们只需要考虑一个方向上的分速度反向,如果碰撞面是倾斜的,就需要进行坐标系统的旋转,这个之后会专门介绍)。
仍然拿小球系统来分析,只需要在每一帧绘制小球前执行以下代码即可:
if (ball.x + ball.radius > right) { ball.x = right - ball.radius; vx *=bounce; } else if (ball.x - ball.radius < left) { ball.x = left + ball.radius; vx *=bounce; } if (ball.y + ball.radius > bottom) { ball.y = bottom - ball.radius; vy *= bounce; } else if (ball.y - ball.radius < top) { ball.y = top + ball.radius; vy *= bounce; }
其中的bounce是一个常量,如果是完全弹性碰撞,则bounce=-1。如果在反弹时有损耗,可以将其值根据实际情况进行调整。此处我们的碰撞处理是最简单的处理方式,如果运动物体的速度非常快,那其效果会与我们预想的有一些偏差,如下面两幅图的对比就可以看出差异。
左图是我们使用程序处理的结果示意
左图是小球在实际物理世界中的运行示意
5.2 摩擦力
在实际环境中,运动对象在运动时会有摩擦力阻止其运动。对于摩擦力的处理有两种方式,下面仍以小球系统为例分别介绍。
理论方法
摩擦力是用来减少对象速度的力。所以,其方向是与速度方向相反的。按前面介绍的速度与三角函数方面的理论,我们可以得到速度为:
var speed=Math.sqrt(ball.vx*ball.vx+ball.vy*ball.vy); var angle=Math.atan2(ball.vy,ball.vx);
接下来,我们就可以从速度中减去摩擦力产生的速度减少常数值。但要注意,在速度减少到已经小于摩擦力产生的速度减少常数时,我们就不需要再给速度减去摩擦力产生的速度常数了,否则就会产生反向的速度,此时只需要将速度置零即可。
if(speed>friction){ speed-=friction; }else{ speed=0; } ball.vx=speed*Math.cos(angle); ball.vy=speed*Math.sin(angle);
上述代码中的friction为摩擦力产生的速度减少常数。我们只需要将上述代码放入帧函数animationLoop中小球绘制前,就可以为系统添加摩擦力处理了。
简易方案
从前面的理论处理方法,我们可以看到,一个确定的系统,我们只需要给正交分解后的各个坐标轴上分速度持续地乘以一个速度衰减常数(0到1之间),这样速度会逐渐减少到无限接近0。精确到一定程度,速度已经小到用户无法分辨。
具体实现时,只需要将如下两行代码放入帧函数animationLoop中小球绘制前即可:
ball.vx*=friction; ball.vy*=friction;
有时候,为了优化系统性能。我们可以对当前的速度进行判断,如果小于一个指定常数,则不进行速度处理,如:
if(Math.abs(ball.vx)>0.0001){ ball.vx*=friction; }
5.2.3 实例对比
我们对之前的两种方式,在一个系统中进行对比。代码如下:
window.onload=function(){ var canvas = document.getElementById('canvas'); var context = canvas.getContext("2d"); //使用理论方式运算 var friction=0.01; //使用简易方式计算 var friction2=0.992; //实例化并设置初始值 var ball=new Ball(); var ball2=new Ball(); ball2.color='#00ff00'; ball.x=ball2.x=canvas.width/2; ball.y=ball2.y=canvas.height/2; ball.vx=ball2.vx=Math.random()*10-5; ball.vy=ball2.vy=Math.random()*10-5; (function drawFrame(){ window.requestAnimFrame(drawFrame,canvas); context.clearRect(0,0,canvas.width,canvas.height); //理论方式处理第一个小球,先球合速度,再减去摩擦速度,再分解小球分速度与位置 var speed=Math.sqrt(ball.vx*ball.vx+ball.vy*ball.vy); var angle=Math.atan2(ball.vy,ball.vx); if(speed>friction){ speed-=friction; }else{ speed=0; } ball.vx=Math.cos(angle)*speed; ball.vy=Math.sin(angle)*speed; ball.x+=ball.vx; ball.y+=ball.vy; ball.draw(context); //直接乘以摩擦常数 ball2.vx*=friction2; ball2.vy*=friction2; ball2.x+=ball2.vx; ball2.y+=ball2.vy; ball2.draw(context); })(); };