打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
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)。

声明:该文章系网友上传分享,此内容仅代表网友个人经验或观点,不代表本网站立场和观点;若未进行原创声明,则表明该文章系转载自互联网;若该文章内容涉嫌侵权,请及时向上学吧网站投诉>>
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
as3烟花
动量守恒与能量守恒
高级动画
2D WebGL renderer Pixi.js v4 入门【最终回】
Canvas2D mobile web game development – implementation | Tizen Developers
Flash AS3.0制作飘动的气泡内含详细注释
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服