使用 curveTo
绘制曲线
下一个绘图函数,curveTo(x1, y1, x2, y2),起点和 lineTo
一样,同样是以上一次画线的终点做为本次画线的起点,也可以使用
moveTo
命令指定画笔的起点,如果是第一次画线默认的起点为0,0。
可以看到, curveTo
函数中包括两个点。第一个是控制点影响曲线的形状,另一个是曲线的终点。这里使用的是名为二次方贝塞尔曲线的标准公式,该公式可以计算出两点间的曲线,这条曲线向着控制点弯曲。请注意,这条曲线不会与控制点接触,很像是曲线被它吸引过去的。
下面来看动作脚本,文档类 DrawingCurves.as:
package {
import flash.display.Sprite;
import flash.events.MouseEvent;
public class DrawingCurves extends Sprite {
private var x0:Number =
100;
private var y0:Number =
200;
private var x1:Number;
private var y1:Number;
private var x2:Number =
300;
private var y2:Number =
200;
public function DrawingCurves()
{
init();
}
private function init():void
{
stage.addEventListener(MouseEvent.MOUSE_MOVE,
onMouseMove);
}
private function
onMouseMove(event:MouseEvent):void {
x1 =
mouseX;
y1 =
mouseY;
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(x0,
y0);
graphics.curveTo(x1,
y1, x2, y2);
}
}
}
测试这个文件,把鼠标来回移动。这里使用了两个给定的点作为起点和终点,使用鼠标位置作为控制点。请注意,曲线不会真正到达控制点位置,而只到达与控制点一半的位置。
过控制点的曲线
现在,如果想让曲线真正地穿过控制点,那么这就是我们工具箱中的另一个工具。使用下面这个公式计算出控制点的实际位置,这样就可以让曲线穿过指定的点了。同样,以
x0,y0 为起点,以 x2,y2
为终点,x1,y1为控制点,把将要穿过的点叫 xt,yt
(目标点)。换言之,如果让曲线穿过 xt,yt点,那么
x1,y1 又需要如何使用呢?公式如下:
x1 = xt * 2 – (x0 + x2) / 2;
y1 = yt * 2 – (y0 + y2) / 2;
只需要把目标点乘以2,然后减去起点与终点的平均值。大家可以画张图来究竟一下它的原理,要么就直接学会使用它。
把公式放在代码中,鼠标坐标用使用
xt,yt,我们只需要改变前一个文档类中的两行,将下面两行:
x1 = mouseX;
y1 = mouseY;
替换为
x1 = mouseX * 2 - (x0 + x2) / 2;
y1 = mouseY * 2 - (y0 + y2) / 2;
或者直接看
CurveThroughPoint.as,现成的文件。
创建多条曲线
下面我们将目光转向创建多条曲线,而不仅是一条曲线,创建一条平滑的向各个方向弯曲的线。首先,来看一个错误的做法,是我原先尝试过的一种方法。从随便一个点位出发,经过第一个点到第二个点再到第三个点,经过第四个到达第五个,经过第六个到达第七个等等绘制一条曲线。这里是代码(文档类
MultiCurve1.as):
package {
import flash.display.Sprite;
public class MultiCurves1 extends Sprite {
private var numPoints:uint =
9;
public function MultiCurves1()
{
init();
}
private function init():void
{
// first set
up an array of random points
var
points:Array = new Array();
for (var
i:int = 0; i < numPoints; i++) {
points[i]
= new Object();
points[i].x
= Math.random() * stage.stageHeight;
points[i].y
= Math.random() * stage.stageHeight;
}
graphics.lineStyle(1);
// now move
to the first point
graphics.moveTo(points[0].x,
points[0].y);
// and loop
through each next successive pair
for (i = 1; i
< numPoints; i += 2) {
graphics.curveTo(points[i].x,
points[i].y,
points[i
+ 1].x, points[i + 1].y);
}
}
}
}
第一次循环在 init
方法中,建立一个数组存储九个点。每个点都是一个
object 拥有 x,y
属性,它们的值都是舞台尺寸的随机数。当然,在一个真正的程序中,点位也许不是随机的,只是用这种方法进行快速设置。
随后设置线条样式,将笔移动到第一个点位。下一个循环从1开始每次递增2,所以线条是经过第一点到达第二点,然后从第三点到第四点,再从第五点到第六点,最后从第七点到第八点。至此,循环停止,因为第八点是最后一个点。大家也许注意到了,这里至少要有三个点,而且点的数量必需为奇数个。
程序看起来还不错,测试一下试试。如图4-1所示,看起来不是非常平滑,有棱有角的,这是因为曲线之间没有进行协调,它们之间共用了一个点。

