5.2加速度

通常认为加速度就是使速度加快,减速度就是使速度减慢。没错,在本书中,为加速度作了一个更为科学的定义。

速度(向量)和加速度有很多相同之处,它们都是向量(或矢量)。 速度(向量)和加速度(向量)都用量值(大小)和方向进行定义。然而,速度是改变物体位置的,而加速度是改变其速度的。

想象一下,你坐在车上,然后启动车子,踩油门。什么是加速度?踩下油门以后(等同于加速),速度开始发生变化(速度开始加快,方向的变化由方向盘决定)。过一两秒后,速度将提升至每小时 4到 5 英里,随后时速会变为 10 英里,20 英里,30 英里等等。发动机以其速度向量驱动汽车前进。

因此,加速度的通俗定义是:改变对象速度的力。

用 ActionScript 术语可以表示为,加速度就是一个增加到速度上的数值。举一个例子,假如有一架火箭要从 A 星球飞到 B 星球。它的方向由A 星球与 B 星球的位置决定,调整好方向后,开始点燃火箭,当火箭点燃后前进速度就会越来越快,当指挥官认为火箭的速度已经足够快了,为了保持燃料,就要让火箭慢下来。假设宇宙空间中不存在阻力,火箭以同样的速度继续飞行。当火箭不再点火时,就没有更多的力来驱动它了。因此,就失去了加速度,速度就不再发生变化。

当火箭接近目标时,就需要减慢速度。指挥官应该怎么做?不能使用刹车,也不可能抓住什么东西。这时,指挥官会让火箭转回去,就是朝相反的方向运动,并再次点火。这就使用了负加速度,或者说反方向的加速度。然后这股力量继续改变速度,但是这时是将速度减小,速度会越来越小,最终到达零。理想来说,这时火箭应该正处于星球地面几英寸的位置。

5.2.1一个轴上的加速度

让我们将前面学过的知识溶入到 Flash 中进行一下实践。 与第一个速度向量示例相同,第一个加速度也只在一个轴上。回到 Ball 类中,下面是第一个示例的代码(Acceleration1.as):

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	
	public class Acceleration1 extends Sprite
	{
		private var ball:Ball;
		private var vx:Number = 0;
		private var ax:Number = .2;
		
		public function Acceleration1()
		{
			init();
		}
		
		private function init():void
		{
			ball = new Ball();
			addChild(ball);
			ball.x = 50;
			ball.y = 100;
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
		private function onEnterFrame(event:Event):void
		{
			vx += ax;
			ball.x += vx;
		}
	}
}

开始的速度向量(vx)为零,加速度(ax)为0.2,在每一帧中加速度都被加入到速度向量中,再加到小球的位置上。

测试一下这个示例,我们会看到小球开始移动得非常慢,而后就会非常快速地向右侧飞行,从而移动出舞台范围。

下面制作一个小球的示例,允许小球拥有加速度和反加速度。在这里使用方向键,我们已经学习过了侦听键盘事件的方法,也学过使用 keyCode 属性找到引发事件的事件对象,再调用事件处理函数。然后与 flash.ui.Keyboard 类中的常量做比较,这个类中包括适合用户读取的键码属性值,比如,Keyboard.LEFT, Keyboard.SPACE, Keyboard.SHIFT 等。目前,只关心左右方向键,Keyboard.LEFT 和 Keyboard.RIGHT,使用它们来改变加速度。这里是代码(Acceleration2.as):

package 
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;

	public class Acceleration2 extends Sprite
	{
		private var ball:Ball;
		private var vx:Number = 0;
		private var ax:Number = 0;
		public function Acceleration2()
		{
			init();
		}

		private function init():void
		{
			ball = new Ball();
			addChild(ball);
			ball.x = stage.stageWidth / 2;
			ball.y = stage.stageHeight / 2;
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDownHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUpHandler);
		}

		private function onKeyDownHandler(event:KeyboardEvent):void
		{
			if (event.keyCode == Keyboard.LEFT)
			{
				ax = -0.2;
			}
			else if (event.keyCode == Keyboard.RIGHT)
			{
				ax = 0.2;
			}
		}

		private function onKeyUpHandler(event:KeyboardEvent):void
		{
			ax = 0;
		}

		private function onEnterFrame(event:Event):void
		{
			vx +=  ax;
			ball.x +=  vx;
		}
	}
}

