返回“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)。
联系客服