图4-1
多条曲线,错误的方法。我们可以清楚地看到曲线的结束和开始的位置。
我们也许不得不去加入更多的点才能使解决这个问题。这里有个策略:在每两对点之间,加入一个新点(中间点)放在这两点的正中间。然后使用这些中间点作为起点和终点,而把最初的那些点(原始点)作为控制点。
图4-2
说明了解决办法。在图中,白点为原始点,黑点为中间点。这里使用了三条
curveTo
方法,图中的点使用了不同的颜色,这样就能分辨出起点与终点了。(图4-2
是 multicurvedemo.fla 文件的一张截图,可以在
www.friendsofted.com 的 books 页面下载)

图4-2 带有中间点的多线条
注意,图4-2中第一个中间点和最后一个中间点都没有被使用,第一个和最后一个原始点留作曲线的两个端点,只需在第二个点和倒数第二个点之间进行连接。这里是前一个例子的升级版,文档类
MultiCurve2.as:
package {
import flash.display.Sprite;
public class MultiCurves2 extends Sprite {
private var numPoints:uint =
9;
public function MultiCurves2()
{
init();
}
private function init():void
{
// first set
up an array of random points
var
points:Array = new Array();
for (var
i:int = 0; i < numPoints; i++) {
points[i]
= new Object();
points[i].x
= Math.random() * stage.stageHeight;
points[i].y
= Math.random() * stage.stageHeight;
}
graphics.lineStyle(1);
// now move
to the first point
graphics.moveTo(points[0].x,
points[0].y);
//
curve through the rest, stopping at each midpoint
for (i = 1; i
< numPoints - 2; i ++) {
var
xc:Number = (points[i].x + points[i + 1].x) / 2;
var
yc:Number = (points[i].y + points[i + 1].y) / 2;
graphics.curveTo(points[i].x,
points[i].y, xc, yc);
}
// curve
through the last two points
graphics.curveTo(points[i].x,
points[i].y, points[i+1].x,
points[i+1].y);
}
}
}
请注意,在新代码中, for 循环从1开始到
points.length -2
结束,也就避开了第一个点和最后一个点。程序要做的是,创建新的
x,y
点,这个点是数组中后面两个点位的平均值。然后从数组下一个点位开始画一条曲线到新的平均点(中间点)。当循环结束时,
i
变量指向倒数第二个元素,因此,可以穿过这里向最后一个点画条曲线。
这时,就得到一个非常平滑的图形,见图4-3。注意,这时原始点的数量不再受奇数个的限制。
再加一点小小的变化,使用同样的技术创建一条封闭的曲线。首先,计算一个初始的中间点,并移动到这里。然后,进行循环,获得每一个中间点,最后,将最后一条曲线画回初始中间点。图4-4
为显示结果

图4-3 多条平滑曲线
package {
import flash.display.Sprite;
public class MultiCurves3 extends Sprite {
private var numPoints:uint =
9;
public function MultiCurves3()
{
init();
}
private function init():void
{
var
points:Array = new Array();
for (var
i:int = 0; i < numPoints; i++) {
points[i]
= new Object();
points[i].x
= Math.random() * stage.stageHeight;
points[i].y
= Math.random() * stage.stageHeight;
}
//
find the first midpoint and move to it
var
xc1:Number = (points[0].x + points[numPoints - 1].x) / 2;
var
yc1:Number = (points[0].y + points[numPoints - 1].y) / 2;
graphics.lineStyle(1);
graphics.moveTo(xc1,
yc1);
// curve
through the rest, stopping at midpoints
for (i = 0; i
< numPoints - 1; i ++) {
var
xc:Number = (points[i].x + points[i + 1].x) / 2;
var
yc:Number = (points[i].y + points[i + 1].y) / 2;
graphics.curveTo(points[i].x,
points[i].y, xc, yc);
}
// curve
through the last point, back to the first midpoint
graphics.curveTo(points[i].x,
points[i].y, xc1,
yc1);
}
}
}