在这个例子中,只需要检查是否按下了左右方向键。如果按下了左键,就为 ax 设置一个负值。如果按下了右键,则是正值,如果没有按下则设为零。在 onEnterFrame 方法中,将速度赋值到物体位置上。

测试这个影片,会发现,我们不能完全控制物体的速度。也就是说,不能立即将影片停止运动。如果将它的速度将得过低,就会向反方向运动。

5.2.2两个轴上的加速度

与速度向量一样,可以同时在 x,y 轴使用加速度。只需要为每一个轴设置一个加速度(用 ax 和 ay 作为变量名),将它们加入 vx 和 vy, 再将 vx,vy 赋给 x,y 属性。在上一个例子中加入 y 轴非常简单,只需要加入:ay 和 vy 变量即可。

以下是代码(Accelertation3.as):

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;
	
	public class Acceleration3 extends Sprite
	{
		private var ball:Ball;
		private var vx:Number = 0;
		private var vy:Number = 0;
		private var ax:Number = 0
		private var ay:Number = 0
		
		public function Acceleration3()
		{
			init();
		}
		
		private function init():void
		{
			ball = new Ball();
			addChild(ball);
			ball.x = stage.stageWidth / 2;
			ball.y = stage.stageHeight / 2;
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDownHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUpHandler);
		}
		
		private function onKeyDownHandler(event:KeyboardEvent):void
		{
			switch(event.keyCode)
			{
				case Keyboard.LEFT :
				ax = -0.2;
				break;
				
				case Keyboard.RIGHT :
				ax = 0.2;
				break;
				
				case Keyboard.UP :
				ay = -0.2;
				break;
				
				case Keyboard.DOWN :
				ay = 0.2;
				break;
				
				default :
				break;
			}
		}
		
		private function onKeyUpHandler(event:KeyboardEvent):void
		{
			ax = 0;
			ay = 0;
		}
		
		private function onEnterFrame(event:Event):void
		{
			vx += ax;
			vy += ay;
			ball.x += vx;
			ball.y += vy;
		}
	}
}

请注意,本例中将上/下/左/右键的检查放到了 switch 语句中,这与 if 语句的功能相同。

这样就可以让小球在整个屏幕上移动了。试着让物体从左到右移动,然后按“上”键,注意这时 x 速度向量没有受到影响,物体依然保持在 x 轴上的运动速度。

5.2.3重力加速度

到目前为止,已经讨论了物体对其自己施加的力量为加速度,如汽车和火箭。对于任何通过加速度改变自身速度的力来说,还有很多种,例如重力,磁力,弹力,摩擦力等等。

观察重力(Gravity)有两种方法。一种是从太阳系这个宽度来看,重力就是两个天体之间的吸引力,这时必需要考虑两个天体间的角度和距离,才能计算出每个天体真正的加速度。

另一种观察重力的方法是从一个比较精确地视点上看, 发生在地球上。存在于现实生活中,在地球上,由物体间的距离决定重力的大小看起来是微乎其微的。虽然科学来讲,当我们在高空或高山上重力会减小一些,但这些变化几乎是感觉不到的。因此,在水平面上模拟重力时,几乎只使用一个固定的值,就像在前面例子中的加速度变量一样。同样也是因为,地球太大而人类太小了,这样一来实际的加速度方向就可以忽略不计了,只需要一个“向下”的力就可以了。换句话讲,无论物体在什么位置,我们都可以放心地在 y 轴上定义重力作为加速度。

放到 ActionScript 代码上来说, 只需要定义一个数值作为重力, 并在每一帧加入到 vy上,用一个分数就可以了,比如 0.5 或更小的数。如果用更大的数,物体就会显得太重了。如果用更小的数值,物体看起来会像飘浮的羽毛。当然,这个效果也是很实用的,例如通过不同的重力变化来模拟不同的星球。

下面这个例子加入了重力系统。完整代码在 Gravity.as ,在这里就不把它全部列出来了,仅与 Acceleration3.as 有一点点不同。除了类以外,还有构造函数名也要变换一下,请在最开始的变量列表中加入一个变量:

private var gravity:Number = 0.1;

在 onEnterFrame 方法中加入一句:

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

让重力值很小,是为了不让小球很快就离开屏幕,我们还可以使用方向键进行控制。前面所制作的就是一个古老的月球登陆者的游戏。再加入一些漂亮的图形和碰撞检测,就完成了!(后面会学到碰撞检测,而图形就要自己完成了)

