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

  前面我们做的一切都是二维的(有时只有一维),但是已经可以做出非常酷的东东了。
现在,将它们带入到下一个等级。
     创建 3D  图形总是那么另人兴奋。新加入的这个维度似乎将物体真正地带入到了生活
中。如何在 Flash  中实现 3D  在无数的书籍和教学软件中都有介绍。但是我不打算跳过这
些内容,我们会很快地将所有基础的知识讲完。随后,将前面章节中讨论的运动效果放到三
维空间中。说得详细些,将给大家介绍速度,加速度,摩擦力,反弹,屏幕环绕,缓动,弹
性运动,坐标旋转以及碰撞检测。
     现在,首先要关注 sprite 影片在 3D  空间中运动,使用透视法计算影片在屏幕上的大
小和位置。当然,sprite 本身是平面的,我们看不到它的背面,侧面,顶面或
两章,我们将学习到点,线,图形和立体图形的 3D  建模。 

第三维度及透视法
    在 3D 背后最重要的理论就是超出 x  和 y  存在的另一个维度。这是表示深度的维度,
通常记为 z。
    Flash  没有内置的 z 维度,但是要想在 ActionScript 中创建它也不是件难事。实际上,
远没有我们前面章节中的内容那么复杂!


     首先,需要确定 z 最是朝哪个方向的:向内或向外。回忆一下第三章讨论的坐标系统,
它比普通的坐标系统在某些地方是相反的。y  轴向下,而非向上,角度则是以顺时针方向而
定的,而非逆时针方向。
    因此,当物体远离或接近我们的时候,是否应该让物体 z  轴上位置增加?没有必要去
比较哪个更正确。事实上,这个课题已经被讨论许久了,人们甚至为了描述这两种方法分别
给它们取了名字:左手系统和右手系统。
     伸出您的右手,让拇指与食指构成一个 L 形,然后将中指弯曲 90  度,每个手指都将
指向一个维度。现在,将您的食指指向 x  轴的正半轴,中指指向 y  轴的正半轴。在右手坐
标系中,拇指的指向就是 z  轴的正半轴方向。对于 Flash 而言,意味着物体远离观察者时
轴将增大,临近观察者时 z  轴将减小,如图 15-1  所示。 
 转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(1)(as3.0)

图 15-1  右手坐标系
     如果我们用左手来试的话,得到的结果则是相反的。如图 15-2  所示,左手坐标系。



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

图 15-2  左手坐标系
     下面我们使用右手坐标系为例(图 15-1)。没有理由说不能使用左手坐标系,只不过让
轴向内看起来比较好。在 Flash  中创建第三维度(z)的下一个步骤是如何计算模拟透视。

 

透视法
     透视法是指如何表述物体接近或远离我们时的方法。换句话讲,如何让物体看起来更近
或更远。一幅美术作品中可能有大量的表现透视的技巧,这里我们只关注两点:
■  当物体离得远时,会变小。
■  当物体远离时,它们会聚集到一个消失点上。
     大家肯定见过火车驶向地平线时的景象。当我们在 z  轴上移动物体时,需要做两件事:
■  增大或减小物体的比率。
■  让物体接近或远离消失点。
      在二维系统中,我们可以使用屏幕的 x  和 y  坐标作为物体的 x  和 y  坐标。只需要
一对一地映射过来即可。但是在 3D  系统中就行不通了,因为两个物体可以有相同的 x, y

坐标,由于它们的深度不同,会使它们在屏幕上有不同的位置。因此,在 3D  空间中移动
每个物体都需要知道它们各自的 x, y, z  坐标,这是屏幕坐标不能做到的。现在就要用到这
三个量来描述虚拟空间的一个位置。透视法将告诉我们应该将物体放到屏幕的什么位置。
 

透视公式
     让物体的距离更远(增加 z),基本思想是想让它缩放比率接近0,让它的 x, y  坐标集
中到消失点的 0,0  处。幸好,缩放的比率与汇集的比率相同。因此,我们只需要根据给定
的距离计算出这个比率,然后在这两个地方使用它即可。图 15-3  帮助大家解释这个概念。

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

