加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-15 06:44:10 所属栏目:百科 来源:网络整理
导读:??前面我们做的一切都是二维的(有时只有一维),但是已经可以做出非常酷的东东了。 现在,将它们带入到下一个等级。 ? ? ? ? 创建 3D? 图形总是那么另人兴奋。新加入的这个维度似乎将物体真正地带入到了生活 中。如何在 Flash? 中实现 3D? 在无数的书籍和教

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

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

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

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



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

?

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

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

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

图 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,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
所示。现在当我们移动鼠标时,小球也会随之移动,但是移动的距离很小,产生了视差效应。

图 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,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,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,radius);
?? graphics.endFill();
? }
?}
}
???? 我们看到,这里所做的就是加入了每个轴的位置和速度的属性。同样,将类中的属性设
置为 public 实在不是一个好的面向对象程序设计的习惯,但是现在我们只是为了能够简单
地说明公式才这么做的。在 MultiBounce3D.as 中,创建了 50? 个新类的实例。每个实例都
有自己的? xpos,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? 所示,
这样我们就可以看出这个空间的形状了。

图 15-5 3D 小球反弹?


?

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读