边界与摩擦力

最后更新于:2022-04-02 01:28:28

## 边界与摩擦力 在前面的几章中,我们已经介绍了如何实现用户交互、利用三角函数实现物体旋转、给物体加上速度和加速度,利用这些知识,我们已经能够实现很丰富的动画效果,不过,似乎动画还不够真实,比如:物体可以无限的向左或向右移动、运动没有阻力,这就是这一章要处理的问题:边界和摩擦力。 **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://docs.gechiui.com/gc-content/uploads/sites/kancloud/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://docs.gechiui.com/gc-content/uploads/sites/kancloud/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://docs.gechiui.com/gc-content/uploads/sites/kancloud/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://docs.gechiui.com/gc-content/uploads/sites/kancloud/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; ```
';