图 15-3  从侧面观察透示图
     我们距离对象有一段距离。有一个观察点:眼睛。有一个成象面,可以想象成电脑的屏
幕。对象与成象面之间有一段距离,这就是 z  的值。最后,距离观察点到成象面还有一段
距离。最后这点最为重要。虽然这段距离不完全等同于摄象机的焦距,但是与它基本相似
因此我通常用变量 fl [焦距:focal length]  表示。下面是这个公式:
       scale = fl / (fl + z)
scale  值通常是介于 0.0 到 1.0  之间的,这就是缩放和汇聚到消失点上的比率。然而,当 z
变为负数时,fl + z  接近 0  而缩放比例接近无穷大。
     拿到这个 scale  的值能做些什么呢?假设在处理一个影片(或 Sprite  的子类),我们将
这个值赋给影片的 scaleX  和 scaleY。然后再用这个因数乘以物体的 x,y  坐标,就可以算
出物体在屏幕上的 x,y 的位置。
     看一个例子。通常情况下 fl  的值在 200  到 300  之间。我们选用 250 这个值。如果 z
等于 0  ——换句话讲,物体就在成象面上---  那么 scale 就等于  250 / (250 + 0)。结果等
于 1.0。这就是 scaleX  和 scaleY  的值(别忘了对于 scaleX  和 scaleY  而言, 1.0  就意味
着 100%)。让物体的 x,y  坐标乘以 1.0,返回的结果不变,因此物体在屏幕上的位置就等
于它自身的 x  和 y。

    现在将物体向外移让 z  等于 250。则让 scale  等于 250 / (250 + 250),scaleX 和 scaleY
等于 0.5。同样也改变了物体在屏幕上的位置。如果原来物体在屏幕上的位置是 200, 300 
么现在就应该是 100, 150。因此,它向着消失点移动了一半的距离。(事实上,屏幕上的位
置是相对于消失点的位置而定的,大家马上会看到)。
     现在,将 z  向外移动到 9750。scale  变成 250 / 10000, scaleX  和 scaleY  等于 0.025。
物体将变成一个小点儿,并且非常接近消失点。
 OK,理论够了。来看代码。
 

ActionScript 透视
     各位也许猜到了,我还要使用 Ball  类。当然,您也可以自由地选择自己喜欢物体,但
是我只专注于代码,将那些酷酷的图形留给大家去做。我们用鼠标和键盘作为交互。使用鼠

标控制小球的 x,y  坐标,方向键的上下键来控制 z  轴的前后方向。注意,因为变量 x,y 
由 ActionScript  持有的,因此我们将使用 xpos, ypos, zpos  代表 3D  坐标。
文档类 Perspective1.as  的代码如下:
package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
  public class Perspective1 extends Sprite {
   private var ball:Ball;
   private var xpos:Number = 0;
   private var ypos:Number = 0;
   private var zpos:Number = 0;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   public function Perspective1() {
   init();
  }
   private function init():void {
   ball = new Ball();
   addChild(ball);

addEventListener(Event.ENTER_FRAME, onEnterFrame);
   stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  }
   private function onEnterFrame(event:Event):void {
     xpos = mouseX - vpX;
     ypos = mouseY - vpY;
     var scale:Number = fl / (fl + zpos);
     ball.scaleX = ball.scaleY = scale;
     ball.x = vpX + xpos * scale;
     ball.y = vpY + ypos * scale;
  }
   private function onKeyDown(event:KeyboardEvent):void {
     if (event.keyCode == Keyboard.UP) {
    zpos += 5;
     } else if (event.keyCode == Keyboard.DOWN) {
    zpos -= 5;
   }
  }
 }
}

首先创建变量  xpos, ypos, zpos, fl。然后创建一个消失点(vanishing point)vpX, vpY。
记住当物体向远处运动一段距离后,就会聚在 0, 0  点。如果不进行偏移,所有物体都会向
屏幕左上角汇集,这并不我们想要的结果。将 vpX, vpY  设置为舞台的中心点,作为消失点。
     接下来,在 onEnterFrame 中设置 xpos  和 ypos  为鼠标与消失点的偏移位置。换句话
