Flash基础理论课 第十七章 背面剔除与3D灯光Ⅱ
“天海祐希”通过精心收集,向本站投稿了3篇Flash基础理论课 第十七章 背面剔除与3D灯光Ⅱ,下面是小编为大家整理后的Flash基础理论课 第十七章 背面剔除与3D灯光Ⅱ,仅供大家参考借鉴,希望大家喜欢!
篇1:Flash基础理论课 第十七章 背面剔除与3D灯光Ⅱ
返回“Flash基础理论课 - 目录”
3D灯光
刚刚这个例子近乎可以让我们的渲染达到完美的效果,但是它似乎还缺少点儿什么,有些单调。OK,OK,大家看到标题就已经知道了,下面就让我们加入3D的灯光效果吧。
同背面剔除一样,3D灯光的细节也是相当复杂并且需要数学运算的。我实在没有太多的空间讨论每个漂亮的细节,但是通过快速的网络搜索大家可以获得非常更多的相关资料,也许这些资料多得我们一生也看不完。在这里,我给大家的都是一些基础的需要用到的函数。
首先,需要一个光源。一个最简单的光源只有两个属性:位置和亮度(brightness)。在更加复杂的 3D 系统中,它也能够指向某个方向,并且还带有颜色,衰减率(falloff rate),圆锥区域等等。但是这些都超出了本例的范围。
让我们从制作一个 Light灯光类开始。它会持有我们刚说的那两个属性 —— 位置和亮度。
package {
public class Light {
public var x:Number;
public var y:Number;
public var z:Number;
private var _brightness:Number;
public function Light(x:Number = -100,
y:Number = -100,
z:Number = -100,
brightness:Number = 1) {
this.x = x;
this.y = y;
this.z = z;
this.brightness = brightness;
}
public function set brightness(b:Number):void {
_brightness = Math.max(b, 0);
_brightness = Math.min(_brightness, 1);
}
public function get brightness:Number {
return _brightness;
}
}
}
现在可以在主类的 init 方法中创建一个新的默认灯光:
var light:Light = new Light();
或者可以创建一个指定位置和区域的灯光:
var light:Light = new Light(100, 200, 300, .5);
这里有两个重要的地方需要注意。一个是位置,仅用于计算灯光的角度。灯光的亮度不会因为距离而衰减。因此改变 x, y, z 到 –1,000,000 或 -1 对于照射在物体上的灯光的亮度是没有区别的。
只有 brightness 属性才会改变灯光的特性。我们当然可以加入一个函数用以判断灯光与物体间的距离来计算灯光的亮度值(brightness)。不会很难,现在已经介绍得差不多了,因此把这个函数留给大家去做。
brightness 必需是 0.0 到 1.0 之间的数。如果出了这个范围,会带来一些奇怪的结果。就是这个原因,我创建了一个私有属性 _brightness,并允许通过公共的 getter和setter 访问 brightness。这样做,允许我们传入的数值得到有效性的验证,确保这个数在有效范围内。
一个理想的类不应该出现公有的属性,即使这些属性不需要验证,也只有私有属性通过 getter和setter 函数才能访问。这里我抄了近路,为的是让代码简洁并突出动画编程的原则。但是在本例中,额外添加的这一步是有必要的。
下面,光源要做的就是根据灯光照射在多边形上的角度来改变三角形颜色的亮度值。因此如果一个多边形直接面对灯光,它就会显示出全部的颜色值。当离开灯光时,就会变得越来越暗。最终,当它完全离开光源时,它将完全变为阴影或黑色。
由于 Triangle 类的成员知道自己的颜色是什么,并知道如何绘制自己,似乎每个三角形只需访问这个 light 就可以实现自己 draw 函数。因此,让我们给所有三角形一个 light 属性。我还要超个近路设置它们为公有属性:
public var light:Light;
然后在主类中,创建这些三角形后,只需要循环它们把灯光的引用赋值给每个三角形:
var light:Light = new Light();
for(i = 0; i < triangles.length; i++) {
triangles[i].light = light;
}
或者,我们也可以让 light 作为 Triangle 构造函数中的一个附加的参数,让每个三角形都有一个光源。我将这个方法留给大家去选择。
现在,Triangle 需要一个关于其灯光颜色、角度、亮度的函数,并返回一个调整后的颜色值。以下是这个函数:
function getAdjustedColor():uint {
var red:Number = color >> 16;
var green:Number = color >> 8 & 0xff;
var blue:Number = color & 0xff;
var lightFactor:Number = getLightFactor();
red *= lightFactor;
green *= lightFactor;
blue *= lightFactor;
return red << 16 | green << 8 | blue;
}
这个函数首先将三角形的基本颜色分为了 red, green, blue 三个成分(见第四章)。然后调用另一个方法 getLightFactor,稍后会看到这个函数。现在,只需要知道它返回的是 0.0 到 1.0 之间的一个数,表示该颜色需要改变的大小,1.0 表示全部亮度,0.0 表示为全黑色。
然后将每个颜色成分乘以这个滤光系数(light factor),最后再将它们组合为一个 24 位的颜色值,并且将它作为调整后的颜色返回。它将成为灯光照射下三角形的颜色。
现在,如何得到这个 lightFactor 呢?让我们看一下:
private function getLightFactor():Number {
var ab:Object = new Object();
ab.x = pointA.x - pointB.x;
ab.y = pointA.y - pointB.y;
ab.z = pointA.z - pointB.z;
var bc:Object = new Object();
bc.x = pointB.x - pointC.x;
bc.y = pointB.y - pointC.y;
bc.z = pointB.z - pointC.z;
var norm:Object = new Object();
norm.x = (ab.y * bc.z) - (ab.z * bc.y);
norm.y = -((ab.x * bc.z) - (ab.z * bc.x));
norm.z = (ab.x * bc.y) - (ab.y * bc.x);
var dotProd:Number = norm.x * light.x +
norm.y * light.y +
norm.z * light.z;
var normMag:Number = Math.sqrt(norm.x * norm.x +
norm.y * norm.y +
norm.z * norm.z);
var lightMag:Number = Math.sqrt(light.x * light.x +
light.y * light.y +
light.z * light.z);
return Math.acos(dotProd / normMag * lightMag) / Math.PI * light.brightness;
}
哇,好大一个函数不是吗?要想完全理解它,就一定要对高等向量学有较深的掌握,但是我也会试将基础的地方解释一下。
首先,我们需要找到三角形的法线(normal)。它是一个向量,是三角形平面上的一条垂线,如图 17-5 所示。想象一下,我们拿着一块木制的三角板,然后从背后钉入一根钉子,它会从正面穿出。这根钉子就代表三角形平面的法线。如果您学过 3D 渲染和灯光的话,一定看过各种关于法线的资料。
图17-5 法线是到达三角形表面的一条垂线
我们可以通过该平面的两个向量计算出它们的外积(cross product)从而求出这条法线。两个向量的积是一条垂直于这两条向量的新向量。我们将使用的这两条向量是点 A和B,点 B和C 之间的连线。每个向量都用有带有 x, y, z的 Object 持有。
var ab:Object = new Object();
ab.x = pointA.x - pointB.x;
ab.y = pointA.y - pointB.y;
ab.z = pointA.z - pointB.z;
var bc:Object = new Object();
bc.x = pointB.x - pointC.x;
bc.y = pointB.y - pointC.y;
bc.z = pointB.z - pointC.z;
然后计算法线,即另一个向量。求该对象的模(norm)。下面的代码用于计算向量ab和bc的外积:
var norm:Object = new Object();
norm.x = (ab.y * bc.z) - (ab.z * bc.y);
norm.y = -((ab.x * bc.z) - (ab.z * bc.x));
norm.z = (ab.x * bc.y) - (ab.y * bc.x);
我没有太多的篇幅来介绍这种计算方法的细节,这是计算向量外积的标准公式。如果您对它的推导感兴趣,可以随便找一本线性代数的正规参考书查一查。
现在我们需要知道这条法线与灯光的角度。向量数学的另一个好东西叫做内积(dot product),它与外积不同。我们有了法线的向量和灯光的向量。下面计算点积:
var dotProd:Number = norm.x * light.x + norm.y * light.y + norm.z * light.z;
我们看到,内积要比外积简单一些!
OK,都差不多了!接下来,计算法线的量值,以及灯光的量值,大家应该还认识这个 3D 版的勾股定理吧:
var normMag:Number = Math.sqrt(norm.x * norm.x + norm.y * norm.y + norm.z * norm.z);
var lightMag:Number = Math.sqrt(light.x * light.x + light.y * light.y + light.z * light.z);
请注意,当一个三角形被渲染时,变量lightMag 每次都要进行计算,这样就允许灯光是移动的。如果知道光源是固定的,我们可以在代码的一开始就加入这个变量,只需在创建灯光或为三角形赋值时进行一次计算。或者可以为 Light 类添加 lightMag 属性,让它可以在每次 x, y, z 属性发生变化时被计算。看,我已经给大家留出了各种发挥的空间!
最后,将前面计算出的这些数放入一个具有魔力公式中:
return (Math.acos(dotProd / (normMag * lightMag)) / Math.PI) * light.brightness;
其中 dotProd 是一个分量,而 normMag * lightMag 是另一个分量。两者相除得出一个比率。回忆一下第三章,一个角度的余弦给了我们一个比率,而一个比率的反余弦给了我们一个角度。这就是灯光照射在多边形表面上的角度。它的范围在 0 到 Math.PI 个弧度之间(0 到 180 度),也就是说灯光完全照射在物体前面上或完全照射在物体背面。
用这个数除以Math.PI得出一个百分数,再用它乘以brightness的百分比就得出了最终用于改变底色的滤光系数。
OK,所有这些仅仅给出了多边形表面颜色!此刻,在现有的代码中实现它就非常简单了。我们在 draw 方法中使用它。应该像这样直接使用这个调整后的颜色:
g.beginFill(getAdjustedColor());
为了把上述内容综合起来,以下是全部最终的 Triangle.as和ExtrudedA.as 代码,列出了我们本章所有发生变化的部分:
首先是 Triangle:
package {
import flash.display.Graphics;
public class Triangle {
private var pointA:Point3D;
private var pointB:Point3D;
private var pointC:Point3D;
private var color:uint;
public var light:Light;
public function Triangle(a:Point3D, b:Point3D,
c:Point3D, color:uint) {
pointA = a;
pointB = b;
pointC = c;
this.color = color;
}
public function draw(g:Graphics):void {
if (isBackFace()) {
return;
}
g.beginFill(getAdjustedColor());
g.moveTo(pointA.screenX, pointA.screenY);
g.lineTo(pointB.screenX, pointB.screenY);
g.lineTo(pointC.screenX, pointC.screenY);
g.lineTo(pointA.screenX, pointA.screenY);
g.endFill();
}
private function getAdjustedColor():uint {
var red:Number = color >> 16;
var green:Number = color >> 8 & 0xff;
var blue:Number =color & 0xff;
var lightFactor:Number = getLightFactor();
red *= lightFactor;
green *= lightFactor;
blue *= lightFactor;
return red << 16 | green << 8 | blue;
}
private function getLightFactor():Number {
var ab:Object = new Object();
ab.x = pointA.x - pointB.x;
ab.y = pointA.y - pointB.y;
ab.z = pointA.z - pointB.z;
var bc:Object = new Object();
bc.x = pointB.x - pointC.x;
bc.y = pointB.y - pointC.y;
bc.z = pointB.z - pointC.z;
var norm:Object = new Object();
norm.x = (ab.y * bc.z) - (ab.z * bc.y);
norm.y = -((ab.x * bc.z) - (ab.z * bc.x));
norm.z = (ab.x * bc.y) - (ab.y * bc.x);
var dotProd:Number = norm.x * light.x +
norm.y * light.y +
norm.z * light.z;
var normMag:Number = Math.sqrt(norm.x * norm.x +
norm.y * norm.y +
norm.z * norm.z);
var lightMag:Number = Math.sqrt(light.x * light.x +
light.y * light.y +
light.z * light.z);
return Math.acos(dotProd / normMag * lightMag) / Math.PI * light.brightness;
}
private function isBackFace():Boolean {
// 见 www.jurjans.lv/flash/shape.html
var cax:Number = pointC.screenX - pointA.screenX;
var cay:Number = pointC.screenY - pointA.screenY;
var bcx:Number = pointB.screenX - pointC.screenX;
var bcy:Number = pointB.screenY - pointC.screenY;
return cax * bcy > cay * bcx;
}
public function get depth():Number {
var zpos:Number = Math.min(pointA.z, pointB.z);
zpos = Math.min(zpos, pointC.z);
return zpos;
}
}
}
然后是 ExtrudedA:
package {
import flash.display.Sprite;
import flash.events.Event;
public class ExtrudedA extends Sprite {
private var points:Array;
private var triangles:Array;
private var fl:Number = 250;
private var vpX:Number = stage.stageWidth / 2;
private var vpY:Number = stage.stageHeight / 2;
public function ExtrudedA() {
init();
}
private function init():void {
points = new Array();
points[0] = new Point3D( -50, -250, -50);
points[1] = new Point3D( 50, -250, -50);
points[2] = new Point3D( 200, 250, -50);
points[3] = new Point3D( 100, 250, -50);
points[4] = new Point3D( 50, 100, -50);
points[5] = new Point3D( -50, 100, -50);
points[6] = new Point3D(-100, 250, -50);
points[7] = new Point3D(-200, 250, -50);
points[8] = new Point3D( 0, -150, -50);
points[9] = new Point3D( 50, 0, -50);
points[10] = new Point3D( -50, 0, -50);
points[11] = new Point3D( -50, -250, 50);
points[12] = new Point3D( 50, -250, 50);
points[13] = new Point3D( 200, 250, 50);
points[14] = new Point3D( 100, 250, 50);
points[15] = new Point3D( 50, 100, 50);
points[16] = new Point3D( -50, 100, 50);
points[17] = new Point3D(-100, 250, 50);
points[18] = new Point3D(-200, 250, 50);
points[19] = new Point3D( 0, -150, 50);
points[20] = new Point3D( 50, 0, 50);
points[21] = new Point3D( -50, 0, 50);
for (var i:uint = 0; i < points.length; i++) {
points[i].setVanishingPoint(vpX, vpY);
points[i].setCenter(0, 0, 200);
}
triangles = new Array();
triangles[0] =new Triangle(points[0], points[1],
points[8], 0xcccccc);
triangles[1] =new Triangle(points[1], points[9],
points[8], 0xcccccc);
triangles[2] =new Triangle(points[1], points[2],
points[9], 0xcccccc);
triangles[3] =new Triangle(points[2], points[4],
points[9], 0xcccccc);
triangles[4] =new Triangle(points[2], points[3],
points[4], 0xcccccc);
triangles[5] =new Triangle(points[4], points[5],
points[9], 0xcccccc);
triangles[6] =new Triangle(points[9], points[5],
points[10], 0xcccccc);
triangles[7] =new Triangle(points[5], points[6],
points[7], 0xcccccc);
triangles[8] =new Triangle(points[5], points[7],
points[10], 0xcccccc);
triangles[9] =new Triangle(points[0], points[10],
points[7], 0xcccccc);
triangles[10] =new Triangle(points[0], points[8],
points[10], 0xcccccc);
triangles[11] =new Triangle(points[11], points[19],
points[12], 0xcccccc);
triangles[12] =new Triangle(points[12], points[19],
points[20], 0xcccccc);
triangles[13] =new Triangle(points[12], points[20],
points[13], 0xcccccc);
triangles[14] =new Triangle(points[13], points[20],
points[15], 0xcccccc);
triangles[15] =new Triangle(points[13], points[15],
points[14], 0xcccccc);
triangles[16] =new Triangle(points[15], points[20],
points[16], 0xcccccc);
triangles[17] =new Triangle(points[20], points[21],
points[16], 0xcccccc);
triangles[18] =new Triangle(points[16], points[18],
points[17], 0xcccccc);
triangles[19] =new Triangle(points[16], points[21],
points[18], 0xcccccc);
triangles[20] =new Triangle(points[11], points[18],
points[21], 0xcccccc);
triangles[21] =new Triangle(points[11], points[21],
points[19], 0xcccccc);
triangles[22] =new Triangle(points[0], points[11],
points[1], 0xcccccc);
triangles[23] =new Triangle(points[11], points[12],
points[1], 0xcccccc);
triangles[24] =new Triangle(points[1], points[12],
points[2], 0xcccccc);
triangles[25] =new Triangle(points[12], points[13],
points[2], 0xcccccc);
triangles[26] =new Triangle(points[3], points[2],
points[14], 0xcccccc);
triangles[27] =new Triangle(points[2], points[13],
points[14], 0xcccccc);
triangles[28] =new Triangle(points[4], points[3],
points[15], 0xcccccc);
triangles[29] =new Triangle(points[3], points[14],
points[15], 0xcccccc);
triangles[30] =new Triangle(points[5], points[4],
points[16], 0xcccccc);
triangles[31] =new Triangle(points[4], points[15],
points[16], 0xcccccc);
triangles[32] =new Triangle(points[6], points[5],
points[17], 0xcccccc);
triangles[33] =new Triangle(points[5], points[16],
points[17], 0xcccccc);
triangles[34] =new Triangle(points[7], points[6],
points[18], 0xcccccc);
triangles[35] =new Triangle(points[6], points[17],
points[18], 0xcccccc);
triangles[36] =new Triangle(points[0], points[7],
points[11], 0xcccccc);
triangles[37] =new Triangle(points[7], points[18],
points[11], 0xcccccc);
triangles[38] =new Triangle(points[8], points[9],
points[19], 0xcccccc);
triangles[39] =new Triangle(points[9], points[20],
points[19], 0xcccccc);
triangles[40] =new Triangle(points[9], points[10],
points[20], 0xcccccc);
triangles[41] =new Triangle(points[10], points[21],
points[20], 0xcccccc);
triangles[42] =new Triangle(points[10], points[8],
points[21], 0xcccccc);
triangles[43] =new Triangle(points[8], points[19],
points[21], 0xcccccc);
var light:Light = new Light();
for (i = 0; i < triangles.length; i++) {
triangles[i].light = light;
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var angleX:Number = (mouseY - vpY) * .001;
var angleY:Number = (mouseX - vpX) * .001;
for (var i:uint = 0; i < points.length; i++) {
var point:Point3D = points[i];
point.rotateX(angleX);
point.rotateY(angleY);
}
triangles.sortOn(“depth”, Array.DESCENDING | Array.NUMERIC);
graphics.clear();
for (i = 0; i < triangles.length; i++) {
triangles[i].draw(graphics);
}
}
}
}
我们看到,在文档类中只有两个次要的变化,
主要的工作都集中在 Triangle 中。同时,我还让所有的三角形使用相同的颜色,我认为这样做可以更好地观察灯光效果(见图 17-6)。
图17-6 带有背面剔除,深度排序及3D灯光的三维立体模型
篇2:Flash基础理论课 第八章 缓动与弹性运动Ⅰ
返回“Flash基础理论课 - 目录”
很难相信我们居然用了七章才把基础的内容介绍完,现在进入第八章,这里是高级内容的起点,从这里开始内容也开始变得越来越有趣了,前面的章节都是些常用的概念与技术。从今天开始,每章只着重介绍一两种特殊的运动。
本章将介绍缓动运动(成比例速度)与弹性运动(成比例加速度),不用担心它们只是两个名词术语,这章可以快速地略读。我会给出很多例子程序,可以使大家充分了解这项技术的强大。
成比例运动
缓动(ease)与弹性(spring)联系紧密。这两种方法都是将对象(通常指 Sprite或MovieClip)从某一点移动到目标点。使用缓动运动(Easing),如同让影片滑动到目标并停止。使用弹性运动(Springing),会产生向前或向后的反弹,最终停止在目标点位。两种方法具有一些共同点:
■需要一个目标点;
■确定到目标点的距离;
■成比例地将影片移动到目标点——距离越远,移动速度越快。
缓动运动(easing)与弹性运动(springing)的不同之处在于移动的比例。缓动运动时,速度与距离成正比,离目标越远,物体运动速度越快。当物体与目标点非常非常接近时,就几乎不动了。
弹性运动时,加速度与距离成正比。如果物体与目标离得很远,再用上加速度,会使移动速度非常快。当物体接近目标时,加速度会减小,但依然存在!物体会飞过目标点,随后再由反向加速度将它拉回来。最终,用摩擦力使其静止。
下面,我们分别看一下这两种方法,先从缓动(easing)开始。
缓动(Easing)
首先说明缓动的种类不只有一种。在 Flash IDE 中,制作补间动画时,我们就可以看到 “缓动输入”(ease in)和“缓动输出”(ease out)。下面所讨论的缓动类型与运动补间的“缓动输出”相似。在本章后面的“高级缓动”一节,将会给大家一个网站连接,在那里可以学习制作所有缓动的效果。
简单缓动
简单缓动是个非常基础概念,就是将一个物体移到别处去。创建这个“运动效果”时,希望物体能在几帧内慢慢移动到某一点。我们可以求出两点之间的夹角,然后设置速度,再使用三角学计算出 vx和vy,然后让物体运动。每一帧都判断一下物体与目标点的距离,如果到达了目标则停止。这种运动还需要一定条件的约束才能实现,但如果要让物体运动得很自然,显然这种方法是行不通的。
问题在于物体沿着固定的速度和方向运动,到达目标点后,立即停止。这种方法,用于表现物体撞墙的情景,也许比较合适。但是物体移动到目标点的过程,就像是某个人明确地知道他的目的地,然后向着目标有计划地前进,起初运动的速度很快,而临近目标点时,速度就开始慢下来了。换句话讲,它的速度向量与目标点的距离是成比例的。
先来举个例子。比如说我们开车回家,当离家还有几千米的距离时,要全速前进,当离开马路开进小区时速度就要稍微慢一点儿。当还差两座楼时就要更慢一点儿。在进入车库时,速度也许只有几迈。当进入停车位时速度还要更慢些,在还有几英尺的时候,速度几乎为零。
如果大家注意观察就会发现,这种行为就像关门、推抽屉一样。开始的速度很快,然后逐渐慢下来。
在我们使用缓动使物体归位时,运动显得很自然。简单的缓动运动实现起来也非常简单,比求出夹角,计算 vx,vy 还要简单。下面是缓动的实现策略:
1. 确定一个数字作为运动比例系数,这是个小于 1的分数;
2. 确定目标点;
3. 计算物体与目标点的距离;
4. 用距离乘以比例系数,得出速度向量;
5.将速度向量加到当前物体坐标上;
6. 重复 3到5 步。图 8-1 解释了这一过程。
图8-1 简单缓动
我们先来解释一下这个过程,看看在 ActionScript. 中是怎样实现的。
首先,确定一个分数作为比例系数。我们说过,速度与距离是成比例的。也就是说速度是距离的一部分。比例系数在 0和1 之间,系数越接近1,运动速度就会越快;系数越接近0,运动速度就会越慢。但是要小心,系数过小会使物体无法到达目标。开始我们以 0.2 作为系数,这个变量名就叫 easing。初始代码如下:
var easing:Number = 0.2;
接下来,确定目标。只需要一个简单的x,y 坐标,选择舞台中心坐标再合适不过了。
var targetX:Number = stage.stageWidth / 2;
var targetY:Number = stage.stageHeight / 2;
下面,确定物体到达目标的距离。假设已经有一个名为 ball 影片,只需要从ball的x,y 坐标中减去目标的x,y。
var dx:Number = targetX - ball.x;
var dy:Number = targetY - ball.y;
速度等于距离乘以比例系数:
vx = dx * easing;
vy = dy * easing;
下面,大家知道该怎么做了吧:
ball.x += vx;
ball.y += vy;
最后重复步骤 3 到步骤 5,因此只需加入enterFrame. 处理函数。
让我们再看一下这三个步骤,以便将它们最大程度地简化:
var dx:Number = targetX - ball.x;
var dy:Number = targetY - ball.y;
vx = dx * easing;
vy = dy * easing;
ball.x += vx;
ball.y += vy;
把前面四句简化为两句:
vx = (targetX - ball.x) * easing;
vy = (targetY - ball.y) * easing;
ball.x += vx;
ball.y += vy;
如果大家觉得还不够精简,还可以进一步缩短:
ball.x += (targetX - ball.x) * easing;
ball.y += (targetY - ball.y) * easing;
在开始学习使用缓动时,也许大家会比较喜欢用详细的句型,让程序看上去更加清晰。但是当你使过几百次后,就会更习惯用第三种写法。下面,我们选用第二种句型,以加强对速度的理解。
现在就来看一下脚本动作,依然延用Ball 类。以下是文档类 Easing1.as:
package {
import flash.display.Sprite;
import flash.events.Event;
public class Easing1 extends Sprite {
private var ball:Ball;
private var easing:Number=0.2;
private var targetX:Number=stage.stageWidth / 2;
private var targetY:Number=stage.stageHeight / 2;
public function Easing1() {
trace(targetX,targetY);
init();
}
private function init():void {
ball=new Ball ;
addChild(ball);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var vx:Number=(targetX - ball.x) * easing;
var vy:Number=(targetY - ball.y) * easing;
ball.x+= vx;
ball.y+= vy;
}
}
}
试改变easing的值,观察运动效果。
下面,大家可以让小球变成可以拖拽的,与第七章所做的拖拽与抛落效果很像。在点击小球时开始拖拽,同时,删除 enterFrame. 处理函数并且用stage 侦听 mouseUp。在 mouseUp 函数中,停止拖拽,删除 mouseUp 方法,并重新开始 enterFrame。下面是文档类 Easin2.as :
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Easing2 extends Sprite {
private var ball:Ball;
private var easing:Number=0.2;
private var targetX:Number=stage.stageWidth / 2;
private var targetY:Number=stage.stageHeight / 2;
public function Easing2() {
init();
}
private function init():void {
ball=new Ball ;
addChild(ball);
ball.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onMouseDown(event:MouseEvent):void {
ball.startDrag();
removeEventListener(Event.ENTER_FRAME,onEnterFrame);
stage.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
}
private function onMouseUp(event:MouseEvent):void {
ball.stopDrag();
addEventListener(Event.ENTER_FRAME,onEnterFrame);
stage.removeEventListener(MouseEvent.MOUSE_UP,onMouseUp);
}
private function onEnterFrame(event:Event):void {
var vx:Number=(targetX - ball.x) * easing;
var vy:Number=(targetY - ball.y) * easing;
ball.x+= vx;
ball.y+= vy;
}
}
}
缓动何时停止
在物体缓动运动到目标点时,物体最终会到达目标点并且完成缓动效果,
但是,即使不显示该对象,缓动代码仍在执行中,这一来浪费了 CPU 资源。当物体到达目标时,应该停止执行代码。判断物体是否到达目标的方法非常简单,就像这样:
if(ball.x == targetX && ball.y == targetY) {
// code to stop the easing
}
但是这里要注意一些技巧。
我们所讨论的缓动类型涉及到了著名的Xeno悖论。Xeno也是位希腊人,爱好测量实验。Xeno将运动分解为下面几个步骤:物体要从A 点到达 B 点,它首先要移动到两点间一半的距离。然后物体再从该点出发,到达与 B 点距离一半的距离。然后再折半。每次只移动离目标一半的距离,但永远无法准确地达到目标。
这个悖论听起来是非常符合逻辑的。但是很明显,我们确实将物体从一点移动到了另一点,这样看来他的说法有些问题。到 Flash 中看看,影片在 x 轴上的位置为 0,假设要将它移动到 x 轴为 100的位置。按照悖论所说,设缓动系数为 0.5,这样每次运动到离目标一半的距离。过程如下:
■从0 点开始,经过 1 帧,到达 50。
■第 2 帧,到达 75。
■剩下的距离是 25。它的一半是 12.5 ,所以新的距离就是 87.5。
■按照这种顺序,位置将变化为 93.75, 96.875, 98.4375 等等。20 帧以后,将到达 99.999809265。
从理论上讲位置越来越接近目标,但是永远无法准确地到达目标点。然而,在代码中进行试验时,结果就发生了一些微妙的变化。归根结底问题就在于“一次最少能移动多少个像素”,答案是 1/20。事实上,二十分之一像素有个特有的名字:twip (缇)。在 Flash 内部计算单位都采用twip 像素,包括所有 Sprite 影片,影片剪辑和其它舞台对象。因此,在显示影片位置时,这个数值永远是 0.05的倍数。
下面举个例子,一个影片要到达 100的位置,而它所到达的最接近的位置事实上是 99.5。再分隔的距离,就是加上 (100 – 99.95) /2。相当于加上了 0.025,四十分之一像素。超出了 twip 是能够移动的最小值,因此无法加上“半个 twip”,结果是只增加了 0 像素。如果大家不信的话,可以亲自试一下(提示:将代码放入框架类中的init 方法):
var sprite:Sprite;
sprite = new Sprite();
addChild(sprite);
sprite.x = 0;
var targ:Number = 100;
for(var i:Number = 0; i < 20; i++) {
trace(i + “: ” + sprite.x);
sprite.x += (targ - sprite.x) * .5;
}
循环20次,将影片移动离目标一半的距离,这是基本缓动应用。将代码放入for循环,只是为了测试其位置,并不在于观察物体运动。循环到第 11次时,影片已经到达了 99.95,这已经是它能够到达的最远的地方了。
长话短说,影片并非无限地接近目标,但是它确实永远无法准确地到达目标点。这样一来,缓动代码就永远不会停止。我们要回答的问题是 “哪里才是物体最接近的目标位置?”,这需要确定到目标点的距离是否小于某个范围。我发现在很多软件中,如果物体与目标点的距离相差在一个像素以内,就可以说它已经到达了目标点,即可停止缓动了。
在处理二维坐标时,可以使用第三章所介绍的公式来计算点间距离:
distance = Math.sqrt(dx * dx + dy * dy)
如果只处理一维坐标点,如只移动一个轴的位置,就需要使用距离的绝对值,因为它有可能是个负数,使用Math.abs 方法。
OK,说得很多了,来写代码吧。这个简单的文档类,演示了如何关闭缓动运动(EasingOff.as):
package {
import flash.display.Sprite;
import flash.events.Event;
public class EasingOff extends Sprite {
private var ball:Ball;
private var easing:Number = 0.2;
private var targetX:Number = stage.stageWidth / 2;
public function EasingOff() {
init();
}
private function init():void {
ball = new Ball();
addChild(ball);
ball.y = stage.stageHeight / 2;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var dx:Number = targetX - ball.x;
if (Math.abs(dx) < 1) {
ball.x = targetX;
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
trace(“done”);
} else {
var vx:Number = dx * easing;
ball.x += vx;
}
}
}
}
此例中,将缓动公式分解使用,首先计算出距离,因为我们需要知道是否该停止缓动。大家应该知道为什么要使用dx的绝对值了吧。如果小球在目标点的右边,dx的值总是负的,if (dx < 1)的结果永远为真,这就会使缓动停止。而使用Math.abs,就可以判断实际距离是否小于 1。
记住,如果将拖拽与缓动相结合,要在放开小球时,将运动代码重新启用。
移动的目标
上面例子中的目标点都是单一且固定的,这些似乎还不能满足我们的要求。事实上,Flash 并不关心物体是否到达目标,或目标是否还在移动。它只会问 “我的目标在哪里?距离有多远?速度是多少?”,每帧都如此。因此,我们可以很容易将目标点改为鼠标所在的位置,只需将原来 targetX和targetY的地方,改成鼠标的坐标 (mouseX和mouseY)。以下是一个比较简单的版本(EaseToMouse.as):
package {
import flash.display.Sprite;
import flash.events.Event;
public class EaseToMouse extends Sprite {
private var ball:Ball;
private var easing:Number = 0.2;
public function EaseToMouse() {
init();
}
private function init():void {
ball = new Ball();
addChild(ball);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var vx:Number = (mouseX - ball.x) * easing;
var vy:Number = (mouseY - ball.y) * easing;
ball.x += vx;
ball.y += vy;
}
}
}
移动鼠标观察小球跟随情况,是不是距离越远速度越快。
试想还有没有其它可移动的目标。当然还可以是一个影片向着另一个影片缓动。在早先的Flash 时代,鼠标追踪者(mouse trailers)——即一串影片跟踪者鼠标的效果——曾经风靡一时。缓动就是制作这种效果的方法之一。第一个影片缓动到鼠标上,第二个影片缓动到第一个影片上,第三个再缓动到第二个上,依此类推。大家不妨一试。
缓动不仅限于运动
本书中,有很多简单的例子程序。在这些例子中,我们主要是计算影片所用变量的值。通常,使用x,y 属性控制物体的位置。不过别忘了 Sprite 影片,影片剪辑以及各种显示对象还有很多其它可以操作的属性,而且基本都是用数字表示的。所以在读例子程序时,也应该试用其它属性代替这个例子中的属性。下面给大家一些启发。
透明度
将缓动用在 alpha 属性上。开始设置为 0 ,目标设置为 1 :
ball.alpha = 0;
var targetAlpha:Number = 1;
在 enterFrame. 函数中,使用缓动可以实现影片淡入效果:
ball.alpha += (targetAlpha - ball.alpha) * easing;
若将 0和1 颠倒过来就可以实现影片的淡出效果。
旋转
设置一个 rotation 属性和一个目标 rotation。当然,还需要一个能够表现旋转对象,比如一个箭头(arrow):
arrow.rotation = 90;
var targetRotation:Number = 270;
arrow.rotation += (targetRotation - arrow.rotation) * easing;
颜色
如果大家想挑战一下,可以在 24 位色彩中使用缓动。设置好 red,green,blue的初始值,使用缓动单独表现每一色彩元素的值,然后将它们再组合成 24 位色彩值。例如,我们可以从red 缓动到 blue。初始颜色如下:
red = 255;
green = 0;
blue = 0;
redTarget = 0;
greenTarget = 0;
blueTarget = 255;
在 enterFrame. 处理函数中的每一帧执行缓动。这里只表现一个 red 值:
red += (redTarget - red) * easing;
再将这三个数值组合为一个数(如第四章介绍的):
col = red << 16 | green << 8 | blue;
最后可以在 ColorTransform. (见第四章),线条颜色或填充色中使用该颜色值。
高级缓动
现在我们已经看到简单的缓动效果是如何实现的了,大家也许正在考虑如何使用更复杂的缓动公式制作一些效果。例如,在开始时比较缓慢,然后渐渐开始加速,最后在接近目标时再将速度慢下来。或者希望在一段时间或若干帧内完成缓动效果。
Robert Penner 以收集、编制和实现缓动公式而出名。我们可以在 www.robertpenner.com 中找到他的缓动公式。在他写这些内容时 AS 3 版本还没有出现,但是用我们前面几章所学知识,将它们转化为 AS 3 版本的也是件非常容易的事。
OK,下面进入Flash 中我最喜欢的一个主题:弹性运动(Springing)。
篇3:Flash基础理论课 第八章 缓动与弹性运动Ⅲ
返回“Flash基础理论课 - 目录”
弹簧链
下面我们将几个弹性小球串联起来,在介绍缓动一节时,我们简单地讨论了鼠标跟随的概念,意思是说一个物体跟随鼠标,另一个物体再跟随这个物体,依此类推。当时没有给大家举例子,是因为这个效果现在看来有些逊色。但是,当我们在弹性运动中使用这个概念时,效果就截然不同了。
本程序的设计思想:创建三个小球,名为ball0,ball1,ball2。第一个小球,ball0的动作与上面例子中的效果是相同的。ball1向ball0 运动,ball2向ball1 运动。每个小球都受到重力的影响,所以它们都会向下坠。代码稍有些复杂,文档类 Chain.as:
package {
import flash.display.Sprite;
import flash.events.Event;
public class Chain extends Sprite {
private var ball0:Ball;
private var ball1:Ball;
private var ball2:Ball;
private var spring:Number = 0.1;
private var friction:Number = 0.8;
private var gravity:Number = 5;
public function Chain() {
init();
}
private function init():void {
ball0 = new Ball(20);
addChild(ball0);
ball1 = new Ball(20);
addChild(ball1);
ball2 = new Ball(20);
addChild(ball2);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(event:Event):void {
moveBall(ball0,mouseX,mouseY);
moveBall(ball1,ball0.x,ball0.y);
moveBall(ball2,ball1.x,ball1.y);
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(mouseX,mouseY);
graphics.lineTo(ball0.x,ball0.y);
graphics.lineTo(ball1.x,ball1.y);
graphics.lineTo(ball2.x,ball2.y);
}
private function moveBall(ball:Ball,targetX:Number,targetY:Number):void {
ball.vx += (targetX - ball.x) * spring;
ball.vy += (targetY - ball.y) * spring;
ball.vy += gravity;
ball.vx *= friction;
ball.vy *= friction;
ball.x += ball.vx;
ball.y += ball.vy;
}
}
}
看一下 Ball 这个类,我们发现每个对象实例都有自己 vx 和 vy 属性,并且它们的初始值均为0。所以在init方法中,我们只需要创建小球并把它们加入显示列表。
然后在onEnterFrame函数中,实现弹性运动。这里我们调用了 moveBall方法,比复制三次运动代码要好用得多。该函数的参数分别为一个 ball 对象以及目标点的 x,y 坐标。每个小球都调用这个函数,第一个小球以鼠标的 x,y作为目标位置,第二第三个小球以第一第二个小球作为目标位置。
最后,在确定了所有小球的位置后,开始画线,画线的起点是鼠标位置,然后依次画到每个小球上,这样橡皮圈就连接上了所有的小球。注意,程序中的 friction 降为0.8 为了使小球能够很快稳定下来。
创建一个数组保存链中所有对象的引用,然后通过循环遍历数组中的每个小球并执行运动,使这个程序更加灵活。这里只需要做一些小小的改变。首先,需要两个新的变量代表数组和对象数目:
private var balls:Array;
private var numBalls:Number = 5;
在函数 init 中,使用for循环创建所有对象,并将对象引用加入数组:
private function init():void {
balls = new Array();
for(var i:uint = 0; i < numBalls; i++) {
var ball:Ball = new Ball(20);
addChild(ball);
balls.push(ball);
}
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
最后,onEnterFrame方法的变化最大。首先设置线条,将绘图起点移动到鼠标位置,再到第一个小球,然后循环为剩下的小球设置位置并连线。通过改变 numBalls 变量,我们可以加入任意多个小球。
private function onEnterFrame(event:Event):void {
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(mouseX,mouseY);
moveBall(balls[0],mouseX,mouseY);
graphics.lineTo(balls[0].x,balls[0].y);
for(var i:uint = 1; i < numBalls; i++) {
var ballA:Ball = balls[i-1];
var ballB:Ball = balls[i];
moveBall(ballB,ballA.x,ballA.y);
graphics.lineTo(ballB.x,ballB.y);
}
}
运行结果见图 8-3,大家可以在ChainArray.as找到这个类。
图8-3 弹簧链
多目标弹性运动
我们在第五章讨论速度与加速度时,曾说过如何使一个物体受到多种外力。如果每种力都是加速度,我们只需要把它们一个个都加到速度向量中去。因为弹力不过就是施加在物体上的一种加速度,因此在一个物体上添加多种弹力也是非常容易的。
下面是创建多目标弹簧的方法:我们需要三个控制点,这些点都是 Ball 类的实例,并且具有简单的拖拽功能,用它们作为小球弹性运动的控制点。小球会立即运动到点,并在两点间寻找平衡。换句话讲,每个目标都会对小球施加一定的外力,小球的运动速度就是这些外力相加的结果。
例子程序相当复杂,使用多个方法处理不同的事件。以下是代码(文档类 MultiSpring.as),看过后再进行分段讲解:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class MultiSpring extends Sprite {
private var ball:Ball;
private var handles:Array;
private var spring:Number = 0.1;
private var friction:Number = 0.8;
private var numHandles:Number = 3;
public function MultiSpring() {
init();
}
private function init():void {
ball = new Ball(20);
addChild(ball);
handles = new Array();
for (var i:uint = 0; i < numHandles; i++) {
var handle:Ball = new Ball(10,0x0000ff);
handle.x = Math.random() * stage.stageWidth;
handle.y = Math.random() * stage.stageHeight;
handle.addEventListener(MouseEvent.MOUSE_DOWN,onPress);
addChild(handle);
handles.push(handle);
}
addEventListener(Event.ENTER_FRAME,onEnterFrame);
addEventListener(MouseEvent.MOUSE_UP,onRelease);
}
private function onEnterFrame(event:Event):void {
for (var i:uint = 0; i < numHandles; i++) {
var handle:Ball = handles[i] as Ball;
var dx:Number = handle.x - ball.x;
var dy:Number = handle.y - ball.y;
ball.vx += dx * spring;
ball.vy += dy * spring;
}
ball.vx *= friction;
ball.vy *= friction;
ball.x += ball.vx;
ball.y += ball.vy;
graphics.clear();
graphics.lineStyle(1);
for (i = 0; i < numHandles; i++) {
graphics.moveTo(ball.x,ball.y);
graphics.lineTo(handles[i].x,handles[i].y);
}
}
private function onPress(event:MouseEvent):void {
event.target.startDrag();
}
private function onRelease(event:MouseEvent):void {
stopDrag();
}
}
}
在init方法中,创建小球并用for循环创建三个控制点,随机安排位置,并为它们设置拖拽行为。
onEnterFrame方法循环取出每个控制点,使小球向该点方向运动。然后,用控制点的坐标设置小球的速度,反复循环,从小球开始向各个控制点画线。onPress方法的内容非常简单,但是请注意 onRelease函数,我们无法知道当前拖拽的是哪个小球。幸运的是,使用任何一个显示调用stopDrag方法,都可以停止所有的拖拽,所以只需要在文档类中直接调用该方法。
我们只要改变 numHandles 变量的值,就可以轻松地设置控制点的数量。运行结果如图 8-4所示。
图8-4 多目标弹性
到目前为止,我相信大家已经有了很多的心得与体会,并且开始尝试解决一些书中没有提到的问题。如果真是这样的话,那就太好了!这也正是我写这本书的目的。
目标偏移
我们拿到一个真正的弹簧——有弹性的金属圈——然后将它的一头固定起来,另一头放上小球或其它物体,那么物体运动的目标点是哪里?难道说目标点是固定弹簧的那头儿?不,这并不实际。小球永远也到不了这个点,因为它会受到弹簧自身的阻碍。一旦弹簧变回了正常的长度,它会对小球施加更大的力。因此,目标点就应该是弹簧展开后的末端。
要寻找目标点,首先要找到物体与固定点之间的夹角,然后沿这个角度从固定点向外展开一段长度——弹簧的长度。换句话讲,如果弹簧长度是 50,小球与固定点的夹角是 45 度的话,那么就要以 45 度的夹角向外运动 50 个像素,而这个点就是小球的目标点。图8-5 解释了这一过程。
图8-5 弹簧复位
寻找目标点的代码如下:
var dx:Number = ball.x - fixedX;
var dy:Number = ball.y - fixedY;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = fixedX + Math.cos(angle) * springLength;
var targetY:Number = fixedY + Math.sin(angle) * springLength;
运行结果是,物体向着固定点运动,但会在与目标点相差一段距离时停止移动。大家还要注意,虽然我们叫它“固定点”,只是代表弹簧固定到的某个点。而不是指这个点不能移动。也许最好的方法就是看代码。
我们继续使用鼠标位置作为固定点,弹簧的长度为100 像素,
以下是文档类(OffsetSpring.as):
package {
import flash.display.Sprite;
import flash.events.Event;
public class OffsetSpring extends Sprite {
private var ball:Ball;
private var spring:Number = 0.1;
private var vx:Number = 0;
private var vy:Number = 0;
private var friction:Number = 0.95;
private var springLength:Number = 100;
public function OffsetSpring() {
init();
}
private function init():void {
ball = new Ball(20);
addChild(ball);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var dx:Number = ball.x - mouseX;
var dy:Number = ball.y - mouseY;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = mouseX + Math.cos(angle) * springLength;
var targetY:Number = mouseY + Math.sin(angle) * springLength;
vx += (targetX - ball.x) * spring;
vy += (targetY - ball.y) * spring;
vx *= friction;
vy *= friction;
ball.x += vx;
ball.y += vy;
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(ball.x,ball.y);
graphics.lineTo(mouseX,mouseY);
}
}
}
虽然我们能够看到运行结果,但却不能真正发现这项技术的特殊用处。没关系,下一节会给大家一个特别的例子。
弹簧连接多个物体
我们知道如何用弹簧连接两个物体,还知道这个点不是固定的。但是,如果另一个物体上还有一个弹簧反作用在第一个物体上,又是怎样的呢?这里有两个物体之间由一根弹簧连接。其中一个运动了,另一个物体就要向该物体移动过来。
我开始认为制作这种效果会导致死循环从而无法实现,或者至少会引起错误。但我也没管那么多,勇敢地进行尝试。结果非常完美!
虽然前面已经描述了一些策略,但这里还要细致得说一下:物体A以物体B作为目标,并向它移动。物体B 反过来又以物体A作为目标。事实上,本例中目标偏移起了重要的作用。如果一个物体以其它物体直接作为目标,那么它们之就会相互吸引,最终聚集在一个点上。通过使用偏移目标,我们就可以使它们之间保持距离,如图 8-6所示。
图8-6 弹簧连接的两个物体
下面举一个例子,我们需要两个 Ball 类的实例。分别为ball0 和 ball1。ball0向ball1 偏移运动。ball1向ball0 偏移运动。为了不去反复写偏移弹性运动的代码,我们将这些功能写到函数 springTo 中,直接调用函数即可。如果想让 ball0向ball1 运动,只要写 springTo(ball0,ball1),然后再让 ball1向ball0 运动,就写 springTo(ball1,ball0)。还要设置两个变量,ball0Dragging 和 ball1Dragging,作为每个小球运动的开关。以下是文档类(DoubleSpring.as):
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class DoubleSpring extends Sprite {
private var ball0:Ball;
private var ball1:Ball;
private var ball0Dragging:Boolean = false;
private var ball1Dragging:Boolean = false;
private var spring:Number = 0.1;
private var friction:Number = 0.95;
private var springLength:Number = 100;
public function DoubleSpring() {
init();
}
private function init():void {
ball0 = new Ball(20);
ball0.x = Math.random() * stage.stageWidth;
ball0.y = Math.random() * stage.stageHeight;
ball0.addEventListener(MouseEvent.MOUSE_DOWN,onPress);
addChild(ball0);
ball1 = new Ball(20);
ball1.x = Math.random() * stage.stageWidth;
ball1.y = Math.random() * stage.stageHeight;
ball1.addEventListener(MouseEvent.MOUSE_DOWN,onPress);
addChild(ball1);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
stage.addEventListener(MouseEvent.MOUSE_UP,onRelease);
}
private function onEnterFrame(event:Event):void {
if (!ball0Dragging) {
springTo(ball0,ball1);
}
if (!ball1Dragging) {
springTo(ball1,ball0);
}
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(ball0.x,ball0.y);
graphics.lineTo(ball1.x,ball1.y);
}
private function springTo(ballA:Ball,ballB:Ball):void {
var dx:Number = ballB.x - ballA.x;
var dy:Number = ballB.y - ballA.y;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = ballB.x - Math.cos(angle) * springLength;
var targetY:Number = ballB.y - Math.sin(angle) * springLength;
ballA.vx += (targetX - ballA.x) * spring;
ballA.vy += (targetY - ballA.y) * spring;
ballA.vx *= friction;
ballA.vy *= friction;
ballA.x += ballA.vx;
ballA.y += ballA.vy;
}
private function onPress(event:MouseEvent):void {
event.target.startDrag();
if (event.target == ball0) {
ball0Dragging = true;
}
if (event.target == ball1) {
ball1Dragging = true;
}
}
private function onRelease(event:MouseEvent):void {
ball0.stopDrag();
ball1.stopDrag();
ball0Dragging = false;
ball1Dragging = false;
}
}
}
本例中,每个小球都是可以拖拽的。enterFrame函数负责为小球调用springTo函数。请注意,这两条语句都是由两条判断语句包围起来的,目的是要确认小球目前没被拖拽:
springTo(ball0,ball1);
springTo(ball1,ball0);
springTo函数用于产生运动,函数中的所有语句大家应该都很熟悉。首先,求出距离和角度,再计算目标点,然后向目标点运动。第二次调用函数时,参数相反,两个小球交换位置,开始的小球向另一个小球运动。这也许不是效率最高的代码,但是它可以最好地表现出运动的过程。
我们看到,小球不会依附在任何固定点上,它们都是自由飘浮的。小球之间唯一的约束就是彼此保持一定的距离。这种写法最好的地方是可以很容易地加入新的物体。例如,再创建第三个小球(ball2),同时为它设置一个变量(ball2Dragging),就可以这么添加:
if(!ball0Dragging) {
springTo(ball0,ball1);
springTo(ball0,ball2);
}
if(!ball1Dragging) {
springTo(ball1,ball0);
springTo(ball1,ball2);
}
if(!ball2Dragging) {
springTo(ball2,ball0);
springTo(ball2,ball1);
}
这样就建立了一个三角形结构,如图 8-7所示。大家熟练掌握后,很快就能做出四边形结构,直到一切复杂的弹簧结构。
图8-7 一根弹簧连接三个物体
本章重要公式总结
现在来回顾一下本章的重要公式
简单缓动,长形:
var dx:Number = targetX - sprite.x;
var dy:Number = targetY - sprite.y;
vx = dx * easing;
vy = dy * easing;
sprite.x += vx;
sprite.y += vy;
简单缓动,中形:
vx = (targetX - sprite.x) * easing;
vy = (targetY - sprite.y) * easing;
sprite.x += vx;
sprite.y += vy;
简单缓动,短形:
sprite.x += (targetX - sprite.x) * easing;
sprite.y += (targetY - sprite.y) * easing;
简单弹性,长形:
var ax:Number = (targetX - sprite.x) * spring;
var ay:Number = (targetY - sprite.y) * spring;
vx += ax;
vy += ay;
vx *= friction;
vy *= friction;
sprite.x += vx;
sprite.y += vy;
简单弹性,中形:
vx += (targetX - sprite.x) * spring;
vy += (targetY - sprite.y) * spring;
vx *= friction;
vy *= friction;
sprite.x += vx;
sprite.y += vy;
简单弹性,短形:
vx += (targetX - sprite.x) * spring;
vy += (targetY - sprite.y) * spring;
sprite.x += (vx *= friction);
sprite.y += (vy *= friction);
偏移弹性运动:
var dx:Number = sprite.x - fixedX;
var dy:Number = sprite.y - fixedY;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = fixedX + Math.cos(angle) * springLength;
var targetY:Number = fixedX + Math.sin(angle) * springLength;
【Flash基础理论课 第十七章 背面剔除与3D灯光Ⅱ】相关文章:






文档为doc格式