## 边界与摩擦力
在前面的几章中,我们已经介绍了如何实现用户交互、利用三角函数实现物体旋转、给物体加上速度和加速度,利用这些知识,我们已经能够实现很丰富的动画效果,不过,似乎动画还不够真实,比如:物体可以无限的向左或向右移动、运动没有阻力,这就是这一章要处理的问题:边界和摩擦力。
**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元素内,怎么做呢?一般以两个点作为范围点,看下图:
![](https://box.kancloud.cn/7d8c431ed1024e6ebd42900de1710f11_359x222.jpg)
如何检测物体在这个范围内,代码如下:
```
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('物体超出了下边界');
};
```
当然,上面这段代码是检测物体的中心点是否越界,如果要检测是否完全越界,就需要加或减物体的高宽了:比如检测物体是否完全越出了左边界:
![](https://box.kancloud.cn/f59aa45355ce7c43a09c18f426548c8e_365x222.jpg)
```
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。
实例:canvas-demo/animationResetObject.html
**1.4 屏幕环绕**
屏幕环绕的意思是当物体从屏幕左边移出,它就会在屏幕右边再次出现;当物体从屏幕上方移出,它又会出现在屏幕下方,反之亦然。
屏幕环绕和重置物体类似,都遵循着同一个物体的原则,只不过屏幕环绕是让其从一边出再从相反的一边进而已。
**1.5 反弹**
在让物体反弹之前,你需要检测物体何时离开屏幕,当它刚要离开时,要保持它的位置不变而仅改变它的速度向量,也可以说是速度值取反。
在检测何时反弹时,有一点需要注意,我们不能等到物体完全移出canvas才开始反弹,这显然和现实不符合,不知道你有没有玩过足球,当你将足球踢向墙壁时,你会看到球在撞墙后,停在那里并很快反弹回来。
当物体移到如下图位置,物体就要开始反弹:
![](https://box.kancloud.cn/c8985f501f10399d6da264897832ee1b_365x222.jpg)
```
if( ball.x <= (x1 + ball.radius)){
ball.x = x1 + ball.width;
ball.speed.x *= -1;
}
```
在上面的代码中,我们将 -1 作为反弹系数,不过在现实中,反弹的速度总是会有所减小,这是因为能量损失,所以为了模拟更真实的动画,你可以将 -1 乘以一个百分比来实现能量损耗的效果。
```
ball.speed.x *= -0.8;
```
反弹的步骤如下:
- 检测物体是否越界
- 如果发生越界,立即将物体置回边界
- 反转物体的速度向量的方向,也可以说是速度取反。
实例: canvas-demo/oneDirection.html
**2、摩擦力(friction)**
摩擦力,又一物理概念,也可称为阻力,指两个互相接触的物体,当它们要发生或已经发生相对运动时,就会在接触面上产生一种阻碍相对运动的力。
上面是概念式的说法,简单的讲,摩擦力就是阻止你运动的力,它并不会改变你运动的方向,而只会让你慢慢减速,直至速度为0。
如果想让动画更加真实,很多时候我们都需要考虑摩擦力,那如何用代码实现呢?
**(1)精确方法**
上面也说到,摩擦力是阻止你运动的力,这就意味着,可以用速度向量减去摩擦力。更准确地说,只能沿着速度向量的方向减去与摩擦力相等的大小,而不能分别在x、y轴上减小速度向量,也可以这样理解,摩擦力必须与合速度相减,然后再根据减后的合速度分别求出x、y轴上的最终速度。
![](https://box.kancloud.cn/e6345a1590dab43dcc5657ce3b20bb49_232x133.jpg)
如下方式:
```
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;
```