讲,如果鼠标在中心点右面 200  像素,x  就等于 200。如果在中心点左面 200  像素的位置,
则等于 -200。
     然后添加 keyDown  事件的侦听,用于改变 zpos。如果方向键上被按下 zpos  增加,

如果方向键上被按下则减小。这将使小球向着观察者更近或更远的方向运动。
     最后,使用刚刚介绍过的公式计算 scale,设置小球的位置与大小。注意小球在屏幕上
的位置 x,y 是根据消失点计算的,还要加上 xpos, ypos  与 scale  的乘积。因此,当 scale 变
得很小时,小球将汇集到消失点上。
     测试一下影片,开始看起来像一个简单的鼠标拖拽。这是因为 zpos  等于 0,scale 
于 1.0。所以注意不到透视的存在。当按下方向键上时,小球向内滑入一段距离,如图 15-4
所示。现在当我们移动鼠标时,小球也会随之移动,但是移动的距离很小,产生了视差效应。

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

图 15-4 ActionScript 透视
     大家也许注意到了,如果长期按住方向键下,小球会变得非常大。这是对的。如果拿起
一小块石子放到眼前,它就会像一块巨石一样大。如果继续按住方向键下,它将变成无限大,
然后又收缩回去,但是这时整个小球已经颠倒或反转过来了。小球跑到了观察点的后面。因
此,如果眼睛可以看到身背后的东西,我猜这一定是我们所看到的。
     用数字解释一下,当 zpos  等于  –fl  时,公式从  scale = fl / (fl + zpos) 变为  scale = fl /
0。在许多语言中,除以 0  会报错。在 Flash 中,将得到一个无限大的值。如果再将 zpos 减
小,那么就是用 fl  除以一个负数。scale  变为负数,这就是为什么小球会颠倒并反向运动
的原因。
     学会了吗?解决方法只需在小球在超过某一点时将其设置为不可见的。如果 zpos  小于
或等于  –fl,会出现问题,因此可以判断一下这个条件,并在下面这个 Perspective2.as 
的 enterFrame  函数中进行处理(其余部分与 Perspective1.as 完全相同):

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

     注意,如果小球不可见,我们就不必考虑缩放和位置问题了。同样还要注意如果小球处
于可见的范围,就要确保它是可见的。虽然可能略些多余的设置,但这是必要的。
     好的,现在我们已经学习了 3D  基础的框架。不是很痛苦吧?一定要测试一下这个影
片,能够很好地掌握它。试改变 fl 的值,观察不同的效果。这就相当于在改变照相机的镜
头。较高的 fl  值就像一个长焦镜头,给我们一个较小的观察空间,以及较少的可见的透视。
较小的 fl  值将给我们一个广角镜头,形成非常广阔的透视。
     本章剩下的部分都是前面章节中介绍过的不同的运动效果,只不过这次是三维的

速度与加速度

实现 3D  的速度与加速度超级简单。对于 2D  而言,我们用 vx  和 vy  变量表示两个
轴的速度。现在只需要再加入 vz  表示第三个轴即可。同样,如果有 ax 和 ay  作为加速度,
那么再添加一个 az  变量即可。
     我们可以将最后一个例子改为小行星太空船这样的游戏,不过是 3D  版的。将它变为
全键盘控制的。方向键可以提供 x,y 轴上的推进,再加入一对儿键 Shift 和 Ctrl  用于 z 
上的推进。
     以下是代码(同样可在 Velocity3D.as  中找到):
