Flash基础理论课 第十五章 3D基础 Ⅲ -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

返回“Flash基础理论课 - 目录”

缓动与弹性运动

在3D中的缓动与弹性运动不会比2D中的难多少(第八章的课题),

Flash基础理论课 第十五章 3D基础 Ⅲ

。我们只需要为z轴再加入一至两个变量。

缓动

对于缓动的介绍不算很多。在2D中,我们用tx和ty最为目标点。现在只需要再在z轴上加入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;
  }
 }
}

我们看到,在第三个轴上使用了基本的弹性运动公式(来自第八章)。

坐标旋转

接下来是3D坐标旋转。这里要比第十和十一章中的2D坐标旋转要稍微复杂一些。我们不仅可以在三个不同的轴上旋转,还可以同时在两个以上的轴上进行旋转。

在2D坐标旋转中物体是绕着 z轴旋转的,如图 15-9 所示。想一想命运之轮(Wheel of Fortune)这类游戏——轮轴从轮盘的中心穿过。轮轴就是z轴。只改变x和y坐标。

图15-9  z轴旋转

在3D中,我们也可以在y 或 z轴上旋转。x轴的旋转如同车轮向前滚动,如图 15-10 所示。这时轮轴就是x轴。只改变y和z 上的点。

图15-10 x轴旋转

y轴上的旋转,请想象一下老的唱片机,如图 15-11 所示。轴心是y轴。只改变x和z 上的点。

图15-11 y轴旋转

因此,对于3D 而言,当我们在某个轴上旋转一个物体时,将改变两个轴上的位置。回顾一下第十章,我们找到 2D 旋转的公式如下:

x1 = cos(angle) * x - sin(angle) * y;
y1 = cos(angle) * y + sin(angle) * x;

在3D中,方法几乎相同,但是需要指定操作的是哪个轴:x, y, z。因此得到下面三个公式:

x1 = cos(angleZ) * x - sin(angleZ) * y;
y1 = cos(angleZ) * y + sin(angleZ) * x;
x1 = cos(angleY) * x - sin(angleY) * z;
z1 = cos(angleY) * z + sin(angleY) * x;
y1 = cos(angleX) * y - sin(angleX) * z;
z1 = cos(angleX) * z + sin(angleX) * y;

下面,试一下 y轴的旋转。以下代码可在RotateY.as中找到。创建 50 个 Ball3D 的实例,随机放置舞台上。根据鼠标 x轴上的位置计算出 y轴角度。鼠标越向右,角度值越高。物体就像跟着鼠标旋转一样。

package {
 import flash.display.Sprite;
 import flash.events.Event;
 public class RotateY 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;
  public function RotateY() {
   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.xpos = Math.random() * 200 - 100;
    ball.ypos = Math.random() * 200 - 100;
    ball.zpos = Math.random() * 200 - 100;
    addChild(ball);
   }
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
  private function onEnterFrame(event:Event):void {
   var angleY:Number = (mouseX - vpX) * .001;
   for (var i:uint = 0; i < numBalls; i++) {
    var ball:Ball3D = balls[i];
    rotateY(ball, angleY);
   }
   sortZ();
  }
  private function rotateY(ball:Ball3D, angleY:Number):void {
   var cosY:Number = Math.cos(angleY);
   var sinY:Number = Math.sin(angleY);
   var x1:Number = ball.xpos * cosY - ball.zpos * sinY;
   var z1:Number = ball.zpos * cosY + ball.xpos * sinY;
   ball.xpos = x1;
   ball.zpos = z1;
   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);
   }
  }
 }
}

重要的部分加粗表示。获得一个角度,然后使用该角度调用rotateY 的方法。该方法中,获得角度的正弦和余弦值,执行旋转,将 x1和z1 再赋值给 ball.xpos和ball.zpos。接下来,就是标准透视以及 z 排序。运行结果如图 15-12 所示。

图15-12  y轴旋转

如果这个程序没问题,大家一定也可以将其转换为x轴的旋转。只需要改变onEnterFrame. 方法并加入rotateX 方法:

private function onEnterFrame(event:Event):void {
 var angleX:Number = (mouseY - vpY) * .001;
 for (var i:uint = 0; i < numBalls; i++) {
  var ball:Ball3D = balls[i];
  rotateX(ball, angleX);
 }
 sortZ();
}
private function rotateX(ball:Ball3D, angleX:Number):void {
 var cosX:Number = Math.cos(angleX);
 var sinX:Number = Math.sin(angleX);
 var y1:Number = ball.ypos * cosX - ball.zpos * sinX;
 var z1:Number = ball.zpos * cosX + ball.ypos * sinX;
 ball.ypos = y1;
 ball.zpos = z1;
 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;
 }
}

现在,angleX 是根据鼠标的y坐标确定的,

电脑资料

