转 第十五章 3D 基础 (2)(as3.0)

来源:互联网 发布:500价位的耳机知乎 编辑:程序博客网 时间:2024/06/06 20:22

排序
     在添加了多个物体后代码中显现出了一个新问题---称为z  排序。Z  排序就像它的名
字一样:物体如何在 z  轴上进行排序,或者说哪个物体在前面。由于物体都使用纯色,所
以看起来不是很明显。为了让效果更加明显,请将 Ball3D  的 init  方法改为以下代码,并
运行刚才那个程序:
public function init():void {
 graphics.lineStyle(0);
 graphics.beginFill(color);
  graphics.drawCircle(0, 0, radius);
 graphics.endFill();
}

     通过给小球添加轮廓线,我们就可以看出哪个小球在前面了。这几乎毁掉了整个 3D 
果,因为现在较小的物体出现在了较大物体的前面。Z  排序就是用来解决这个问题的,但
不是自动的。Flash  不知道我们在模拟 3D。它只知道我们在移动和缩放影片。它也不知道
我们到底是使用左手还是右手坐标系。在小球远离时应该将这个小球放在相邻小球的后面。
Flash  只根据在显示列表中的相对索引进行排序。在 AS 2 中,z  排序只需要改变影片剪辑
的深度即可完成。swapDepths(深度)。深度较高的影片剪辑出现在深度较低的影片的前面。
然而在 AS 3  中,操作会稍微有些复杂。对于显示列表没有可以任意修改的深度值。显示列
表的作用更像是与个数组。列表中的每个显示对象都有一个索引。索引从 0  开始,直到列
表中所有对象的个数。例如,假设在类中加入三个影片 A, B, C。它们的索引应该是 0, 1, 2。
无法将其中的一个影片的索引设置为 100,或 -100。如果已经删除了 B 影片,那么这时 A
和 C  影片的索引应该是 0  和 1。明白了吧,在显示列表中永远没有“空间”这个概念
     根据深度,索引 0  是最低的,任何深度较高的显示对象都将出现在这个较低对象的前
面。我们可以用几种不同的方法来改变物体的深度:
■ setChildIndex(child:DisplayObject, index:int) 给对象指定索引值(index)。
■ swapChildren(child1:DisplayObject, child2:DisplayObject) 交换两个指定的对象。
■ swapChildrenAt(index1:int, index2:int)  交换两个指定的深度。
使用 setChildIndex 是最简单的。因为我们已经有了一个 balls 数组。可以根据小球的 z 轴
深度从高到低来排序这个数组,然后从 balls 的 0(最远的)  到 49(最近的)为每个小球
设置索引。请看下面这段代码:

private function sortZ():void {
 balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
  for (var i:uint = 0; i < numBalls; i++) {
   var ball:Ball3D = balls[i];
  setChildIndex(ball, i);
 }

}
     根据数组中每个对象的 zpos  属性对该数组进行排序。因为指定了数组的
DESCENDING  和 Array.NUMERIC,则是按数值大小反向排序的——换句话讲,就是从高
到低。结果会使最远的小球(zpos  值最高的)将成为数组中的第一个,最近的将成为最后
一个。
     然后循环这个数组,将每个小球在显示列表中的索引值设置为与当前在数组中的索引值
相同。
     将这个方法放入类中,只需要在小球移动后调用它即可,将函数调用放在 onEnterFrame
方法的最后:

private function onEnterFrame(event:Event):void {
  for (var i:uint = 0; i < numBalls; i++) {
   var ball:Ball3D = balls[i];
  move(ball);
 }
 sortZ();
}
     剩下的代码与上一个例子中的相同。全部代码可在 Zsort.as  中找到。
 

 

重力
     这里我们所说的重力就像地球表面上的重力一样,如第五章所讲的。既然这样 3D 
重力和 2D  的就很像了。我们所需要做的就是选择一个施加在物体上的重力值,并在每帧
中将它加入到物体的速度中。
     由于 3D  的重力非常简单,我差点就跳过去说“是的,同 2D  一样。OK,下一话题。”
但是,我决定将它放到一个很好的例子中加以解释,让大家知道即使很简单的东西也可以创
造出非常棒的效果,就像 3D  烟火一样。
     首先,我们需要找个物体代表一个单独的“烟火”——我们知道,这些发光的点可以组
