您的位置:首页 > 其它

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

2011-10-12 20:18 176 查看
Z 排序

在添加了多个物体后代码中显现出了一个新问题---称为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 所示。





图 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 所示。





图 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,看起来就像是站在陆地上一样。





图 15-8 看,我在飞!

这里没有对 x 轴的运动加以任何的限制,也就意味着可以在树林边上行进。要想加入

限制对于大家来说也不是件难事,但是作为一个启发性的例子做到这里已经足够了。

缓动与弹性运动

在 3D 中的缓动与弹性运动不会比 2D 中的难多少(第八章的课题)。我们只需为 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;

}

}

}

(如果要转载请注明出处http://blog.sina.com.cn/jooi,谢谢)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: