首页 专题 H5案例 前端导航 UI框架

canvas动画包教不包会:边界与摩擦力

作者:TG 日期: 2016-11-05 字数: 10641 阅读: 6088
在前面的几章中,我们已经介绍了如何实现用户交互、利用三角函数实现物体旋转、给物体加上速度和加速度,利用这些知识,我们已经能够实现很丰富的动画效果,不过,似乎动画还不够真实,比如:物体可以无限的向左或向右移动、运动没有阻力,这就是这一章要处理的问题:边界和摩擦力。

1、边界
在一个游戏中,很少会让物体可以无限的向左或向右的移动,这就出现了“活动范围”这一词,我们在这里称为“边界”,边界也可以认为是我们用墙将物体围住,限制它的移动位置。

在canvas中,我们一般会设置三种边界:
  • 整个canvas元素
  • 大于canvas的区域,比如有一张大地图,物体可以在里面任意移动,移到边界时地图也跟着移动变化
  • 小于canvas的区域,比如给物体设置了一个小房间,限制它的活动范围

1.1 设置边界
设置边界其实也是一种碰撞检测,只不过物体的碰撞对象变成了边界。

当我们将整个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('超出了上边界');

};

这里为什么是 ball.y < 0 是超出了上边界,请回想一下canvas的坐标是怎样的。

那么,如果不是基于整个canvas元素内,怎么做呢?一般以两个点作为范围点,看下图:

如何检测物体在这个范围内,代码如下:

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;


下一章:移动物体


目录