合到一起形成巨大的爆炸。我们给忠实的 Ball3D  类一个较小的半径值,就可以完成这个目
的。只要给每个小球一个随机的颜色效果就会非常漂亮。如果将背景色设置为黑色就更好了。
我使用 SWF  元数据来完成这个设置,但如果是在 Flash CS3 IDE  中,只需要简单地改变
一下文档属性的背景色即可。
     我确信大家现在一定能够完成。先将所有的代码列出来(Fireworks.as),随后加以解释。

package {
 import flash.display.Sprite;
 import flash.events.Event;
 [SWF(backgroundColor=0x000000)];
  public class Fireworks extends Sprite {
   private var balls:Array;
   private var numBalls:uint = 100;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   private var gravity:Number = 0.2;
   private var floor:Number = 200;
   private var bounce:Number = -0.6;
   public function Fireworks() {
   init();
  }

 private function init():void {
  balls = new Array();
   for (var i:uint = 0; i < numBalls; i++) {
    var ball:Ball3D = new Ball3D(3, Math.random() * 0xffffff);
   balls.push(ball);
   ball.ypos = -100;
    ball.vx = Math.random() * 6 - 3;
    ball.vy = Math.random() * 6 - 6;
    ball.vz = Math.random() * 6 - 3;
   addChild(ball);
  }
  addEventListener(Event.ENTER_FRAME, onEnterFrame);
 }
 private function onEnterFrame(event:Event):void {
   for (var i:uint = 0; i < numBalls; i++) {
   var ball:Ball3D = balls[i];
   move(ball);
  }
  sortZ();
 }

 private function move(ball:Ball3D):void {
  ball.vy += gravity;
  ball.xpos += ball.vx;
  ball.ypos += ball.vy;
  ball.zpos += ball.vz;
   if (ball.ypos > floor) {
   ball.ypos = floor;
   ball.vy *= bounce;
  }
   if (ball.zpos > -fl) {
   var scale:Number = fl / (fl + ball.zpos);
    ball.scaleX = ball.scaleY = scale;
    ball.x = vpX + ball.xpos * scale;
    ball.y = vpY + ball.ypos * scale;
   ball.visible = true;
  } else {
   ball.visible = false;
  }
 }
 private function sortZ():void {
  balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
   for (var i:uint = 0; i < numBalls; i++) {
   var ball:Ball3D = balls[i];
   setChildIndex(ball, i);

  }
  }
 }
}
     首先加入一些属性:gravity, bounce, floor。前两个大家都见过。Floor  属性就是  --

bottom --也就是物理反弹之前可以运动到的 y 值。除了增加 y  轴速度以及碰撞地
面后的反弹以外,所有的内容我们前面都介绍过,是不是越来越酷了,哈?
     运行结果如图 15-6 所示。

转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(2)(as3.0)

图 15-6  烟火(相信我,运动中的效果更好)

屏幕环绕
     回忆一下第六章,我们说过三种当物体碰到边界后受到反作用力的可能。目前为
们只介绍了反弹。还有两个:屏幕环绕与重置。对于 3D  而言,我发现屏幕环绕效果是最
为有用的,但只能在 z 轴上使用。
    2D  屏幕环绕中,在 x  或 y  轴上判断物体是否出了屏幕。效果非常好,因为当物体超
出了其中一个边界时就看不到它了,因此可以轻松地重新设置物体的位置,不会引起人们的
注意。但是 3D  中就不能这么潇洒了。
     在 3D  中,实际上只有两个点可以安全地删除和重置物体。一个就是当物体运动到观
察点的后面。前面例子中,将物体设置为不可见时,就是这个道理。另一个就是当物体的距
离太远和太小时也可以将其设为不可见的。这就意味着我们可以在 z  轴上安全地进行屏幕
包装。当物体走到身后时,就将它放到面前的远方。如果物体离得过远,超出了可见范围,
就可以删除它并将其重置到身后。如果大家喜欢 x, y  轴也可以这样做,但是多数情况下,
这么做会导致一些不自然的忽隐忽现的效果。
     还好 z 轴的环绕可以是相当常用的。我曾用它制作出真实的 3D 赛车游戏,下面我们
就来制作其中的一部分。
     主体思想是把不同的 3D  物体放到观察点前。然后将这些物体向观察点移动。换句话
讲,给它们一些负的 z  速度。这要看我们如何设置了,可以让物体向我们走来,让眼睛以
为是我们向物体走去。一旦物体走到了观察点后,就将它重置到眼前一段距离。这样就可以
永无止境地掠过这些物体了。
     本例中使用的物体是一棵线条化的树。创建一棵带有随机枝叉的树形结构。我确信
