什么是三角学(Trigonometry)
三角学是一门研究三角形与其边和角关系的学科。当我们观察一个三角形时,发现它有三条边和三个角(因此称为三角),而且在这些边和角之间存在着一些特殊的
关系。例如,增大其中的任何一个角,那么该角所对应的边就会增长(假设其它两条边长度不变),同时,其它两个角会变小,实际上,究竟它们变化了多少,加以
计算后就可以得出一个比例。在一个三角形中,如果其中有一个角为90度,那么就称为直角三角形,并在该角的夹角处标出一个正方形(垂足),只有在直角三角
形才会这样。学习直角三角形中存在的关系要比推导基本公式简单得多,这使得直角三角形成为一种非常有用的结构,本章及该书后面的内容大多都是直角三角形。
角(Angle)
角是三角学最主要的研究对象,让我们先来解决这个问题。角是由两条相交线构成的图形,或是两条相交线之间的那部分空间,空间远大,夹角越大。事实上,两条相交的线会形成四个角,见图 3-1:
图3-1 两条线形成四个角
弧度制(radian)与角度制(degress)
弧度制与角度制是角度测量中的两种特殊制度。我们大概对于角度制最为熟悉,甚至闭着眼都能画出45度或90度的角。圆的360度体系已经成为了一种文化,
人们常说“180度转弯”就是指“转到相反的方向”,这里并不是指转弯的方向,而是指一种相反的观点。我们所讨论的角度,对于计算机来说,就是弧度。所
以,不管你是否喜欢,都要对弧度制有所了解。
1弧度约等于57.2958度。你也许会问“这符合逻辑吗?”确实有其逻辑所在。一个圆,360度,计算出的弧度为6.2832。仍然没有任何意义?好,
想一下圆周率派 Pi(π) 约等于 3.1416,而一个圆(6.2832弧度)就等于2 pi。我们知道 360 度相当于 2 pi,180
度相当于 pi,90 度相当于 pi/2,等等。图3-2 给出一些常用的弧度制。
图3-2 弧度与角度
从现在起我们就要开始使用弧度制了,而且今后会遇到很多用弧度表示度的情况。
影片剪辑和 Sprite 影片的 rotation
属性都要使用角度制,而且属性非常会经常使用。例如,一辆汽车需要旋转到运动的方向,如果使用三角学计算运动方向,那么所得到的角度是以弧度制表示的,而
汽车的旋转则需要使用角度制。相反,如果要指定某个对象向某个方向前进,就要获得它的旋转(rotation)角度,而这是用角度制表示的,如果要在三角
函数中使用它就一定要转换为弧度制。
角度制,还应用在滤镜上,如果使用投影滤镜(drop shadow filter),来为物体投射45度的阴影,就需要指定其角度而非弧度,不论是在 Flash IED 中还是使用 ActionScript 代码都一样。
为什么在一个编程体系里有两种截然不同的制度呢?也许这就是Flash双重性。一方面,这是设计人员的工具,在 Flash IDE
中拥有所有的绘图和变形工具,可以绘制出漂亮的图形。如果你对一名设计员说把你制作的 logo
文字旋转一个弧度,你肯定会遭白眼。另一方面,Flash 也是一个开发工具,更像一种编程语言,ActionScript
用户使用弧度制。总之,不论你是否喜欢都要使用到它们,而且还需要掌握角度制与弧度制间的相互转换。以下是公式:
弧度(radians) = 角度(degrees) * Math.PI /180
角度(degrees) = 弧度(radians) * 180 / Math.PI
在学习本书的过程中,会遇到很多公式。无论哪里,遇到需要记忆的公式时,我都会指出来,希望大家能够识记,这里是第一个公式。每次需要用到这些公式时,可
以查找一下,但不会得到现成的代码,因为这些代码都需要用手敲进去。我使用 ActionScript 写这些公式,比如使用 Math.PI 要比使用
pi 或其它字符要好,因为这和我们输入的代码是一致的。
180度大约等于3.14…弧度。换句话讲,半圆为 pi 个弧度,整圆为 2 pi个弧度,一个弧度大概为 57.29…度。
Flash 坐标系
在讨论角度时,就要提到 Flash 坐标系。如果我们习惯于数学坐标系,那么对于 Flash
坐标系可能会有些不习惯,因为在这里一切是颠倒(upside down)的。在标准坐标系中,用X表示水平轴,用Y表示垂直轴,Flash
也是一样。当 x=0,y=0时,坐标(0,0)通常显示在中心位置,X为正数时在右边,X为负数时在左边,Y为正数时在上边,Y为负数时在下边,如图
3-3 所示。
图3-3 标准坐标系
然而 Flash 是基于视频屏幕的坐标系,0,0 点为左上角,如图3-4。X值从左向右不断增大,但Y轴是相反的,正值向下,负值向上。这个系统有其历史根源,与屏幕扫描建立图像的原理一样,从左到右,从上到下。
图3-4 Flash 坐标系
我们可以想像成一个普通的坐标系,只是要把Y轴颠倒过来,并把屏幕中心迁移到屏幕的左上角。下面就来说说角。在一般的坐标系中,角度是以逆时针计算的,并以0度为起点向正X轴引一条线,如图3-5所示。
图3-5 普通的角度
在 Flash 中是颠倒的,如图3-6所示。顺时针旋转角度为正角。逆时针就意味着为负角。
图3-6 Flash 的角度
三角形的边
对于三角形的边,没有太多可说的,但它们都有各自的术语。以直角三角形为例,如图3-7所示,每条边都有各自的名称,与90度角相接的两条边称为直角边(legs),相对的边称为斜边,它总是那个最长的边。
图3-7 直角三角形各部分
刚才说到对边时,说它是与该角不相接的边。说到邻边时,说它是与角相接的边。在很多例子中,都是与其余两个不是90度的角打交道。在三角形中最有趣的就是角与边的关系,这些关系对于动画制作非常有用,下面就让我们来看看。
三角函数
ActionScript 拥有一套用于计算不同三角关系的三角函数:正弦,余弦,正切,反正弦,反余弦和反正切。下面我们就开始定义和使用这些函数,而后还会介绍它们的实际应用。
正弦(Sine)
下面是三角学的第一个部分。一个角的正弦值等于该角的对边与斜边的比,在 ActionScript中,使用 Math.sin(angle)
函数来表示。图3-8
所示为一个30度角的正弦。对边长为1,斜边长为2,两条边的比为1比2,或记作1/2或0.5,因此,我们可以说30度角的正弦值为0.5,下面在
Flash 中测试一下:
trace(Math.sin(30));
图3-8 角的正弦值为对边/斜边
输出结果为 –0.988031624092862,为什么会这样,能够找出原因吗?这是因为我们忘记了将结果转换为弧度制。我敢说你以后会常犯这种错误(我也一样),所以一定要小心。以下是正确的写法:
trace(Math.sin(30 * Math.PI / 180));
成功!输出 0.5
还可能得到 0.4999… 这样的值,这并不是程序的错误,而是由于二进制计算机常以浮点形式表示数值。但这个值已经非常接近了,所以就认为它等于0.5。
可以把一个三角形想象为角度为30,两条边长分别为1和2,然后把它移到普通坐标系中,不要忘了, Flash 坐标系的Y轴向下,角度是顺时针的。所以,对边和角度都是相反的,见图3-9。
图3-9 在 Flash 坐标系中创建相同的角
因此,比例也变成了-1/2,我们就称它为-30度角的正弦值。同时,把表达式改为:
trace(Math.sin(-30 * Math.PI / 180));
好的,不会很痛苦吧?下面再来看一个三角函数:余弦。
余弦(Cosine)
在 Flash 中,使用 Math.cos(angle) 就可以计算余弦值,余弦的定义为角的邻边与斜边之比。见图 3-10。
图3-10 角的余弦值为邻边/斜边
图3-10中的角度与图3-9中的相同,这次在图中直接加入了邻边的长度 1.73。角的余弦值为 1.73/2,或 0.865。因此,我们可以说-30度角的余弦值为 0.865,下面测试一下:
trace(Math.cos(-30 * Math.PI / 180));
与使用正弦函数一样,只不过这次调用的是 Math.cos 函数,这次输出结果为 0.866025403784439,非常接近
0.865。之所以会有所不同,是因为我把邻边的值取整了。真正的长度应该近似于1.73205080756888,用这个数除以2,那么结果就非常接近
-30度的余弦值。到现在为止,我们所说的都是左下方的角(degrees)。下面来看看右上方的角,首先,需要重新在坐标系中定位该角,这里的坐标系是
指 Flash 坐标系,见图3-11。
图3-11 观查对角(opposite angle)
该角的正弦值为对边与斜边之比,或1.73/2(0.865),余弦值为邻边与斜边之比,1/2(0.5)。因此就得出,一个角的余弦值等于另一个角的正弦值,请注意它们之间是相互关联,成比的。
正切(Tangent)
另一个重要的三角函数是正切,用 Flash 表示为 Math.tan(angle)。它反应的是对边与邻边之间的关系如图3-12所示。
图3-12 角的正切值为对边/邻边
两者的比例为 -1/1.73 或 -0.578,直接在 Flash 中进行验证,会得到更准确的结果:
trace(Math.tan(-30 * Math.PI / 180));
输出结果为 –0.577350269189626,证实了前面的计算。在 ActionScript 中,这个函数并不常用,而使用正弦和余弦的时候要多一些。另外,反正切函数却是非常有用的,后面会讲到,这里请大家记住正切函数的比例关系。
反正弦(Arcsine)和反余弦(Arccosine)
与正切相似,反正弦和反余弦在一般的 Flash 动画中很少使用。然而,我们还是要学习一下它们的用法,实际上就是正弦和余弦函数的反函数。换句话讲,就是输入一个比例值,返回一个角度值(以弧度表示)。
在 ActionScript 函数中记作 Math.asin(ratio) 和 Math.acos(ratio)。下面让来测试一下,我们已经知道30度角的正弦值为0.5,所以0.5的反正弦值应为30度,检验一下:
trace(Math.asin(0.5) * 180 / Math.PI);
别忘记将结果转换为角度制,才能得到角度制30度,而不是弧度制0.523。
我们知道,30度角的余弦值大约为 0.865,下面以同样的方法来测试一下:
trace(Math.acos(0.865) * 180 / Math.PI);
得到结果为 30.1172947473221。如果把30度的余弦值输入得更准确,那么所得的结果也会更为精确。怎么样,不难吧?
反正切(Arctangent)
大家可能都猜到了,反正切简单地说就是正切函数的反函数。我们只要输入对边与邻边的比值,就可以得到相应的角度。
在 Flash 中有两个函数可计算反正切。第一个就是像前面介绍过的函数一样 Math.atan(ratio),只需提供对边与邻边的比例值。例如,前面学过30度角的正切值约为0.577。试一下:
trace(Math.atan(0.577) * 180 / Math.PI);
输出结果是一个近似30的数,不是非常直观易懂吗,为什么还需要另一个函数呢?下面请看图3-13,让它来回答:
图3-13 四个象限上的角
如图3-13所示,有四个不同的角:A,B,C,D。角A和B,在X轴上为正数,角C和D在X轴上为负数,同样,角A和D在Y轴上为负数,而角B和C在Y轴上为正数。因此,四个内角的比例分别为:
A: –1/2 (–0.5)
B: 1/2 (0.5)
C: 1/ –2 (–0.5)
D: –1/ –2 (0.5)
对边与邻边之比为0.5,输入Math.atan(0.5),并转换为角度制,结果大约为 26.57,那么究竟所指的是角B还是角D呢?两个比例都为0.5那样就无法分辨了,看似是个小问题,但对于日后的工作确有很大的影响。
下面有请 Math.atan2(y,x),这是 Flash 的另一个反正切函数,它比
Math.atan(ratio)要有用得多。实事上,只需要学会这个函数的用法就可以了,函数中包括两个参数:对边长度与邻边长度。有时常会误写成
x,y,请注意应该是 y,x。请看如下示例,输入 Math.atan2(1,2),然后记住这个结果:
trace(Math.atan2(1, 2) * 180 / Math.PI);
输出结果为 26.565051177078,这正是角B的度数。下面再输入-1/-2(角D),再来试试:
trace(Math.atan2(-1, -2) * 180 / Math.PI);
出乎意料的结果–153.434948822922.为什么会这样?图3-14能给你解释。
图3-14 一个角的两种表示方法
从角D自身的底边开始,它确实为26.57度,但别忘了 Flash 的角度是从 X 轴的正半轴顺时针计算的。因此,从 Flash 的角度来衡量,则该角被视为-153.43度。下面就要开始在 Flash 中实践和应用三角学了。
旋转(Rotation)
我们想让一个影片剪辑或 Sprite 影片通过旋转来指向鼠标的位置,这将是个挑战。旋转(rotation)将成为我们工具箱中非常的工具,可以应用于游戏制作,鼠标追踪,界面设计等。
下面看一个示例。也可以根据以下步骤或打开文档类 RotateToMouse.as 和 Arrow.as(与本书中其它代码一同在 www.friendsofed.com 下载),这些是已写好的代码。首先,需要让物体旋转,它可以是一个在 Sprite 中绘制的箭头(Arrow)。事实上,如果我们要反复应用到这个箭头,可以把它制作成一个类:
package {
import flash.display.Sprite;
public class Arrow extends Sprite {
public function Arrow() {
init();
}
public function init():void {
graphics.lineStyle(1,0,1);
graphics.beginFill(0xffff00);
graphics.moveTo(-50,-25);
graphics.lineTo(0,-25);
graphics.lineTo(0,-50);
graphics.lineTo(50,0);
graphics.lineTo(0,50);
graphics.lineTo(0,25);
graphics.lineTo(-50,25);
graphics.lineTo(-50,-25);
graphics.endFill();
}
}
}
这里使用到了绘图 API (会在下一章介绍)来绘制箭头。无论何时需要一个箭头,只需写一句 new Arrow()即可,在图3-15中可看到显示结果。当绘制一些图像并进行旋转时,要注意它的指向,默让地指向右边,X的正半轴,这就是它旋转到0度时的状态。
我们先要创建一个Arrow类的实例,放致于舞台中心,并让它指向鼠标的方向,如图3-16。
图3-15 使用绘图API绘制的箭头
图3-16 下一次需要计算的值
很熟悉吗?与我们之前所讲的三角形相同,只不过多加入了鼠标与箭头的坐标。鼠标的位置只需使用 mouseX 和 mouseY
属性即可获得,同样,使用x,y属性,获得箭头的位置。使它们的值相减,就得到了两条边的长度。现在只需要使用 Math.atan2(dy,dx)
就可以求出夹角,然后把结果转换为角度制,最后让箭头的 rotation 属性等于这个夹角。代码如下:
var dx:Number = mouseX - arrow.x;
var dy:Number = mouseY - arrow.y;
var radians:Number = Math.atan2(dy, dx);
arrow.rotation = radians * 180 / Math.PI;
当然,为了使之形成一个动画,还需要加入循环。如同前一章提到的,使用事件处理函数将会是最好的选择,请使用 enterFrame 事件。以下是这个完整的文档类:
package {
import flash.display.Sprite;
import flash.events.Event;
public class RotateToMouse extends Sprite {
private var arrow:Arrow;
public function RotateToMouse() {
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);
}
public function onEnterFrame(event:Event):void {
var dx:Number=mouseX - arrow.x;
var dy:Number=mouseY - arrow.y;
var radians:Number=Math.atan2(dy,dx);
arrow.rotation=radians * 180 / Math.PI;
}
}
}
请
确认 RotateToMouse.as 文件与 Arrow.as 文件在同一目录下,以 RotateToMouse 作为文档类,并为它创建
SWF。怎么样?就像施了魔法一样!假设如果我们没有 Math.atan2 这个函数,就要先通过,dy除以dx求出对边与邻边的比值,然后再写入
Math.atan 函数。下面用 Math.atan 函数来代替 Math.atan2 函数来试一下,代码如下:
var radians = Math.atan(dy / dx);
试试这种写法,马上就会发现问题。如果鼠标位于箭头的左侧,箭头不会指向鼠标,并与鼠标相背离。能说说为什么吗?回到有 A,B,C,D
四个角的图(图3-13),不要忘记角A和C拥有相同的比值,角B和D也是一样。这样一来, Flash
就无法知道所指的是哪个角,所以只能得到A与或角B。如果,鼠标处于D角区域,Flash 会回到B角区域并把箭头指向这个角度。毫无疑问,这时
Math.atan2 的好处就显示出来了,书中会经常用到这个函数。
波形
大家肯定听说过正弦波,也一定见过图3-17所示的图形。
图3-17 正弦波形
那么为什么要把正弦函数与正弦图像两个不相干的东西联系到一起呢?如果将0到360度(或着0到2pi)代入到正弦函数中,那么就会得到这个正弦函数图像。从左到右代表所使用的角度值,而图中y坐标变化,代表这些角的正弦值。
图3-18中,标出了一些特殊的角度,我们可以看到0度的正弦值为0,90度或pi/2的正弦值为1,180度或pi的正弦值又回到0,270度或3
/2pi的正弦值为-1,360度的正弦值为0。下面用 Flash 来试一下正弦波形,把以下代码放入文档类的框架中进行测试:
for (var angle:Number = 0; angle < Math.PI * 2; angle += .1) {
trace(Math.sin(angle));
}
从现在起,要开始习惯只使用弧度制。除了使用 rotation 或其它只使用角度制的属性外,要开始学着不去使用角度制。
图3-18 正弦图像值
在这个例子中,角度从0开始,每次递增0.1直到大于 Math.PI*2
为止,并输出该角的正弦值。看一下输出结果,我们发现角度是从0开始,增加到1后,开始减小,减少到-1时,再回归至0。这些值不会真正准确地到达1或
0,因为每次增加0.1,所以永远不会得到pi或pi/2的整数倍。
平滑的上下运动
如何使用 Math.sin(angle) 呢?如果想让物体上下或左右移动,那么就要用到这个函数。考虑:使用 0~1~-1~0 的变化来实现这个动画,并且反复地使用这个波形。
活动域仅仅是从1到-1,不能看出效果,所以要把这些数值放大一些,比如扩大100倍。这样就拥有了一个从100到-100的波形,并且连绵不断。在下面这个文档类 Bobbing.as 中,要使用一个在 Ball 类中定义的 Sprite 影片,请看代码:
package {
import flash.display.Sprite;
public class Ball extends Sprite {
private 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();
}
}
}
当
这个类被实例化后,就能绘制出一个圆。我们还可以自行给出圆的半径(radius)和颜色(color)。如果不给这些参数的话,就会使用默认的参数:半
径为40,颜色为红色(这是AS3.0新增的功能)。这个类非常简单,但却非常有用,今后在书中会经常用到,所以大家一定要掌握。
文档类创建一个 Ball 类的实例,并加入到舞台上,再为它增加一个 enterFrame 侦听器,这样就可以让小球上下移动了。
package {
import flash.display.Sprite;
import flash.events.Event;
public class Bobbing extends Sprite {
private var ball:Ball;
private var angle:Number = 0;
public function Bobbing() {
init();
}
private function init():void {
ball = new Ball();
addChild(ball);
ball.x = stage.stageWidth / 2;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
public function onEnterFrame(event:Event):void {
ball.y = stage.stageHeight / 2 + Math.sin(angle) * 50;
angle += .1;
}
}
}
首
先,需要一个角度属性(angle)初始值为0。在 onEnterFrame
方法中,使用该角的正弦值并扩大50倍。这样一来,取值的范围就变成了50到-50。再在这个值上加舞台高度的一半,数值就变为从250到150(设舞台
高度为400像素),用这个值作为小球的Y坐标,最后为下一次循环增加0.1个弧度,这样就完成了小球平滑的上下运动。每一次循环的值都不相同,我们发现
如果将0.1变为另一个数值的话,就改变了小球运动的速度。角度(angle)变化的快慢与 Math.sin
从1到-1变化的速度成正比。很明显,改变50这个值,就改变了小球移动的距离,而改变 stage.stageHeight / 2
的值,就改变了小球运动时围绕的位置。我们还可以给出一些抽象的值作为变量,代码如下(只给出需要改变或增加的部分):
// at the top of the class:
private var angle:Number = 0;
[b]private var centerY:Number = 200;
private var range:Number = 50;
private var speed:Number = 0.1;[/b]
// and the handler function:
public function onEnterFrame(event:Event):void {
[b] ball.y = centerY + Math.sin(angle) * [i]range[/i];
angle += [i]speed[/i];[/b]
}