package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
  public class Velocity3D extends Sprite {
   private var ball:Ball;
   private var xpos:Number = 0;
   private var ypos:Number = 0;
   private var zpos:Number = 0;
   private var vx:Number = 0;
   private var vy:Number = 0;
   private var vz:Number = 0;
   private var friction:Number = .98;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   public function Velocity3D() {
   init();
  }

private function init():void {
 ball = new Ball();
 addChild(ball);
 addEventListener(Event.ENTER_FRAME, onEnterFrame);
 stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
private function onEnterFrame(event:Event):void {
 xpos += vx;
 ypos += vy;
 zpos += vz;
 vx *= friction;
 vy *= friction;
 vz *= friction;
  if (zpos > -fl) {
   var scale:Number = fl / (fl + zpos);
   ball.scaleX = ball.scaleY = scale;
   ball.x = vpX + xpos * scale;
   ball.y = vpY + ypos * scale;
  ball.visible = true;
 } else {
  ball.visible = false;
 }

}
   private function onKeyDown(event:KeyboardEvent):void {
   switch (event.keyCode) {
    case Keyboard.UP :
     vy -= 1;
     break;
    case Keyboard.DOWN :
     vy += 1;
     break;
    case Keyboard.LEFT :
     vx -= 1;
     break;
    case Keyboard.RIGHT :
     vx += 1;
     break;
    case Keyboard.SHIFT :
     vz += 1;
     break;
    case Keyboard.CONTROL :
     vz -= 1;
     break;
    default :
     break;
   }

 }
 }
}
     我们所要做的就是为每个轴加入速度和摩擦力。当六个键中有一个被按下,将会对速度
进行适当的增加或减少(记住加速度改变速度)。然后将速度加到每个轴上,最后计算摩擦
力。现在我们就有了带有加速度,速度和摩擦力的一个 3D  物体。哇,真是一举多得。说
过这很简单。
 

反弹
     本节我们将讨论平面反弹的问题--换句话讲,是与 x, y, z  轴充分结合的反弹,与 2D
的屏幕边界反弹相似。
 
 
 
单物体反弹
    3D  反弹,同样需要判断物体何时超出了边界,然后将物体调整到边界上,把相应轴上
的速度反转。3D  反弹唯一的不同之处在于如何确定边界。在 2D  中,一般都是用舞台的坐
标或其它一些可见的矩形区域。在 3D  中,就不那么简单了。这里没有真正的可见边界的
概念,除非在三维空间中绘制一个。我们将在下一章学习三维空间中的绘制,因此现在将在
不可见的随意放置的墙壁上进行反弹。
     我们设置的边界和以前相同,只不过现在要把它们放到三维空间中,也就意味着可以是
正的也可以是负的。还可以选择在 z  轴上设置边界。边界大概是这样:
private var top:Number = -250;

private var bottom:Number = 250;
private var left:Number = -250;
private var right:Number = 250;
private var front:Number = 250;
private var back:Number = -250;
     接下来,确定物体的新位置,需要判断是否所与这六个边界产生了碰撞。别忘了我们是