回到向量加法,如果以初始向量为起点出发,作为一个向量,每个加速度,重力,或其它附加力都可以看作一个是添加到这个速度向量上的附加向量。把它们全部相加后,就绘制出了一条从起点到终点的连线,也就是合成向量,与加入到 x,y 的力是一样的。现在,想象一下有一个热汽球的影片,也许应该加入一个名为 lift(上升) 的力,这也是一个加在 y 轴上的加速度。不过,这次它是一个负数,表示“向上”。现在,这个物体上已经施加了三个力:方向键的力,重力和上升力。为了让汽球上升,则上升力要略高于重力,这样也是符合逻辑的——如果它们相等,则两种力会相互抵消掉,又回到了起点,这时只有方向键的力起作用。

还可以试一试风力,很明显,这是加在 x 轴上的力。取决于风吹去的方向,可以是正向力也可以是反向力,方向从 0 度到 180 度。

5.2.4角加速度

我们说过,加速度由力和方向组成,在速度向量中的这两个要素需要分解为 x,y 。如果大家加以留心,就会明白同样可以使用 Math.cos 和 Math.sin 的方法。代码如下:

var force:Number = 10; 
var angle:Number = 45; // degrees. Need to convert!
var ax:Number = Math.cos(angle * Math.PI / 180) * force;
var ay:Number = Math.sin(angle * Math.PI / 180) * force;

这样在每一个轴上都有了加速度,我们可以刷新每个轴上的速度向量,并更新为物体的位置。

继续使用本章的鼠标跟随示例,并使加速度应用在加速度上。回忆上次的示例,使用鼠标与箭头间的夹角决定 vx 和 vy。这一次,使用同样的方法,计算 ax 和 ay,然后将加速度值累加到速度向量中,再将速度向量赋给 x,y 属性。代码如下(FollowMouse2.as):

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	
	public class FollowMouse2 extends Sprite
	{
		private var arrow:Arrow;
		private var vx:Number = 0;
		private var vy:Number = 0;
		private var force:Number = 0.5;
		
		public function FollowMouse2()
		{
			init();
		}
		
		private function init():void
		{
			arrow = new Arrow();
			addChild(arrow);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
		private function onEnterFrame(event:Event):void
		{
			var dx:Number = mouseX - arrow.x;
			var dy:Number = mouseY - arrow.y;
			var angle:Number = Math.atan2(dy, dx);
			arrow.rotation = angle * 180 / Math.PI;
			var ax:Number = Math.cos(angle) * force;
			var ay:Number = Math.sin(angle) * force;
			vx += ax;
			vy += ay;
			arrow.x += vx;
			arrow.y += vy;
		}
	}
}

请注意,本例中将 speed 转为 force 并让它的值变得很小,是因为加速度是累加的,我们希望让它开始的时候小一些,这个值很快就会增大。同样注意 vx,vy 被声明为类的变量,可以由类的任意方法对其进行访问。早期它们都由每一帧重新进行计算,但是现在需要它们保存自身的数值,并且每次要进行自加或自减操作。当然,也可以不使用 ax 和 ay 变量,只需要将正弦和余弦的结果直接加在速度向量上就可以,之所以这么写是为了让代码看起来更清晰。

目前,这些代码不是很复杂,对吗?但是回顾一下本章开始时给大家的示例,就可以发现走了有多远。通过学习这些基本规则,可以制作出成百上千的动态的效果——有些动画是活灵活现的。这一章还没有完!

OK,让我们齐心协力,看看到底能够走多远吧。

5.2.5制作飞船

接下来,制作一个模拟太空船的例子,计划如下。太空船专由一个类进行绘制,就像前面用过的 Arrow 和 Ball 类一样。使用左右键控制飞船向左右旋转,上键用于点燃飞船。

当然,火箭是位于飞船尾部的。因此,火箭的力会使飞船在某个方向上进行加速运动。

首先,需要一架飞船,在一个类中使用绘图 API 代码绘制四条白色的线,作为飞船模型。如果大家比较有艺术天赋的话,可以用 PhotoShop 或 Swift 3D,制作一张位图,并使用嵌入技术将其嵌入,至于嵌入外部位图请见第四章。代码如下(Ship.as):

