转 第十五章 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,谢谢)
在添加了多个物体后代码中显现出了一个新问题---称为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,谢谢)
相关文章推荐
- 转 第十五章 3D 基础 (1)(as3.0)
- 转 第十五章 3D 基础 (3)(as3.0)
- 在flash cs4以上版本的3D基础 (as3.0)
- AS3.0基础学习笔记(1):开始第一程序
- DirectX 3D_基础之顶点着色器 顶点声明 顶点着色器的输入 顶点着色器的编写和编译 卡通着色 轮廓勾勒
- [云生活]智能DIY的基础准备:3D打印机
- 在vs2010下学《directx9.0 3D 游戏开发编程基础》
- [3D计算机图形学]学习笔记 第一章 计算机图形学中的数学基础
- 3D数学基础 - 坐标系、向量、矩阵
- Unity 3D需要注意的程序基础
- ArcGIS教程:3D表面的基础知识(二)
- OGRE 3D基础五:缓冲输入
- 3D空间基础概念之一:点、向量(矢量)和齐次坐标
- 【学习笔记】3D图形核心基础精炼版-1:入门概念
- Unity3D数学基础之3D坐标系(一)
- 《DirectX 9.0 3D游戏开发编程基础》-第一篇-VS2010下安装配置DirectX9 runtime和SDK
- 3D数学基础:图形与游戏开发(第7章矩阵)笔记
- 3D重建基础
- OpenGL基础图形编程 - OpenGL与3D图形世界
- photoshop cs5基础教程10增强的3d…