canvas动画包教不包会:边界与摩擦力
- 整个canvas元素
- 大于canvas的区域,比如有一张大地图,物体可以在里面任意移动,移到边界时地图也跟着移动变化
- 小于canvas的区域,比如给物体设置了一个小房间,限制它的活动范围
if(ball.x > canvas.width){
console.log('超出了右边界');
}else if(ball.x < 0){
console.log('超出了左边界');
};
if(ball.y > canvas.height){
console.log('超出了下边界');
}else if(ball.y < 0){
console.log('超出了上边界');
};
if( ball.x < x1 ){
console.log('物体超出了左边界');
}else if( ball.x > x2){
console.log('物体超出了右边界');
};
if( ball.y < y1 ){
console.log('物体超出了上边界');
}else if( ball.y > y2){
console.log('物体超出了下边界');
};
if( ball.x < (x1 - ball.radius)){
console.log('物体完全越出了左边界');
};
大多数情况下,我们不是单纯的检测物体是否越界,而是为了在物体越界后进行某些操作,当然,你也可以在物体越界后不做任何操作,不过这不是我们所推荐的。
当物体越界时,一般我们会进行以下4中选择操作:
- 移除物体
- 重置物体,也就是让物体所有状态恢复到原始位置
- 屏幕环绕:让同一个物体出现在边界内的另一个位置
- 物体反弹,也就是向反方向运动
1.2 移除物体
移除物体多用在多个物体在canvas上移动时,这时,我们一般将它们的引用保存到一个数组中,再通过遍历整个数组的来移动它们(前面的例子,我都是采取这种方式),这样,我们就可以使用 Array.splice 方法来移除数组中的某个物体了。
var balls = []; // 存放多个物体的数组
var ball = balls[i];
if(ball.x < x1 || ball.x > x2 || ball.y < y1 || ball.y > y2){
balls.splice(balls.indexof(ball),1);
i -= 1;
}
上面的检测越界条件是和检测在边界内的条件是不一样的。
注意:当你使用 Array.splice 方法在循环中移除元素后,需要加上 i -= 1,不然后续循环会出问题。当然,你也可以使用反向遍历,就不会存在这问题:
var i = balls.length;
while( i-- ){
if(ball.x < x1 || ball.x > x2 || ball.y < y1 || ball.y > y2){
balls.splice(balls.indexof(ball),1);
}
}
1.3 重置物体
重置物体其实就是重新设置物体的位置坐标。
在下面的例子,你会看到一个物体从上向下落下,当它离开canvas后,又有一个物体在同一个位置开始从上向下落下,看起来是不同的物体,其实是同一个,只不过每次它离开canvas后,都将它的 ball.y 设置为原始值,在这里是0。
1.4 屏幕环绕
屏幕环绕的意思是当物体从屏幕左边移出,它就会在屏幕右边再次出现;当物体从屏幕上方移出,它又会出现在屏幕下方,反之亦然。
屏幕环绕和重置物体类似,都遵循着同一个物体的原则,只不过屏幕环绕是让其从一边出再从相反的一边进而已。
1.5 反弹
在让物体反弹之前,你需要检测物体何时离开屏幕,当它刚要离开时,要保持它的位置不变而仅改变它的速度向量,也可以说是速度值取反。
在检测何时反弹时,有一点需要注意,我们不能等到物体完全移出canvas才开始反弹,这显然和现实不符合,不知道你有没有玩过足球,当你将足球踢向墙壁时,你会看到球在撞墙后,停在那里并很快反弹回来。
当物体移到如下图位置,物体就要开始反弹:
if( ball.x <= (x1 + ball.radius)){
ball.x = x1 + ball.width;
ball.speed.x *= -1;
}
在上面的代码中,我们将 -1 作为反弹系数,不过在现实中,反弹的速度总是会有所减小,这是因为能量损失,所以为了模拟更真实的动画,你可以将 -1 乘以一个百分比来实现能量损耗的效果。
ball.speed.x *= -0.8;
反弹的步骤如下:
- 检测物体是否越界
- 如果发生越界,立即将物体置回边界
- 反转物体的速度向量的方向,也可以说是速度取反。
简单例子:
2、摩擦力(friction)
摩擦力,又一物理概念,也可称为阻力,指两个互相接触的物体,当它们要发生或已经发生相对运动时,就会在接触面上产生一种阻碍相对运动的力。
上面是概念式的说法,简单的讲,摩擦力就是阻止你运动的力,它并不会改变你运动的方向,而只会让你慢慢减速,直至速度为0。
如果想让动画更加真实,很多时候我们都需要考虑摩擦力,那如何用代码实现呢?
(1)精确方法
上面也说到,摩擦力是阻止你运动的力,这就意味着,可以用速度向量减去摩擦力。更准确地说,只能沿着速度向量的方向减去与摩擦力相等的大小,而不能分别在x、y轴上减小速度向量,也可以这样理解,摩擦力必须与合速度相减,然后再根据减后的合速度分别求出x、y轴上的最终速度。
如下方式:
var v = Math.sqrt( vx * vx + vy * vy );
var angle = Math.atan2(vy,vx);
if(v > f){
v -= f;
}else{
v = 0;
};
vx = Math.cos(angle) * v;
vy = Math.sin(angle) * v;
(2)约等方法
约等方法是指将x、y轴上的速度向量乘以一个百分数,一个接近0.9的系数能很好的模拟出摩擦力的效果。
vx *= f;
vy *= f;
使用这种方法的好处就是不必去做条件判断,但它只能无限接近于0,不过由于JavaScript的精度约束,最后的结果也会变为0。
在上面的反弹中,反弹系数也是用这种方法。
总结
- 介绍了如何检测是否越界
- 越界后的处理方式:移除物体、重置物体、屏幕环绕、反弹
- 实现摩擦力的两种方法:精度方法、约等方法
附录
重要公式:
(1)检测是否越界
if( object.x - object.width / 2 > right ||
object.x + object.width / 2 < left ||
object.y - object.height / 2 > bottom ||
object.y + object.height / 2 < top){}
(2)摩擦力(精度方法)
var v = Math.sqrt( vx * vx + vy * vy );
var angle = Math.atan2(vy,vx);
if(v > f){
v -= f;
}else{
v = 0;
};
vx = Math.cos(angle) * v;
vy = Math.sin(angle) * v;
(3)摩擦力(约等方法)
vx *= friction;
vy *= friction;
下一章:移动物体