转 第八章 缓动与弹性运动(2)(as3.0)

弹簧链
      下面我们将几个弹性小球串联起来。 在介绍缓动一节时,我们简单地讨论了鼠标跟随的
概念,意思是说一个物体跟随鼠标,另一个物体再跟随这个物体,依此类推。当时没有给大
家举例子,  是因为这个效果现在看来有些逊色。 但是,当我们在弹性运动中使用这个概念时,
效果就截然不同了。
      本程序的设计思想:创建三个小球,名为 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);
    }
}
多目标点弹性运动
    我们在第五章讨论速度与加速度时,曾说过如何使一个物体受到多种外力。如果每种力
都是加速度,我们只需要把它们一个个都加到速度向量中去。因为弹力不过就是施加在物体
上的一种加速度,因此在一个物体上添加多种弹力也是非常容易的。
    下面是创建多目标弹簧的方法:我们需要三个控制点,这些点都是 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 变量的值,就可以轻松地设置控制点的数量。

    到目前为止,我相信大家已经有了很多的心得与体会,并且开始尝试解决一些书中没有
提到的问题。如果真是这样的话,那就太好了!这也正是我写这本书的目的。

 


目标偏移
    我们拿到一个真正的弹簧——有弹性的金属圈——然后将它的一头固定起来, 另一头放
上小球或其它物体,那么物体运动的目标点是哪里?难道说目标点是固定弹簧的那头儿?
不,这并不实际。小球永远也到不了这个点,因为它会受到弹簧自身的阻碍。一旦弹簧变回
了正常的长度,它会对小球施加更大的力。因此,目标点就应该是弹簧展开后的末端。
    要寻找目标点,首先要找到物体与固定点之间的夹角,然后沿这个角度从固定点向外展
开一段长度——弹簧的长度。换句话讲,如果弹簧长度是 50,小球与固定点的夹角是 45 度
的话,那么就要以 45 度的夹角向外运动 50 个像素,而这个点就是小球的目标点。图 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;
                                                                                145
     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 作为目标。事实上,本例中目标偏移起了重要的作
用。如果一个物体以其它物体直接作为目标,那么它们之就会相互吸引,最终聚集在一个点
上。通过使用偏移目标,我们就可以使它们之间保持距离。
     下面举一个例子,我们需要两个 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 所示。大家熟练掌握后,很快就能做出四边
形结构,直到一切复杂的弹簧结构。
本章重要公式总结
现在来回顾一下本章的重要公式
简单缓动,长形:
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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值