能做得更好!
     绘制树的代码放在名为 Tree  的类中,下面会看到,用三个位置属性以及随机绘制树枝
的代码来代表一颗树。

 

package {
 import flash.display.Sprite;
  public class Tree extends Sprite {
   public var xpos:Number = 0;
    public var ypos:Number = 0;
   public var zpos:Number = 0;
   public function Tree() {
   init();
  }
   public function init():void {
   graphics.lineStyle(0, 0xffffff);
     graphics.lineTo(0, -140 - Math.random() * 20);
     graphics.moveTo(0, -30 - Math.random() * 30);
     graphics.lineTo(Math.random() * 80 - 40,
     -100 - Math.random() * 40);
     graphics.moveTo(0, -60 - Math.random() * 40);
     graphics.lineTo(Math.random() * 60 - 30,
     -110 - Math.random() * 20);
  }
 }
}

  同样,还是使用 SWF  元数据将背景色设置为黑色。大家可以创建任何喜欢的物体,
想要多复杂都可以自行设置。在文档类中创建所有的树(100  左右)。随机分散在 x  轴上,
每个方向 1000  像素。它们同样随机分散到 z  轴上,从 0  到 10000。它们都以 floor  属性
为基础,具有相同的 y 坐标,给人一种地平面的感觉。
     以下是代码(可以见 Trees.as):
Here’s the code (which you can also find in Trees.as):
package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
 [SWF(backgroundColor=0x000000)];
  public class Trees extends Sprite {
   private var trees:Array;
   private var numTrees:uint = 100;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   private var floor:Number = 50;
   private var vz:Number = 0;
   private var friction:Number = 0.98;

 public function Trees() {
  init();
 }
 private function init():void {
  trees = new Array();
   for (var i:uint = 0; i < numTrees; i++) {
   var tree:Tree = new Tree();
   trees.push(tree);
     tree.xpos = Math.random() * 2000 - 1000;
   tree.ypos = floor;
   tree.zpos = Math.random() * 10000;

    addChild(tree);
   }
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
   stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  }
   private function onEnterFrame(event:Event):void {
     for (var i:uint = 0; i < numTrees; i++) {
    var tree:Tree = trees[i];
    move(tree);
   }
   vz *= friction;
   sortZ();
  }
   private function onKeyDown(event:KeyboardEvent):void {
     if (event.keyCode == Keyboard.UP) {
    vz -= 1;
     } else if (event.keyCode == Keyboard.DOWN) {
    vz += 1;
   }
  }
   private function move(tree:Tree):void {

   tree.zpos += vz;
     if (tree.zpos < -fl) {
    tree.zpos += 10000;
   }
     if (tree.zpos > 10000 - fl) {
    tree.zpos -= 10000;
   }
     var scale:Number = fl / (fl + tree.zpos);
     tree.scaleX = tree.scaleY = scale;
     tree.x = vpX + tree.xpos * scale;
     tree.y = vpY + tree.ypos * scale;
   tree.alpha = scale;
  }
   private function sortZ():void {
   trees.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
     for (var i:uint = 0; i < numTrees; i++) {
    var tree:Tree = trees[i];
    setChildIndex(tree, i);
   }
  }
 }
}

  请注意,这里只有一个 z 轴速度变量,因为树不需要在 x 或 y  轴上进行移动,所有
的移动都在 z  轴上。在 onEnterFrame 方法中,判断方向键上和下,增加或减少 vz。加入
一点点摩擦力让速度不会增加到无限大,在按键松开时将速度降下来。
     代码循环获得每棵树,用当前 z  速度更新该树的 z  坐标。然后判断这棵树是否走到了
我们的身后。如果是,将这个棵树向 z 轴内移动 10000  像素。否则,如果超过了 10000 
fl,就将该树往回移动 10000  像素。再执行标准透视动作。为了更好地加强立体感我还加

入了一个小小的设计:
    tree.alpha = scale;
根据 z  轴的深度设置树的透明度。离得越远颜色越淡。这是大气透视,模拟大气与观察者
和物体之间的效果。这是本例中表现物体远离时一种特殊效果。这个特殊的设计给了我们黑
暗的效果和幽深的夜。大家也许可以试试这种方法:
       tree.alpha = scale * .7 + .3;
让树的可见度至少为 30%。看上去不再那么朦胧。这里没有正确或错误可言--只有不同
的数值创造不同的效果。
    大家也许注意到了,我仍把 z 排序方法留在这里。在这个特殊的例子中,它没有发挥