图4-4 多条封闭曲线
使用 beginFill 和 endFill
创建图形
beginFill(color, alpha)
方法非常简单,没有太多可说的。有一点值得注意,同
lineStyle 一样, alpha 的取值范围也变为了 0.0 到
1.0,而不是 0 到
100,这项也是可选的,默认为1.0。无论何时执行该帧的绘图代码
Flash 都会开始进行计算,无论何时遇到 endFill 指令
Flash 都会停止计算。总结一下,过程如下:
■ moveTo
■ lineStyle (如果有参数可以填入)
■ beginFill
■ 在一系列的 lineTo 和 curveTo
方法后,要在最初的点位结束
■ endFill
事实上,使用前三个方法的顺序不会影响到绘图。我们不是必需要指定线条样式,请记住如果不指定线条样式就会得到一条看不见的线条,非常适合绘制填充色,当然两者同时绘制也不错。如果所绘制的线条没有回到最初开始的点位,一但调用了
endFill, Flash
将会自动绘制一条封闭线,是为了能封闭这个图形。调用
endFill
后,无论线条样式如何,都会自动将最后一条线绘制完成。当然,我们自己将线条封闭是个很好的习惯,这样一来,既确保了最后的能够正确绘制,又可以让看代码的人知道我们究竟想画的是什么图形。
下面来试一下绘制填充色,可以使用前面的封闭曲线示例(MultiCurve3.as)来完成,这里已生成了一个封闭的图形。只要将
beginFill 语句放在第一条 curveTo
前面的任何地方——如
beginFill(0xff00ff);,这样就创建了亮紫色的填充——最后使用
endFill() 结束。
使用 beingGradientFill
创建渐变填充
下面开始学习绘图 API
的强大函数:beginGradientFill(fillType, colors,
alpha, ratios,
matrix)。与线条样式相同,这个方法也有很多额外的可选参数——扩散方法(spreadMethod),插补方法(interpolationMethod)及焦点位置比例(focalPointRatio)。这些参数可用来调整渐变的属性,但是在大多数简单的应用中这些参数不是必需的,而这个方法与
beginFill 有很多相似之处,同样也使用 endFill
作为结束。两者最大的不同是填充的颜色,我虽不想说
beginGradientFill
是用来创建渐变填充的,但是,如果不这么说,又感觉少了点什么。渐变填充至少要有两种颜色,图形的第一部分从第一种颜色开始,然后逐渐混合成为另一种颜色,或者混合成一个或多个已定义的颜色。
指定填充类型
我们可以建立两种类型的渐变填充:线性(linear)和放射(radial)状。在线性填充时,渐变的颜色沿着直线从一点到另一点。默认的情况,是从左向右的一条直线,也可以是从上到下或其它角度的直线。图4-5是一些线性渐变的例子。

图4-5 线性填充
为了能够看到线性渐变,需要至少两种不同的颜色。如果指定了两种颜色,那么填充将会从第一种颜色向第二种颜色渐变。如果指定了更多的颜色,填充色将会从第一种颜色渐变为第二种,然后再渐变到第三种……直到最后一种。
放射状填充与线性填充使用的参数大致相同,只是在解释上有所不同。从指定的中心位置开始创建渐变,以这点为基础向外进行放射,形成一个圆或椭圆。我们指定的第一种颜色用做内圆,最后一种颜色作为外圆,唯一不需要的就是角度。图4-6是一个放射状填充。
对于 beginGradientFill(fillType, colors, alphas, ratios, matrix)
方法,第一个参数为填充类型,非常简单,用一个字符串表示,这两个值中的一个:”linear”或”radial”。与第三章讲的事件类型很像,目前事件类型也被设置为
flash.display.GradientType
类的静态属性,为的是避免错误输入。我们可以导入
GradientType 类然后输入 GradientType.LINEAR 或
GradientType.RADIAL。