用物体一半的宽度来判断碰撞的,而这个值已经存在了 Ball  类名为 radius  的变量中。以
下是全部 3D  反弹的代码(可见 Bounce3D.as):
package {
 import flash.display.Sprite;
 import flash.events.Event;
  public class Bounce3D extends Sprite {
   private var ball:Ball;
   private var xpos:Number = 0;
   private var ypos:Number = 0;
   private var zpos:Number = 0;
   private var vx:Number = Math.random() * 10 - 5;
   private var vy:Number = Math.random() * 10 - 5;
   private var vz:Number = Math.random() * 10 - 5;
   private var fl:Number = 250;

private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   private var top:Number = -100;
   private var bottom:Number = 100;
   private var left:Number = -100;
   private var right:Number = 100;
   private var front:Number = 100;
   private var back:Number = -100;
   public function Bounce3D() {
   init();
  }
   private function init():void {
   ball = new Ball(15);
   addChild(ball);
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
   private function onEnterFrame(event:Event):void {
   xpos += vx;
   ypos += vy;
   zpos += vz;
     var radius:Number = ball.radius;
     if (xpos + radius > right) {
      xpos = right - radius;
    vx *= -1;

} else if (xpos - radius < left) {
      xpos = left + radius;
    vx *= -1;
   }
     if (ypos + radius > bottom) { 
      ypos = bottom - radius;
    vy *= -1;
     } else if (ypos - radius < top) {
    ypos = top + radius;
    vy *= -1;
   }
     if (zpos + radius > front) {
       zpos = front - radius;
    vz *= -1;
     } else if (zpos - radius < back) {
    zpos = back + radius;
    vz *= -1;
   }
     if (zpos > -fl) {
      var scale:Number = fl / (fl + zpos);
      ball.scaleX = ball.scaleY = scale;
      ball.x = vpX + xpos * scale;
      ball.y = vpY + ypos * scale;
    ball.visible = true;
   } else {
    ball.visible = false;
   }
  }

 }
}
     注意,我删掉了所有按键处理的部分,只让小球以随机的速度在每个轴上运动。现在可
以看到小球按照我们旨意进行反弹,但是谁也说不上来反弹在什么东西上——正如我所说
的,这些是任意放置不可见的边界。

 

多物体反弹
     让更多的物体充满整个空间也是对我们看出这些墙壁会有些帮助。为了完成这个目的,
需要很多 Ball  类的实例。每个实例都要有自己的 xpos, ypos, zpos 以及每个轴的速度。为
了让主类(main class)的结构清晰,下面创建了一个新的类 Ball3D,来看一下:
package {
 import flash.display.Sprite;
  public class Ball3D extends Sprite {
   public var radius:Number;
   private var color:uint;
   public var xpos:Number = 0;
   public var ypos:Number = 0;
   public var zpos:Number = 0;
   public var vx:Number = 0;
   public var vy:Number = 0;
   public var vz:Number = 0;
   public var mass:Number = 1;
   public function Ball3D(radius:Number=40, color:uint=0xff0000) {
   this.radius = radius;

 this.color = color;
   init();
  }
   public function init():void {
   graphics.beginFill(color);
   graphics.drawCircle(0, 0, radius);
   graphics.endFill();
  }
 }
}
     我们看到,这里所做的就是加入了每个轴的位置和速度的属性。同样,将类中的属性设
置为 public 实在不是一个好的面向对象程序设计的习惯,但是现在我们只是为了能够简单
地说明公式才这么做的。在 MultiBounce3D.as 中,创建了 50  个新类的实例。每个实例都
有自己的  xpos, ypos, zpos, vx,vy, vz。 onEnterFrame 方法循环获得每个 Ball3D  的引用,然
后将它们传给 move  方法。这个方法与最初的 onEnterFrame  完成的功能相同。代码如下(可
在 MultiBounce3D.as  中找到):

package {
 import flash.display.Sprite;
 import flash.events.Event;
  public class MultiBounce3D extends Sprite {
   private var balls:Array;
   private var numBalls:uint = 50;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   private var top:Number = -100;
   private var bottom:Number = 100;
   private var left:Number = -100;
   private var right:Number = 100;
   private var front:Number = 100;
   private var back:Number = -100;
   public function MultiBounce3D() {
   init();
  }

 private function init():void {
   balls = new Array();
     for (var i:uint = 0; i < numBalls; i++) {
      var ball:Ball3D = new Ball3D(15);
    balls.push(ball);
      ball.vx = Math.random() * 10 - 5;
      ball.vy = Math.random() * 10 - 5;
      ball.vz = Math.random() * 10 - 5;
    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);
   }
  }
   private function move(ball:Ball3D):void {
     var radius:Number = ball.radius;
   ball.xpos += ball.vx;
   ball.ypos += ball.vy;
   ball.zpos += ball.vz;
     if (ball.xpos + radius > right) {
      ball.xpos = right - radius;
    ball.vx *= -1;
     } else if (ball.xpos - radius < left) {
      ball.xpos = left + radius;
    ball.vx *= -1;
   }
     if (ball.ypos + radius > bottom) {
      ball.ypos = bottom - radius;
    ball.vy *= -1;
     } else if (ball.ypos - radius < top) {
      ball.ypos = top + radius;
    ball.vy *= -1;

 }
     if (ball.zpos + radius > front) {
      ball.zpos = front - radius;
    ball.vz *= -1;
     } else if (ball.zpos - radius < back) {
    ball.zpos = back + radius;
    ball.vz *= -1;
   }
     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;
   }
  }
 }
}

运行这个文件后,可以看到小球将六个边界内的大部空间都填满了,如图 15-5  所示,
这样我们就可以看出这个空间的形状了。
转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(1)(as3.0)

图 15-5 3D 小球反弹 


 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值