Flash基础理论课 第十五章 3D基础 Ⅲ》(https://www.unjs.com)。然后计算出正余弦的值,并使用它们算出 y1和z1,再赋值给 ypos和zpos 属性。

接下来,我们将两种旋转组合起来。以下是代码(可以在RotateXY.as中找到):

package {
 import flash.display.Sprite;
 import flash.events.Event;
 public class RotateXY 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;
  public function RotateXY() {
   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.xpos = Math.random() * 200 - 100;
    ball.ypos = Math.random() * 200 - 100;
    ball.zpos = Math.random() * 200 - 100;
    addChild(ball);
   }
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
  private function onEnterFrame(event:Event):void {
   var angleX:Number = (mouseY - vpY) * .001;
   var angleY:Number = (mouseX - vpX) * .001;
   for (var i:uint = 0; i < numBalls; i++) {
    var ball:Ball3D = balls[i];
    rotateX(ball, angleX);
    rotateY(ball, angleY);
    doPerspective(ball);
   }
   sortZ();
  }
  private function rotateX(ball:Ball3D, angleX:Number):void {
   var cosX:Number = Math.cos(angleX);
   var sinX:Number = Math.sin(angleX);
   var y1:Number = ball.ypos * cosX - ball.zpos * sinX;
   var z1:Number = ball.zpos * cosX + ball.ypos * sinX;
   ball.ypos = y1;
   ball.zpos = z1;
  }
  private function rotateY(ball:Ball3D, angleY:Number):void {
   var cosY:Number = Math.cos(angleY);
   var sinY:Number = Math.sin(angleY);
   var x1:Number = ball.xpos * cosY - ball.zpos * sinY;
   var z1:Number = ball.zpos * cosY + ball.xpos * sinY;
   ball.xpos = x1;
   ball.zpos = z1;
  }
  private function doPerspective(ball:Ball3D):void {
   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);
   }
  }
 }
}

与前一个例子相比主要的变化用粗体表示。现在,我们计算出 angleY, angleX ,并调用rotateX, rotateY。注意,我将透视的代码从 rotate 方法中分离到一个单独的方法中,因为它不需要被调用两次。我相信根据前面的公式,您一定可以自行加入rotateZ 方法。

测试一下这个影片。通过将 3D坐标旋转与前一节赛车游戏的“屏幕环绕”的概念相结合,大家一定还可以创建出丰富的,交互性的3D 环境。

碰撞检测

本章的最后我要给大家介绍一下 3D 的碰撞检测。在Flash 的三维空间中唯一可行的方法就是距离碰撞检测。找出两个物体的距离(使用3D 距离公式),如果小于两个物体的半径之和,就产生了碰撞。

我将前面 3D 反弹的例子改成了 3D 碰撞检测的例子,加入了少量的物体并扩大了一些空间。代码中首先执行普通的3D 运动及透视,然后做一个双重循环比较所有小球的位置。如果有两个物体的距离小于它们的半径之和,就使用颜色转换代码将它们都设置为蓝色。非常简单。以下是代码(Collision3D.as):

package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.geom.ColorTransform;
 public class Collision3D extends Sprite {
  private var balls:Array;
  private var numBalls:uint = 20;
  private var fl:Number = 250;
  private var vpX:Number = stage.stageWidth / 2;
  private var vpY:Number = stage.stageHeight / 2;
  private var top:Number = -200;
  private var bottom:Number = 200;
  private var left:Number = -200;
  private var right:Number = 200;
  private var front:Number = 200;
  private var back:Number = -200;
  public function Collision3D() {
   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.xpos = Math.random() * 400 - 200;
    ball.ypos = Math.random() * 400 - 200;
    ball.zpos = Math.random() * 400 - 200;
    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);
   }
   for (i = 0; i < numBalls - 1; i++) {
    var ballA:Ball3D = balls[i];
    for (var j:uint = i + 1; j < numBalls; j++) {
     var ballB:Ball3D = balls[j];
     var dx:Number = ballA.xpos - ballB.xpos;
     var dy:Number = ballA.ypos - ballB.ypos;
     var dz:Number = ballA.zpos - ballB.zpos;
     var dist:Number = Math.sqrt(dx*dx + dy*dy + dz*dz);
     if (dist < ballA.radius + ballB.radius) {
      var blueTransform.:ColorTransform. =
      new ColorTransform(0, 1, 1, 1, 0, 0, 255, 0);
      ballA.transform.colorTransform. = blueTransform;
      ballB.transform.colorTransform. = blueTransform;
     }
    }
   }
   sortZ();
  }
  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;
   }
  }
  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);
   }
  }
 }
}

重要的部分用粗体表示。小球起初都是红色的,当它们碰撞后,颜色发生改变。最后,全部变为蓝色。

本章重要公式

本章的重要公式是3D 透视,坐标旋转以及距离的计算。

基本透视法:

scale = fl / (fl + zpos);
sprite.scaleX = sprite.scaleY = scale;
sprite.alpha = scale; // 可选
sprite.x = vanishingPointX + xpos * scale;
sprite.y = vanishingPointY + ypos * scale;

Z 排序:

// 假设有一个带有 zpos 属性的3D 物体的数组
objectArray.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
for(var i:uint = 0; i < numObjects; i++) {
    setChildIndex(objectArray[i], i);
}

坐标旋转:

x1 = cos(angleZ) * xpos - sin(angleZ) * ypos;
y1 = cos(angleZ) * ypos + sin(angleZ) * xpos;
x1 = cos(angleY) * xpos - sin(angleY) * zpos;
z1 = cos(angleY) * zpos + sin(angleY) * xpos;
y1 = cos(angleX) * ypos - sin(angleX) * zpos;
z1 = cos(angleX) * zpos + sin(angleX) * ypos;

3D 距离:

dist = Math.sqrt(dx * dx + dy * dy + dz * dz);

最新文章