package
{
	import flash.display.Sprite;
	
	public class Ship extends Sprite
	{
		public function Ship()
		{
			draw(false);
		}
		
		public function draw(showFlame:Boolean):void
		{
			graphics.clear();
			graphics.lineStyle(1, 0xffffff);
			graphics.moveTo(10, 0);
			graphics.lineTo(-10, 10);
			graphics.lineTo(-5, 0);
			graphics.lineTo(-10, -10);
			graphics.lineTo(10, 0);
			
			if(showFlame)
			{
				graphics.moveTo(-7.5, -5)
				graphics.lineTo(-15, 0);
				graphics.lineTo(-7.5, 5);
			}
		}
	}
}

这是一个公共的 draw 方法,带有 true/false 两个值。这样,就得到了有点火和无点火的飞船,用于表示飞船起动和熄火。图 5-8和 5-9 所示,有点火的飞船和无点火的飞船。

图5-8

图 5-8 看到太空旅行的未来

图5-9

图 5-9 小心火焰

飞船控制

OK,飞船已经有了,接下来就要控制它了。刚才说过,要执行三种控制:左转,右转和点火,分别由左右上三个键控制。本例的代码与 Acceleration3.as 非常相似,用到事件处理函数 keyDown 和 keyUp 还有一个 switch 语句对按键进行分类处理。 先把所有代码给大家,随后再进行解释(ShipSim.as):

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.ui.Keyboard;
	
	public class ShipSim extends Sprite
	{
		private var ship:Ship;
		private var vr:Number = 0;
		private var thrust:Number = 0;
		private var vx:Number = 0;
		private var vy:Number = 0;
		public function ShipSim()
		{
			init();
		}
		
		private function init():void
		{
			ship = new Ship();
			addChild(ship);
			ship.x = stage.stageWidth / 2;
			ship.y = stage.stageHeight / 2;
			ship.draw(true);
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDownHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUpHandler);
		}
		
		private function onKeyDownHandler(event:KeyboardEvent):void
		{
			switch(event.keyCode)
			{
				case Keyboard.LEFT :
				vr = -5;
				break;
				
				case Keyboard.RIGHT :
				vr = 5;
				break;
				
				case Keyboard.UP :
				thrust = 0.2;
				ship.draw(true);
				break;
				
				default :
				break;
			}
		}
		
		private function onKeyUpHandler(event:KeyboardEvent):void
		{
			vr = 0;
			thrust = 0;
			ship.draw(false);
		}
		
		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;
			ship.x += vx;
			ship.y += vy;
		}
	}
}

首先定义 vr ,旋转速度向量,即飞船旋转的速度。初值为零,意思是没有旋转:

private var vr:Number = 0; 

在 onKeyDown 方法中,如果 switch 语句发现按下了左右方向键,就赋值 vr 为-5或 5。

case Keyboard.LEFT : 
    vr = -5;
    break; 
case Keyboard.RIGHT : 
    vr = 5; 
    break;

然后在 onEnterFrame 处理函数中,加入 vr 作为飞船的当前旋转方向,当按下一个键后,将 vr 重置为零。OK,以上就是旋转的问题。接下来,看一下 thrust(推力)。

声明 thurst 变量指明每一次所用的力。很明显,只有在点火以后火箭才具有加速度,所以在开始之前速度为零:

private var thrust:Number = 0;

然后在 switch 语句,如果持续按着上键应将 thrust 设为较小的数值,如 0.2。在用到推力(thrust)的时候,要绘制飞船的火焰:

case Keyboard.UP : 
    thrust = 0.2; 
    ship.draw(true); 
    break;

当按键释放后,设置 thrust 为零并消除火焰:

private function onKeyUpHandler(event:KeyboardEvent):void
{
    vr=0;
    thrust=0;
    ship.draw(false);
}

认真思考 onEnterFrame 函数后,就会发现 rotation 的角度值于确定推力的大小。将rotation 转换为弧度制并使用正余弦函数连同 thrust 一起,计算出每轴上的加速度:

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;
	ship.x += vx;
	ship.y += vy;
}

测试后,让飞船飞行起来,你会惊喜地发现制作如此复杂的运动原来这么简单。如果大家使用代码绘制飞船的话,请不要忘记将背景色设置为黑色或其它深色,这样才能看出白色的线条。

文件下载(已下载 2471 次)

发布时间:2011-3-16 10:23:28  阅读次数:7244

评论

由于网络审查方面的原因,本网站即日起关闭留言功能,若有什么问题可致信站长邮箱:fjphysics@qq.com。