6.2 摩擦力

假设有一张纸,将它撕碎后用力丢向空中。纸片会受到重力向下的牵引(y 轴),当我们松手后,纸片的 x 轴起初运动得非常快,但很快 x 轴的运动速度又归为零。

很显然,这里面没有负的加速度,但是纸片的速度向量却发生了改变,这就是摩擦力,阻力或阻尼。虽然它不是一种严格意义上的力,但作用是相同的,因为它改变了物体的速度。原理是,摩擦力只改变速度向量中的速度,而不会改变运动的方向。

那么如何使用代码来实现摩擦力呢?这里有两种方法。就像生活中很多事情一样,有一种正确的方法和一种简易的方法。我们会分别讲述这两种方法,首先从“正确”的方法开始吧。

6.2.1 摩擦力,正确的方法

摩擦力是与速度向量相反的力,假设有一个摩擦力的数值,就可以将它从速度向量中减去。事实上,是从速度向量的量值或速度中减去,不能只是简单地从 x,y 轴上减去。这样做的话,如果物体沿着一定角度运动,其中的一个分速度会提前到达零,使得物体继续垂直或水平地运动一会儿,结果看起来非常奇怪。

所以,我们要做的就是根据速度和方向找出角速度。使用 vx 和 vy 的平方和开平方后求出速度(是的,这就是勾股定理,第三章的内容)。再使用, Math.atan2(vy, vx) 求出角度,代码如下:

var speed:Number = Math.sqrt(vx * vx + vy * vy);
var angle:Number = Math.atan2(vy, vx); 

然后就可以从速度向量中减去速度。如果摩擦力大于速度,速度就变为零,计算代码如下:

if (speed > friction)
{
    speed -= friction;
}
else
{
    speed = 0;
}

这样一来,还需要使用正弦和余弦将角速度转换回 vx 和 vy,如下:

vx = Math.cos(angle) * speed;
vy = Math.sin(angle) * speed; 

工作量不小吧?这里是全部文档类的内容 Friction1.as:

package
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	
	public class Friction1 extends Sprite
	{
		private var ball:Ball;
		private var vx:Number = 0;
		private var vy:Number = 0;
		private var friction:Number = 0.1;
		
		public function Friction1()
		{
			init();
		}
		
		private function init():void
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align=StageAlign.TOP_LEFT;

			ball = new Ball();
			ball.x = stage.stageWidth / 2;
			ball.y = stage.stageHeight / 2;
			vx = Math.random() * 10 - 5;
			vy = Math.random() * 10 - 5;
			addChild(ball);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
		
		private function onEnterFrame(event:Event):void
		{
		  var speed:Number = Math.sqrt(vx * vx + vy * vy);
		  var angle:Number = Math.atan2(vy, vx);
		  if (speed > friction)
		  {
				speed -= friction;
		  }
		  else
		  {
				speed = 0;
		  }
		  vx = Math.cos(angle) * speed;
		  vy = Math.sin(angle) * speed;
		  ball.x += vx;
		  ball.y += vy;
		}
	}
}

这就是反比例速度向量的代码。摩擦力设置为0.1,然后给小球一个随机的 x,y 速度向量。 在 onEnterFrame 方法中,speed 和 angle 的计算方法同前面所介绍的。 如果 speed 小于 friction 则相减,否则让 speed 等于零。然后,重新计算 vx 和 vy ,最后将它们加入到坐标上。

总共用了十几行代码和四个三角函数才完成,大家也许能提出更好的写法,但却不能简化这个计算过程。我想你一定同意去看看那个简便的方法吧。

6.2.2 摩擦力,简便方法

大家也许都猜到了,简便的方法不像前面的方法那样精确,但是我敢打赌不会有人会注意到这些细微的变化。只用两行简单的乘法即可搞定,我们所要做的就是用摩擦力乘以 x,y 速度向量,摩擦力常用的值大约为 0.9 或 0.8。因此,在每一帧, vx 和 vy 的值都将变为上一次的 80% 或 90%。理论上,速度向量会无限接近零,但永远不会等于零。在实际应用中,计算机计算如此小的数字的能力是有限的,所以最终都会取整为零。

这种方法最好的一点是速度向量永远不会变为负数, 所以不需要进行判断。同样, x,y 的速度向量也是同比率变化的,所以不需要再将进行繁琐的转换。

只需要将前面例子中的 firction 变量设为 0.9,然后按如下代码改变 onEnterFrame 方法(可在文档类 Friction2.as 中找到):

private function onEnterFrame(event:Event):void
{
    vx *= friction;
    vy *= friction; 
    ball.x += vx;
    ball.y += vy;
}

的确简便了不少吧!测试几次,能看出与之前的不同吗?平心而论,这个效果看起来更真实。

6.2.3 摩擦力应用

让我们回到熟悉的飞船上,现在让宇宙空间具有摩擦力。在 ShipSim2.as 类中加入friction 变量:

private var friction:Number = 0.97; 

其它的类变量继续延用,还要改变 onEnterFrame 方法,如下(ShipSimFriction.as):

private function onEnterFrame(event:Event):void
{
	ship.rotation += vr;
	var angle:Number = ship.rotation * Math.PI / 180;
	var ax:Number = Math.cos(angle) * thrust;
	var ay:Number = Math.sin(angle) * thrust;
	vx += ax;
	vy += ay;
	vx *= friction;
	vy *= friction;
	ship.x += vx;
	ship.y += vy;
	var left:Number = 0;
	var right:Number = stage.stageWidth;
	var top:Number = 0;
	var bottom:Number = stage.stageHeight;
	if (ship.x - ship.width / 2 > right)
	{
		ship.x = left - ship.width / 2;
	}
	else if (ship.x + ship.width / 2 < left)
	{
		ship.x = right + ship.width / 2;
	}
	if (ship.y - ship.height / 2 > bottom)
	{
		ship.y = top - ship.height / 2;
	}
	else if (ship.y < top - ship.height / 2)
	{
		ship.y = bottom + ship.height / 2;
	}
}

只加了三行代码,感觉就不一样了。

任何使用速度向量的地方都可以加入摩擦力。比如在物体的旋转上(变量 vr )应用摩擦力,会使旋转的速度慢下来直至停止。大家可以在第五章的旋转箭头中试验一下。

这个手段可以应用在所有的物体上,比如轮盘,电风扇或飞船推进器。

文件下载(已下载 2317 次)

发布时间:2011/6/27 上午9:34:51  阅读次数:7662

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号