图4-6 放射状填充
设置 colors , alphas 及
ratios
使用 colors
参数设置颜色,当然也必需设置每一个颜色所要填充的位置,使用0到255中的一个数进行指定,0表示填充的开始位置,255表示填充结束位置。在这些数值中,每一个数代表一个颜色的位置,这就是填充的比例。因此,如果有要填充两种颜色,那么应该指定0和255作为
ratios,如果有三个颜色值,那么应该写成
0,128,255。这样就将第二个颜色放到了另外两个颜色的中间。如果比例值为
0,20,255,那么第一种颜色会很快渐变为第二种颜色,然后非常缓慢地渐变为第三种颜色。请记住这些数值不是像素值,而是指在
255 中的某一个部分。
我们同样必须指定渐变色的透明度,这就是 alpha
值,从 0.0 到 1.0,而在 AS 2
中表示为0到100。如果不需要透明度,那么就设置为
1.0。如果设置的透明度是从1.0到0.0,那么渐变的过程不仅是改变颜色,而且还有平滑淡出的效果。可以用做创建柔和的阴影(也许比使用投影滤镜(drop
shadow filter)还要好些)。
这里每一个参数都是一个数组,因为需要传入至少两个以上的
colors,alphas 及
ratios。我们可以先创建一个数组,然后写入每一个参数的值,如下:
var colors:Array = new Array();
colors[0] = 0xffffff;
colors[1] = 0x000000;
下面是一个更简单作法,我们可以在创建数组的同时,为数组的每个元素赋值,直接写在方括号中,以逗号作为分隔:
var colors:Array = [0xffffff, 0x000000];
事实上,我们甚至可以不去使用 colors
变量。直接把右边的数组写入 beginGradientFill
表达式中作为参数。因此,可以写成这样:
graphics.beginGradientFill(GradientType.LINEAR,
[0xffffff, 0x000000],
[1, 1],
[0, 255],
matrix);
这里定义了两个颜色值,两个 alpha 值(均为
100%)和比例值的开始位置与结束位置,所以渐变将以白色为开始最后逐渐变化为黑色。当然,也可以为每个参数设置一个对应的变量,如果定义了很多个颜色值的话,这样写会更清楚些。如果定义了三个颜色值,就必需有三个
alpha
值和三个比例值与之对应。如果有某些值多了或少了,那么就会引起会静默失败——没有渐变,没有填充,没有错误信息。
下面只需要设置一下填充的起点、终点或角度了。也许大家已经猜到了,这就是神秘的matrix(矩阵)
参数。
创建矩阵
矩阵(Matrix)就是一个二维表格,每行每列中包括不同的数,可以出计算不同的值。矩阵此外还用于绘图(graphics)中,用作旋转,缩放和移动物体。在这里,用于对渐变的控制,我们需要为填充进行定位,设置它的大小,或是进行旋转。使用
matrix 时,需要创建一个 matix 对象,这是一个
flash.geom.Matrix 类的对象。(实际上, Matrix
类不仅用于操作渐变填充,但这里我们只介绍它在渐变中的应用。第18章中介绍了更多的矩阵使用)。
在使用
Matrix
类时有一点复杂,这里有一个特殊的方法用于创建渐变填充的矩阵类型,这个方法名为
createGradientBox,必要参数为 width 和
height,可选参数为 rotation 及渐变的起点的 x,y
位置。首先创建一个 Matrix
类的实例,然后调用它的 createGradientBox
方法自动设置内部参数值。形式如下:
var matrix:Matrix = new Matrix();
matrix.createGradientBox(width, height, rotation, x,
y);
不要忘记在类的开始处导入 flash.geom.Matrix
类。如果仅指定 width 和 height,最后三个值默认为
0。来看一下代码,这里是文档类
GradientFill.as:
package {
import flash.display.GradientType;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Matrix;
public class GradientFill extends Sprite {
public function GradientFill()
{
init();
}
private function init():void
{
graphics.lineStyle(1);
var
colors:Array = [0xffffff, 0xff0000];
var
alphas:Array = [1, 1];
var
ratios:Array = [0, 255];
var
matrix:Matrix = new Matrix();
matrix.createGradientBox(100,
100, 0, 0, 0);
graphics.beginGradientFill(GradientType.LINEAR,
colors,
alphas,
ratios, matrix);
graphics.drawRect(0,
0, 100, 100);
graphics.endFill();
}
}
}
测试影片后,会看到一个从白色到红色渐变的正方形。现在,我们改变一下绘图代码,在不同的地方绘制这个正方形(见
GradientFill2.as):
graphics.drawRect(100, 100, 100, 100);
现在这个正方形为全红色,为什么会这样?渐变在
x
为0处开始,但它只有100像素宽,而正方形是从100像素开始的,渐变色已经到达了全红色,红色从这里开始已经出界了。因此,如果想让矩阵的
x,y 在正方形的左上角开始,应该这样写:
matrix.createGradientBox(100, 100, 0, 100, 100);
这样,
x,y
坐标就与正方形的起点相同。请使用同样的正方形,通过改变矩阵和渐变填充观察创建的填充是什么效果。首先,试一下三种颜色:
var colors:Array = [0xffffff, 0x0000ff, 0xff0000];
var alphas:Array = [1, 1, 1];
var ratios:Array = [0, 128, 255];
不要忘记改变 alphas 和 ratios
,将中间的比例移动一下看看对渐变色的影响。试用
64 或 220 代替 120。
下面是一个直接 alpha
变化的例子,使用相同的颜色,只改变 alpha
的值:
var colors:Array = [0x000000, 0x000000];
var alphas:Array = [1, 0];
var ratios:Array = [0, 255];
试着改变一下角度,下面是 45度角:
matrix.createGradientBox(100, 100, Math.PI / 4, 100, 100);
使用 Math.PI/2 旋转90度形成一个垂直填充。 - Math.PI/2
形成向上填充,而 Math.PI
左是从右向左填充,默认为从左向右填充。
beginGradientFill(GradientType.RADIAL, colors, alphas, ratios,
matrix);
现在,将这些应用在线性填充(linear)的技巧改为放射状填充(radial)的版本。
颜色转换
下面一个渲染工厂是 flash.geom.ColorTransform
类及其方法。与绘图 API
不同,该类不允许创建图形,仅仅用于改变已存在于影片或显示对象实例中图形的颜色。让我们去看看它是怎样工作的。
使用 ColorTransform
类改变颜色
在
ActionScript 2 中,操作影片剪辑颜色最常用的方法是
Color 类,拥有像 setRGB 和 setTransform
这样的方法。Flash 8 中就引用了 ColorTransform
类,同时 Color
类就不再被推荐使用,但是想大多数人仍然继续使用
Color 类,因为这已经成为习惯了。Color 类已经不是
AS 3
的一部分了,现在是学习新方法的最佳时机。ColorTransform
的方法与 Color
类的方法本质上是非常像,两者差不太多,只是语法上略有不同。
首先,要知道 Sprite
影片,影片剪辑,或其它任何显示对象都有一个属性叫作
transform。这是 flash.geom.Transform
类的一个实例,其中包括一些不同的属性用于缩放,旋转,定位和改变影片颜色,影响颜色属性的就是
colorTransform。要知道这是一个显示对象属性的属性,访问方法如下;
mySprite.transform.colorTransform
或使用一个继承自 Sprite
的类,可以直接获得类自身的 transform 属性:
transform.colorTransform
通过为一个 ColorTransform
对象的颜色属性赋值可以改变一个对象的色彩。你也许会问,什么是
ColorTransform 对象?如果大家使用过 AS 2 的 Color
方法:setTransform,就知道我们需要传入一个
object(对象) ,在这个 object
中有各种属性,需要告诉它如何变换颜色。
ColorTransform
与这个方法非常相似,但不是使用一个普通的对象(object),而现在这个方法已经拥有了官方的类。在
AS 2 中,是这样做的:
myTransform = new Object();
myTransform.ra = 50;
myTransform.ga = 40;
myTransform.ba = 12;
myTransform.aa = 40;
myTransform.rb = 244;
myTransform.gb = 112;
myTransform.bb = 90;
myTransform.ab = 70;
在 AS 3 中,应该创建一个同样的 ColorTransform
对象,像这样:
myTransform = new ColorTransform(0.5, 0.4, 0.12, 0.4, 244, 112, 90,
70);
前四个值为乘数,后四个值为偏移量,马上会介绍这个公式,然后就会知道为什么它们这样命名了。大家也许注意到了乘数的比例值为十进制范围
-1.0到1.0,而不是
-100到100。事实上,帮助文档说是从0.0到1.0,但还可以使用负数做为乘数实现一些有趣的效果(稍后会看到),偏移量依然是从-255到255。ColorTransform
对象的构造函数如下:
ColorTransform(redMultiplier,
greenMultiplier,
blueMultiplier,
alphaMultiplier,
redOffset,
greenOffset,
blueOffset,
alphaOffset)
转换一个特殊颜色通道的公式如下,以红色通道为例:
newRed = oldRed * redMultiplier + redOffset;
在使用 ColorTransform 时,记得它是 flash.geom
包中的一部分,所以需要导入类。
给大家一个例子,下一个文档类:TransformColor.as,在
SWF
中嵌入一张图片作为位图(位图类的实例)。因为位图类是一个显示对象,拥有
transform 属性,代码设置了位图的 transform 的
colorTransform
属性,使用构思好的设置来制作一张底片效果的图像:
package {
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.geom.ColorTransform;
public class TransformColor extends Sprite
{
[Embed(source="picture.jpg")];
public var Picture:Class;
public function
TransformColor() {
init();
}
private function init():void
{
var
pic:Bitmap=new
Picture
;
addChild(pic);
pic.transform.colorTransform=new
ColorTransform(-1,-1,1,1,
255,255,255,0);
}
}
}
测试这个影片时,请改变这行代码
[Embed(source="picture.jpg")]
请匹配这个路径为所使用图片的路径,如果在 Flash
IDE 中编辑,只需要在库中导入这张图片,为
ActionScript 导出,并输入类名为
Picture。重要的一句是 ColorTransform 的设置。