本应有的作用,因为树都是由同一颜色的简单线条构成的,但如果绘制的是一些非常复杂的
或重叠的图形,那么它的存在就是至关重要的了。
     本文件的运行结果  15-7  所示。

转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(2)(as3.0)

图 15-7  当心小树!
     下面我将给大家一个加强的例子,让我们看一下还可以做到什么样的程度。以下是程
(可以在 Trees2.as  中找到):
package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
 [SWF(backgroundColor=0x000000)];
  public class Trees2 extends Sprite {
   private var trees:Array;
   private var numTrees:uint = 100;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   private var floor:Number = 50;
   private var ax:Number = 0;

   private var ay:Number = 0;
   private var az:Number = 0;
   private var vx:Number = 0;
    private var vy:Number = 0;
   private var vz:Number = 0;
   private var gravity:Number = 0.3;
   private var friction:Number = 0.98;
   public function Trees2() {
   init();
  }
   private function init():void {
   trees = new Array();
     for (var i:uint = 0; i < numTrees; i++) {
    var tree:Tree = new Tree();
    trees.push(tree);
       tree.xpos = Math.random() * 2000 - 1000;
    tree.ypos = floor;
    tree.zpos = Math.random() * 10000;
    addChild(tree);
   }

   addEventListener(Event.ENTER_FRAME, onEnterFrame);
   stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
   stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
  }
   private function onEnterFrame(event:Event):void {
   vx += ax;
   vy += ay;
   vz += az;
   vy -= gravity;
     for (var i:uint = 0; i < numTrees; i++) {
    var tree:Tree = trees[i];
    move(tree);
   }
   vx *= friction;
   vy *= friction;
   vz *= friction;
   sortZ();
  }
   private function onKeyDown(event:KeyboardEvent):void {
   switch (event.keyCode) {
    case Keyboard.UP :

   az = -1;
     break;
    case Keyboard.DOWN :
     az = 1;
     break;
    case Keyboard.LEFT :
     ax = 1;
     break;
    case Keyboard.RIGHT :
     ax = -1;
     break;

   case Keyboard.SPACE :
     ay = 1;
     break;
    default :
     break;
   }
  }
   private function onKeyUp(event:KeyboardEvent):void {
   switch (event.keyCode) {
    case Keyboard.UP :
    case Keyboard.DOWN :
     az = 0;
     break;
    case Keyboard.LEFT :
    case Keyboard.RIGHT :
     ax = 0;
     break;
    case Keyboard.SPACE :
     ay = 0;
     break;

  default :
     break;
   }
  }
   private function move(tree:Tree):void {
   tree.xpos += vx;
   tree.ypos += vy;
   tree.zpos += vz;
     if (tree.ypos < floor) {
    tree.ypos = floor;
   }
     if (tree.zpos < -fl) {
    tree.zpos += 10000;
   }
     if (tree.zpos > 10000 - fl) {
    tree.zpos -= 10000;
   }
     var scale:Number = fl / (fl + tree.zpos);
     tree.scaleX = tree.scaleY = scale;

   tree.x = vpX + tree.xpos * scale;
   tree.y = vpY + tree.ypos * scale;
  tree.alpha = scale;
 }
 private function sortZ():void {
  trees.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
   for (var i:uint = 0; i < numTrees; i++) {
   var tree:Tree = trees[i];
   setChildIndex(tree, i);
  }

  }
 }
}
     这里,我已经加入了 x  和 y 轴的速度,还有重力。还必需要能够捕获多个按键。我唯
一想念 AS 2  的是 Key.isDown()  方法,任何时间都可以调用找出某个键是否被按住。因为
在 AS 3  中我们只能知道最后一次按下或释放的键,所以不得不判断哪个键被按下并设置相
应轴上的加速度为 1  或 -1。随后,当该键被松开时,再将加速度设回 0。在 onEnterFrame
的开始就将每个轴上的加速度加到相应轴的速度中。左键和右键显然就是用于选择 x  轴的
速度,使用空格键操作 y  轴。有趣的一点是我们实际是从 vy  减去了重力。因为我想要一
个类似于观察者落到树林中的效果,如图 15-8  所示。注意我们同样也限定了树的 y  坐标
为 50,看起来就像是站在陆地上一样。



转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(2)(as3.0)

图 15-8  看,我在飞!
     这里没有对 x  轴的运动加以任何的限制,也就意味着可以在树林边上行进。要想加入
限制对于大家来说也不是件难事,但是作为一个启发性的例子做到这里已经足够了。
缓动与弹性运动
     在 3D 中的缓动与弹性运动不会比 2D  中的难多少(第八章的课题)。我们只需为 z
