梦自在 2008-3-19 22:19
Making Things Move] 第五章 速度与加速度 [FL基理译]
[size=5][b]第五章 速度与加速度[/b][/size]
恭喜大家!至此已经到了真正的动作编程部分,这就意味着:(a) 您已经坚持学习了前面的所有章节;(b) 您感觉前面内容已经会过了,所以跳过前面的章节;(c) 您感觉无聊所以跳过了。但是不管怎么样,要记得如果在日后的学习中遇到了相关的问题,可以回到前面几章寻找答案。
本章以基本运动为基础:速度,向量以及加速度。今后所有的 ActionScript 动画,几乎都会用到这些概念。
[size=4][b]速度向量(Velocity)[/b][/size]
物体运动的最基本属性就是速度。很多人把速度向量(velocity)和速度(speed)等同,这是不对的,因为速度仅仅是速度向量的一部分,速度向量的概念还包括一个非常重要的因素:方向。速度向量的简单定义是:某个方向上的速度。
如果我开车从 X 位置出发,以每小时30英里的速度行驶一个小时,这时如果想找到我可就难了。但是,如果我说以同样的速度向南行驶一个小时,那么大家就可以知道我实际的位置了,这在动画中是非常重要的。
这就是引入速度向量的原因,如果知道物体在某一帧时的位置,那么只要知道它的运动速度和方向,就可以知道物体下一帧所在的位置了。
在介绍速度向量编码之前,先要为大家介绍一些向量的知识,同时为速度向量做一个简单的描述。
[size=4][b]向量与速度向量[/b][/size]
向量由长度和方向组成。在速度向量中,长度就是速度。向量用带有箭头的线段表示,箭头的长度就是向量的长度,箭头所指的方向就是向量的方向。图5-1是一些向量。
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_411d0e6567fc5bdcfb77hkAx2cRYSjdd.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342249¬humb=yes][b][color=#006699]5-1.jpg[/color][/b][/url] (4.66 KB)
2008-3-15 19:06
图5-1 几个向量
需要注意的是,长度总是正数,如果一个长度为负数的向量只表示该向量的反方向,见图解5-2。
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_aeebffab796366716b36nlSokHUaaNxN.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342250¬humb=yes][b][color=#006699]5-2ed.jpg[/color][/b][/url] (8.58 KB)
2008-3-15 19:06
图5-2 反速度向量为反方向的向量
还要注意向量没有起点,向量不能说明哪里是起点哪里是终点,它仅仅表示出了物体移动的速度与方向。因此,如果两个的方向及长度都相同,即使它们位于不同位置,那么它们仍是两个相等的向量,如图5-3所示。
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_3f71d484b64d2b711c6bGbz1XBvYRYfG.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342251¬humb=yes][b][color=#006699]5-3.jpg[/color][/b][/url] (3.69 KB)
2008-3-15 19:06
图5-3 如果向量的方向与长度相同,则它们相等,不考虑位置问题
[size=4][b]单轴速度[/b][/size]
首先,为了简化这个问题,把速度(向量)只放在一个轴上:x 轴(水平轴)。让物体向从屏幕的左侧到右侧——这样会比较符合习惯,移动速度就是物体每一帧移动的像素值。因此,如果说速度向量在 x 轴上为5,就意味着物体在每一帧都会右移动5个像素。同样,如果速度向量在 x 轴上为 -5,那么物体每一帧就会向左移动5个像素。
到现在为止大家都能跟上吗?我们刚刚提到了向量长度等于负值,科学地讲,速度向量实际上应该为5,而方向应为180度。同理, y 轴正半轴上的速度向量应为90度(垂直向下),而负 y 轴负半轴上的速度向量应为270或90度(垂直向上)。
事实上,当计算 x,y 速度向量的分量时,通常可以记作正数或负数,比如“ x 速度向量为 -5”。在 x 轴上把减号看成“向左”的指示符,在 y 轴上则是“向上”的指示符。在本书中,将用 vx 表示 x 轴的速度向量,用 vy 表示 y 轴的速度向量。 vx 为正数表示向右移动,为负数表示向左移动, vy 为正数表示向下, vy 为负数表示向上。
本章的许多例子都会让物体做出各种移动效果。为了不让每个例子都花时间绘制物体,我们下面创建一个 Ball 类,这样就可以经常重复使用它了。
package {
import flash.display.Sprite;
public class Ball extends Sprite {
public var radius:Number;
private var color:uint;
public function Ball(radius:Number=40,color:uint=0xff0000) {
this.radius=radius;
this.color=color;
init();
}
public function init():void {
graphics.beginFill(color);
graphics.drawCircle(0,0,radius);
graphics.endFill();
}
}
}
今后无论何时引用这个类,都要把这个类与工程放在一起,这样只需要使用 new Ball(size, color)就可以了创建一个小球了,或者可以把这个类放在一个特定的位置,并将其所在目录加入类路径中。
现在已经有了运动主体,以下是第一个速度向量的示例,文档类 Velocity1.as :
package {
import flash.display.Sprite;
import flash.events.Event;
public class Velocity1 extends Sprite {
private var ball:Ball;
private var vx:Number=5;
public function Velocity1() {
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 {
ball.x+= vx;
}
}
}
在这个例子中,首先设置一个 x 轴速度向量(vx)等于5。记住是指每一帧5像素,所以,在每一帧中, vx 都会被加到 ball 的 x 属性中。 init 方法将小球放到舞台上并为 enterFrame 设置事件处理函数。每走一帧,小球都会在前一帧的位置基础上向右移动5个像素。怎么样很不错吧,嗯?
给 vx 一个较大或较小的值,或者给个负数来试试,并观察一下物体运动的方向。
双轴上速度向量
使用两个轴对物体进行移动也非常简单,只需要定义 vx 和 vy,并在每一帧将 vx 加到 x 属性上, vy 加到 y 属性上。下面一个示例(Velocity2.as):
package {
import flash.display.Sprite;
import flash.events.Event;
public class Velocity2 extends Sprite {
private var ball:Ball;
private var vx:Number=5;
private var vy:Number=5;
public function Velocity2() {
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 {
ball.x+= vx;
ball.y+= vy;
}
}
}
大家可以试着改变一下速度变量,别忘了还有负数。
[size=4][b]角速度[/b][/size]
假如想让物体以每帧3像素的速度向45度的位置移动,在这个描述中我们看不到 vx,vy 的影子。但是,大家已经学习了使用 vx 和 vx 移动物体的例子,回忆一下第三章所讲的三角学,然后见图5-4所示,每一帧让小球以3像素的速度向45度角的位置移动。在这个图中加入一条边后,是不是与图5-5非常相似了?恩,这就是一个由已知角度与斜边构成的直角三角形!
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_1148bdfca6191a43d8c19m4ooFNdp5PE.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342252¬humb=yes][b][color=#006699]5-4ed.jpg[/color][/b][/url] (4.1 KB)
2008-3-15 19:06
图5-4 长度及方向
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_62cc1ead99339bf864d1rJYDx4Mv3jhg.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342253¬humb=yes][b][color=#006699]5-5ed.jpg[/color][/b][/url] (4.56 KB)
2008-3-15 19:06
图5-5 长度及方向形成一个直角三角形
请注意,这个三角形的两条位于 x,y 轴上的直角边。事实上, x 轴上的直角边长度等于小球所要移动的 x 距离, y 轴上的直角边等于 y 距离。不要忘记,在直角三角形中,只要知道一条边和一个角,就可以求出其它所有边和角的信息。因此,已知角度为45度,斜边长为3像素,就可以使用 Math.cos 和 Math.sin 求出 vx 和 vy 的长度。
角的邻边长度为 vx,因为角的余弦值等于邻边/斜边。也可以说,邻边等于角的余弦值乘以斜边。同样,对边长为 vy 的边,因为角的正弦值等于对边/斜边,或是对边等于正弦乘以斜边。实际使用的代码:
vx = Math.cos(angle) * speed;
vy = Math.sin(angle) * speed;
在使用 Math 函数之前你还敢忘记将45度角转换为弧度值吗!一旦获得了 vx 和 vy 的值,就可以将它们加到物体的 x,y 坐标上,这样就运动起来了。下面一个示例(VelocityAngle.as)代码如下:
package {
import flash.display.Sprite;
import flash.events.Event;
public class VelocityAngle extends Sprite {
private var ball:Ball;
private var angle:Number=45;
private var speed:Number=3;
public function VelocityAngle() {
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 {
var radians:Number=angle * Math.PI / 2;
var vx:Number=Math.cos(angle) * speed;
var vy:Number=Math.sin(angle) * speed;
ball.x+= vx;
ball.y+= vy;
}
}
}
与前面 vx,vy 主要不同的地方是变成了 angle 和 speed,计算出的速度向量作为局部变量被使用。当然,由于是一个简单的示例,角度(angle)和速度(speed)都不变,那么完全可以只计算一次,然后保存在类中作为变量。而对于更高级的运动来说,角度和速度会是应是不断变化的,所以 vx 和 vy 的值也是变化的。
只需要改变角度(angle)与速度(speed),就可以改变物体运动的速度及角度。下面,让从向量的角度审视一下这个例子。
[size=4][b]向量加法[/b][/size]
当在一个平面坐标中有两个向量时,使用向量加法可以求出两个向量的合成向量。合成向量是一条从第一个向量的起点连接到最后一个向量终点的向量。图5-6中,可以看到三个向量相加及一个合成向量。
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_d7141b2cf03854409a499NXooTor2qvi.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342254¬humb=yes][b][color=#006699]5-6ed.jpg[/color][/b][/url] (7.85 KB)
2008-3-15 19:06
图5-6 向量加法
结果与向量所在的位置无关。可以说物体是先沿着这条路前进,再沿那条路前进,然后再到另一条路,顺序可以任意选择,或者可以说物体在这三条路上都走过一次。只要给出速度及方向就可以让物体向终点移动。
在上一节例子中,如果将 x 轴的速度向量向右,再将 y 轴的速度向量竖直向下,那么合成向量就是全部向量的总合,见图5-7。
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_7514015b8b2e4a516b46kLvryFUlaS0N.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342255¬humb=yes][b][color=#006699]5-7.jpg[/color][/b][/url] (5.88 KB)
2008-3-15 19:06
图5-7 速度向量作为向量
梦自在 2008-3-19 22:21
[size=4][b]鼠标跟随[/b][/size]
让我们使用速度向量的概念解释一下早前的一个概念。回到第三章,我们曾使用一个箭头指向鼠标位置的例子,在这个示例中使用 Math.atan2 计算鼠标与箭头之间的夹角,并使箭头旋转到这个角度上。再根据刚才所学的知识,计算出当前角度的速度向量。这个示例中同样使用 Arrow 类,所以大家要把它找出来,然后与 FollowMouse.as 文档类放在同一目录下:
package {
import flash.display.Sprite;
import flash.events.Event;
public class FollowMouse extends Sprite {
private var arrow:Arrow;
private var speed:Number = 5;
public function FollowMouse() {
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 vx:Number = Math.cos(angle) * speed;
var vy:Number = Math.sin(angle) * speed;
arrow.x += vx;
arrow.y += vy;
}
}
}
这是一个相当复杂的效果,不过这里大家都能够看懂。先要计算出箭头与鼠标的 x 距离和 y 距离,并使用 Math.atan2 计算出它们的夹角。然后使用这个角度使箭头旋转,再使用 Math.cos 和 Math.sin 与速度相乘计算出 x,y 速度向量,最后将它们加到箭头的坐标上。
[size=4][b]速度向量扩展[/b][/size]
Sprite 影片,影片剪辑或任何的显示对象都有许多属性可以使用,而这些属性大多数都有比较大的取值范围,可以让我们制作出多种多样的动画。也许,速度向量一词用于这些属性上并不合适,但是在概念上是相同的,所以我也通常使用 v(velocity) 为变量命名的首字母。在一个影片旋转的例子中,通常在每一帧上将物体的 rotation 属性增加一些数值,只要加入更大的数值就可以让物体旋转得更快,反之就会更慢。不论正确与否,我通常将旋转速度变量命名为 vr,表示旋转速度。
同样使用 Arrow 影片,文档类 RotationalVelocity.as:
package {
import flash.display.Sprite;
import flash.events.Event;
public class RotationalVelocity extends Sprite {
private var arrow:Arrow;
private var vr:Number = 5;
public function RotationalVelocity() {
init();
}
private function init():void {
arrow = new Arrow();
addChild(arrow);
arrow.x = stage.stageWidth / 2;
arrow.y = stage.stageHeight / 2;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
arrow.rotation += vr;
}
}
}
速度向量值为5,方向为顺时针运动。同样的道理,可以将其它属性也加以改变,变量名仍使用 v 字系列,就像这样:
arrow.x += vx;
arrow.y += vy;
arrow.alpha += vAlpha;
arrow.rotation += vr;
arrow.scaleX = arrow.scaleY += vScale;
// etc.
在本书中看到很多这样的示例,所以希望大家原谅我经常使用 v 字母作开头,它的意思只是指它们是速度向量的“兄弟”。下面来学习加速度。
[size=4][b]加速度[/b][/size]
通常认为加速度就是使速度加快,减速度就是使速度减慢。没错,在本书中,为加速度作了一个更为科学的定义。
速度(向量)和加速度有很多相同之处,它们都是向量(或矢量)。速度(向量)和加速度(向量)都用量值(大小)和方向进行定义。然而,速度向量是改变物体位置的,而加速度是改变其速度向量的。
想象一下,坐在车上,然后启动车子,踩油门。什么是加速度?踩下油门以后(等同于加速),速度向量开始发生变化(速度开始加快,方向的变化于方向盘决定)。过一两秒后,速度将提升至每小时4到5英里,随后时速会变为10英里,20英里,30英里等等。发动机以其速度向量驱动汽车前进。
因此,加速度的通俗定义是:增加到物体速度向量上的力量。用 ActionScript 术语可以表示为,加速度就是一个增加到速度向量上的数值。
举一个例子,假如有一架火箭要从 A 星球飞到 B 星球。它的方向于 A 星球与 B 星球的位置决定,调整好方向后,开始点燃火箭。当火箭点燃后前进速度就会越来越快,当指挥官认为火箭的速度已经足够快了,为了保持燃料,就要让火箭慢下来。假设宇宙空间中不存在阻力,火箭以同样的速度继续飞行。当火箭不再点火时,就没有更多的力来驱动它了。因此,就失去了加速度,速度就不再发生变化。
当火箭接近目标时,就需要减慢速度。指挥官应该怎么做?不能使用刹车—也不可能抓住什么东西。这时,指挥官会让火箭转回去,就是朝相反的方向运动,并再次点火。这就使用了负加速度,或者说反方向的加速度。然后这股力量继续改变速度,但是这时是将速度减小,速度会越来越小,最终到达零。理想来说,这时火箭应该正处于星球地面几英寸的位置。
[size=4][b]单轴加速度[/b][/size]
让我们将前面学过的知识溶入到 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, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
private function onKeyDown(event:KeyboardEvent):void {
if (event.keyCode == Keyboard.LEFT) {
ax = -0.2;
} else if (event.keyCode == Keyboard.RIGHT) {
ax = 0.2;
}
}
private function onKeyUp(event:KeyboardEvent):void {
ax = 0;
}
private function onEnterFrame(event:Event):void {
vx += ax;
ball.x += vx;
}
}
}
在这个例子中,只需要检查是否按下了左右方向键。如果按下了左键,就为 ax 设置一个负值。如果按下了右键,则是正值,如果没有按下则设为零。在 onEnterFrame 方法中,将速度赋值到物体位置上。
测试这个影片,会发现,我们不能完全控制物体的速度。也就是说,不能立即将影片停止运动。如果将它的速度将得过低,就会向反方向运动。
[size=4][b]双轴加速度[/b][/size]
与速度向量一样,可以同时在 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,onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP,onKeyUp);
}
private function onKeyDown(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 onKeyUp(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 轴上的运动速度。
梦自在 2008-3-19 22:23
[size=4][b]重力加速度[/b][/size]
到目前为止,已经讨论了物体对其自己施加的力量为加速度,如汽车和火箭。对于任何通过加速度改变自身速度的力来说,还有很多种,例如重力,磁力,弹力,摩擦力等等。
观察重力(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度。
[size=4][b]角加速度[/b][/size]
我们说过,加速度由力和方向组成,在速度向量中的这两个要素需要分解为 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,让我们齐心协力,看看到底能够走多远吧。
[size=4][b]制作飞船[/b][/size]
接下来,制作一个模拟太空船的例子,计划如下。太空船专由一个类进行绘制,就像前面用过的 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所示,有点火的飞船和无点火的飞船。
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_1ee1df07aab79d80f764vxYM3TgUxzRO.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342256¬humb=yes][b][color=#006699]5-8.jpg[/color][/b][/url] (3.98 KB)
2008-3-15 19:13
[img]http://space.flash8.net/bbs/attachments/month_0803/20080315_a078a0ba16d6061b2579bXRQahNBLHWT.jpg[/img]
[img]http://space.flash8.net/bbs/images/attachicons/image.gif[/img]
[url=http://space.flash8.net/bbs/attachment.php?aid=342257¬humb=yes][b][color=#006699]5-9.jpg[/color][/b][/url] (4.22 KB)
2008-3-15 19:13
图5-8 视为太空船 图5-9 小心火焰
[size=4][b]飞船控制[/b][/size]
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;
addEventListener(Event.ENTER_FRAME,onEnterFrame);
stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP,onKeyUp);
}
private function onKeyDown(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 onKeyUp(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 onKeyUp(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;
}
测试后,让飞船飞行起来,你会惊喜地发现制作如此复杂的运动原来这么简单。如果大家使用代码绘制飞船的话,请不要忘记将背景色设置为黑色或其它深色,这样才能看出白色的线条。
[size=4][b]本章重要公式[/b][/size]
在我们的工具箱中又多出了不少工具,来看看吧。
[b]角速度转换为 x,y 速度向量:[/b]
vx = speed * Math.cos(angle);
vy = speed * Math.sin(angle);
[b]角加速度(作用于物体上的 force )转换为 x,y 加速度:[/b]
ax = force * Math.cos(angle);
ay = force * Math.sin(angle);
[b]将加速度加入速度向量:[/b]
vx += ax;
vy += ay;
[b]将速度向量加入坐标:[/b]
movieclip._x += vx;
sprite.y += vy;