轴再加入一至两个变量。

 

 
缓动
     对于缓动的介绍不算很多。在 2D  中,我们用 tx  和 ty 最为目标点。现在只需要再在
轴上加入 tz。每帧计算物体每个轴到目标点的距离,并移动一段距离。
     让我们来看一个简单的例子,让物体缓动运动到随机的目标点,到达该点后,再选出另
一个目标并让物体移动过去。注意后面两个例子,我们又回到了 Ball3D  这个类上。以下是
代码(可以在 Easing3D.as  中找到):
package {
 import flash.display.Sprite;
 import flash.events.Event;
  public class Easing3D extends Sprite {
   private var ball:Ball3D;
   private var tx:Number;
   private var ty:Number;
   private var tz:Number;
   private var easing:Number = .1;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;

   private var vpY:Number = stage.stageHeight / 2;
   public function Easing3D() {
   init();
  }
   private function init():void {
   ball = new Ball3D();
   addChild(ball);
     tx = Math.random() * 500 - 250;
     ty = Math.random() * 500 - 250;
     tz = Math.random() * 500;
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
   private function onEnterFrame(event:Event):void {
     var dx:Number = tx - ball.xpos;
     var dy:Number = ty - ball.ypos;
     var dz:Number = tz - ball.zpos;
   ball.xpos += dx * easing;
   ball.ypos += dy * easing;
     ball.zpos += dz * easing;
     var dist:Number = Math.sqrt(dx*dx + dy*dy + dz*dz);

     if (dist < 1) {
       tx = Math.random() * 500 - 250;
       ty = Math.random() * 500 - 250;
       tz = Math.random() * 500;
   }
     if (ball.zpos > -fl) {
    var scale:Number = fl / (fl + ball.zpos);
      ball.scaleX = ball.scaleY = scale;
      ball.x = vpX + ball.xpos * scale;
      ball.y = vpY + ball.ypos * scale;
    ball.visible = true;
   } else {
    ball.visible = false;
   }
  }
 }
}
代码中最有趣的地方是下面这行:

      var dist:Number = Math.sqrt(dx * dx + dy * dy + dz * dz);
我们知道,在 2D  中计算两点间距离的方程是:
      var dist:Number = Math.sqrt(dx * dx + dy * dy);
在 3D  距离中,只需要将第三个轴距离的平方加入进去。由于这个公式过于简单所以我
常会受到质疑。在加入了一个条件后,似乎应该使用立方根。但是它并不是用在这里的。
 
 
 
弹性运动
     弹性运动是缓动的兄弟,需用相似的方法将其调整为 3D  的。我们只使用物体到目标
的距离改变速度,而不是改变位置。给大家一个快速的示例。本例中(Spring3D.as),点击

鼠标将创建出一个随机的目标点。
package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.MouseEvent;
  public class Spring3D extends Sprite {
   private var ball:Ball3D;
   private var tx:Number;
   private var ty:Number;
   private var tz:Number;
   private var spring:Number = .1;
   private var friction:Number = .94;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   public function Spring3D() {
   init();
  }

   private function init():void {
   ball = new Ball3D();
   addChild(ball);
     tx = Math.random() * 500 - 250;
     ty = Math.random() * 500 - 250;
     tz = Math.random() * 500;
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
   stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
  }
   private function onEnterFrame(event:Event):void {
     var dx:Number = tx - ball.xpos;
     var dy:Number = ty - ball.ypos;
     var dz:Number = tz - ball.zpos;
     ball.vx += dx * spring;
     ball.vy += dy * spring;
     ball.vz += dz * spring;
   ball.xpos += ball.vx;
   ball.ypos += ball.vy;
   ball.zpos += ball.vz;
   ball.vx *= friction;
   ball.vy *= friction;
   ball.vz *= friction;

   if (ball.zpos > -fl) {
   var scale:Number = fl / (fl + ball.zpos);
    ball.scaleX = ball.scaleY = scale;
    ball.x = vpX + ball.xpos * scale;
    ball.y = vpY + ball.ypos * scale;
   ball.visible = true;
  } else {
   ball.visible = false;
  }

  }
   private function onMouseDown(event:MouseEvent):void {
     tx = Math.random() * 500 - 250;
     ty = Math.random() * 500 - 250;
     tz = Math.random() * 500;
  }
 }

 




(如果要转载请注明出处http://blog.sina.com.cn/jooi,谢谢)

原创粉丝点击