Flash/Flex学习笔记(43):3D基础
之前我们所做的动画都是基于x,y二维坐标轴的,在三维动画中我们还需要增加一个垂直于屏幕“向里”或“向外”的Z轴,那么z轴到底是应该向外,还是向里呢?这个其实无所谓,不过为了统一,习惯上通常把z轴约定为向里,即所谓的“右手坐标系”

右手坐标系的得名:伸出右手,让食指、中指、大拇指相互垂直;然后 食指指向x轴正向,中指指向y轴正向,则大拇指所指方向即为z轴正向。(事实上这个姿势酷似周杰伦周董的招牌动作)
三维透视的基本规则:
物体在Z轴上的坐标越大(越远),则看起来越小(将上图坐标系旋转,把z轴转到x轴方向后,得到下图),如果距离足够远,则物体将消失于屏幕上的某个特定点(通常称为“消失点”)

技术上的主要处理:动态调整物体的scaleX与scaleY(同时因为物体的大小改变后,相应的x,y坐标值通常也会改变,所以x,y坐标也要做相应调整以符合透视规则),基本公式如下:
scale = fl/(fl+z)
?var ball:Ball = new Ball();
addChild(ball);
?
//观察点 相对于 消失点的坐标
?var xPos:Number=0;
?var yPos:Number=0;
?var zPos:Number=0;
?var fl:Number=250;//焦距
?//消失点
?var vpX:Number=stage.stageWidth/2;
?var vpY:Number=stage.stageHeight/2;
?addEventListener(Event.ENTER_FRAME, EnterFrameHandler);
?stage.addEventListener(KeyboardEvent.KEY_DOWN, KeyDownHandler);
?stage.addEventListener(MouseEvent.MOUSE_WHEEL,MouseWheelHandler);
?//鼠标滚轮事件(注:必须让stage获取焦点时-即用鼠标在动画上点击一下,该事件才会触发,另外还要注意:嵌入网页时,浏览器也会响应鼠标滚轮)
?function MouseWheelHandler(e:MouseEvent):void {
?zPos += (e.delta*5);
?}
?function EnterFrameHandler(event:Event):void {
if (zPos> -fl) {
?ball.visible=true;
?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;
?} else {
?ball.visible=false;
?}
?//辅助线
graphics.clear();
?graphics.lineStyle(1,0xcccccc);
?graphics.moveTo(vpX,vpY);???
?graphics.lineTo(vpX,ball.y);
?graphics.moveTo(vpX,vpY);
graphics.lineTo(ball.x,vpY);
?
graphics.lineStyle(1,0x0000ff,0.5);
graphics.moveTo(vpX,vpY);
?graphics.lineTo(ball.x,ball.y);
?graphics.lineStyle(1,0xff0000,0.5);
?graphics.moveTo(ball.x,ball.y);
?graphics.lineTo(mouseX,mouseY);?
?}
//键盘事件
?function KeyDownHandler(e:KeyboardEvent):void {
if (e.keyCode==Keyboard.UP) {
?zPos+=5;
?} else if (e.keyCode == Keyboard.DOWN) {
?zPos-=5;
}
?}
这个示例中,"鼠标所在位置"充当了"观察点"(即前面示意图中的"人眼"位置),电脑屏幕所在平面即物体的成像面,用键盘上下键可调整小球在Z轴上的位置,然后移动鼠标位置,通过辅助线观察变化。
基本的3D运动:
package {
?import flash.display.Sprite;
?import flash.events.Event;
?import flash.events.KeyboardEvent;
?import flash.ui.Keyboard;
?import flash.display.StageAlign;
?import flash.display.StageScaleMode;
?public class Velocity3D extends Sprite {
?private var ball:Ball;
?//相当于消失点的坐标
?private var xpos:Number=0;
private var ypos:Number=0;
private var zpos:Number=0;
//x,y,z三轴上的速度分量
?private var vx:Number=0;
?private var vy:Number=0;
?private var vz:Number=0;
private var friction:Number=0.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 {
?stage.align = StageAlign.TOP_LEFT;??
?stage.scaleMode = StageScaleMode.NO_SCALE;
?ball = new Ball(20);
addChild(ball);
addEventListener(Event.ENTER_FRAME, EnterFrameHandler);
?
stage.addEventListener(KeyboardEvent.KEY_DOWN, KeyDownHandler);
?}
?private function EnterFrameHandler(event:Event):void {
?vpX =stage.stageWidth/2;
?vpY =stage.stageHeight/2;
?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;
}
?//辅助线
graphics.clear();
graphics.lineStyle(1,0xefefef);
graphics.moveTo(0,stage.stageHeight/2);
graphics.lineTo(stage.stageWidth,stage.stageHeight/2);
graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2-8);
?graphics.moveTo(stage.stageWidth,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2+8);
?graphics.moveTo(stage.stageWidth/2,0);
?graphics.lineTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2-8,stage.stageHeight-15);
?graphics.moveTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2+8,stage.stageHeight-15);
graphics.lineStyle(1,0xdadada);
?graphics.moveTo(vpX,vpY);
graphics.lineTo(ball.x,ball.y);
?}
?private function KeyDownHandler(e:KeyboardEvent):void {
switch (e.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+=0.5;
?break;
case Keyboard.CONTROL :
?vz-=0.5;
?break;
?default :
break;
?}
?}
}
?}
上下左右键控制x,y轴方向速度,shift/ctrl键控制z轴速度(当然这个示例还没考虑到3D环境中的边界反弹,下面就要谈到这个问题)
3维运动反弹:
这个需要一点想象力,二维情况下,通常只要把舞台的四个边当作边界就足够了,但是试想一下:在一个立体的空间里,要限制一个物体的活动范围,得要有6个边界面(上,下,左,右,前,后)
package {
?import flash.display.Sprite;
import flash.events.Event;
import flash.display.StageAlign;
?import flash.display.StageScaleMode;
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()*12-6;
?private var vy:Number=Math.random()*12-6;
?private var vz:Number=Math.random()*12-6;
?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 {
?stage.align = StageAlign.TOP_LEFT;??
stage.scaleMode = StageScaleMode.NO_SCALE;
?ball=new Ball(15);
?addChild(ball);
?addEventListener(Event.ENTER_FRAME, EnterFrameHandler);
?}
private function EnterFrameHandler(event:Event):void {
?vpX =stage.stageWidth/2;
?vpY =stage.stageHeight/2;?
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;
?}
//辅助线
?graphics.clear();
?graphics.lineStyle(1,0xccccff);
graphics.moveTo(0,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2-8);
?graphics.moveTo(stage.stageWidth,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2+8);
?graphics.moveTo(0,stage.stageHeight);
?graphics.lineTo(stage.stageWidth,0);
?graphics.lineTo(stage.stageWidth-15,2);
?graphics.moveTo(stage.stageWidth,0);
?graphics.lineTo(stage.stageWidth-6,13);
?graphics.moveTo(stage.stageWidth/2,0);
?graphics.lineTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2-8,stage.stageHeight-15);
?graphics.moveTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2+8,stage.stageHeight-15);
graphics.lineStyle(1,0xffccff);
?graphics.moveTo(vpX,vpY);
?graphics.lineTo(ball.x,ball.y);
?}
?}
?}
也许这样看得并不清楚,再加入更多的小球反弹,可能更容易理解一些,不过为了方便代码处理,先定义一个新的小球类: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.lineStyle(0);
?graphics.beginFill(color);
?graphics.drawCircle(0, 0, radius);
?graphics.endFill();
}
?}
}
多个小球的3D反弹:
package {
?import flash.display.Sprite;
?import flash.events.Event;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
?public class MultiBounce3D 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=-120;
?private var bottom:Number=120;
?private var left:Number=-120;
?private var right:Number=120;
?private var front:Number=120;
?private var back:Number=-120;
?public function MultiBounce3D() {
?init();
}
?private function init():void {
?stage.align = StageAlign.TOP_LEFT;??
?stage.scaleMode = StageScaleMode.NO_SCALE;
?balls = new Array();
for (var i:uint = 0; i < numBalls; i++) {
?var ball:Ball3D=new Ball3D(15,Math.random()*0xffffff);
?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 {
?vpX =stage.stageWidth/2;
?vpY =stage.stageHeight/2;?
?graphics.clear();
?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;
?//6边界检测
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;
?}
//转换化2D坐标
?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;
?}
?//辅助线???????????
?graphics.lineStyle(1,0xccccff);
graphics.moveTo(0,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2-8);
graphics.moveTo(stage.stageWidth,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2+8);
graphics.moveTo(0,stage.stageHeight);
graphics.lineTo(stage.stageWidth,0);
?graphics.lineTo(stage.stageWidth-15,2);
?graphics.moveTo(stage.stageWidth,0);
?graphics.lineTo(stage.stageWidth-6,13);
graphics.moveTo(stage.stageWidth/2,0);
?graphics.lineTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2-8,stage.stageHeight-15);
graphics.moveTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2+8,stage.stageHeight-15);
?graphics.lineStyle(1,0xff99ff);
?graphics.moveTo(vpX,vpY);
?
graphics.lineTo(ball.x,ball.y);
?}
?}
}
仔细观察一下,相信不少人会发现问题:物体的前后顺序不对,远处的物体居然挡住了近处的物体。(css中可以通过z-Index来调整,silverlight的canvas中也有类似的zIndex,但在As3中如何做呢?)
先跑一下题,来看一个小技巧:Object 数组的排序
var arrTest = [{age:20,name:"a"},{age:50,name:"b"},{age:30,name:"c"}]
?arrTest.sortOn("age",Array.DESCENDING);//按age值倒排
?for(var i:int=0,j=arrTest.length;i<j;i++){
?trace(arrTest[i].age ,arrTest[i].name);
?}
是不是很好用!
ok,问题解决了:Flash的显示列表中,最后被addChild的物体总是显示在上面,在Flash内部"舞台上的每个物体"都对应一个索引值,随着物体不断被添加到舞台上,其对应的索引值也不断增加,我们可以通过调整索引值来改变物体的显示顺序.
基本测试:
?var ballA = new Ball(50);
?ballA.x = stage.stageWidth/2;
?
ballA.y = stage.stageHeight/2;
addChild(ballA);
var ballB = new Ball(45,0x0000ff);
?ballB.x = ballA.x;
?ballB.y = ballA.y + 20;
?addChild(ballB);
?btn1.addEventListener(MouseEvent.MOUSE_DOWN,btn1Click);
?function btn1Click(e:MouseEvent):void{
?setChildIndex(ballB,0);
?setChildIndex(ballA,1);
?}
?btn2.addEventListener(MouseEvent.MOUSE_DOWN,btn2Click);
?
function btn2Click(e:MouseEvent):void{
?setChildIndex(ballB,1);
?setChildIndex(ballA,0);
?}
调整后的3D反弹
?package {
?import flash.display.Sprite;
?import flash.events.Event;
?import flash.display.StageAlign;
?import flash.display.StageScaleMode;
?public class MultiBounce3D 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=-120;
?private var bottom:Number=120;
?private var left:Number=-120;
private var right:Number=120;
private var front:Number=120;
?private var back:Number=-120;
public function MultiBounce3D() {
init();
?}
private function init():void {
?stage.align=StageAlign.TOP_LEFT;
?stage.scaleMode=StageScaleMode.NO_SCALE;
?balls = new Array();
for (var i:uint = 0; i < numBalls; i++) {
?var ball:Ball3D=new Ball3D(15,Math.random()*0xffffff);
?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 {
vpX=stage.stageWidth/2;
?vpY=stage.stageHeight/2;
graphics.clear();
for (var i:uint = 0; i < numBalls; i++) {
?var ball:Ball3D=balls[i];
?move(ball);
?}
sortZ();
}
?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);
}
}
private function move(ball:Ball3D):void {
?var radius:Number=ball.radius;
ball.xpos+=ball.vx;
ball.ypos+=ball.vy;
?ball.zpos+=ball.vz;
?//6边界检测
?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;
?}
//转换化2D坐标
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;
}
?//辅助线
?graphics.lineStyle(1,0xccccff);
?graphics.moveTo(0,stage.stageHeight/2);
graphics.lineTo(stage.stageWidth,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2-8);
?graphics.moveTo(stage.stageWidth,stage.stageHeight/2);
?graphics.lineTo(stage.stageWidth-15,stage.stageHeight/2+8);
?graphics.moveTo(0,stage.stageHeight);
?graphics.lineTo(stage.stageWidth,0);
?graphics.lineTo(stage.stageWidth-15,2);
?graphics.moveTo(stage.stageWidth,0);
?graphics.lineTo(stage.stageWidth-6,13);
?
graphics.moveTo(stage.stageWidth/2,0);
?graphics.lineTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2-8,stage.stageHeight-15);
graphics.moveTo(stage.stageWidth/2,stage.stageHeight);
?graphics.lineTo(stage.stageWidth/2+8,stage.stageHeight-15);
graphics.lineStyle(1,0xff99ff);
?graphics.moveTo(vpX,vpY);
graphics.lineTo(ball.x,ball.y);
?}
?}
?}
3D粒子喷射:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.display.StageAlign;
?import flash.display.StageScaleMode;
?//设置动画背景为黑色?
[SWF(backgroundColor=0x000000)]//c#中的特性? 哈
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=50;//y轴反弹的边界(相对消失点而言)
?private var bounce:Number=-0.6;
?public function Fireworks() {
?init();
?}
?private function init():void {
?stage.scaleMode = StageScaleMode.NO_SCALE;
?stage.align = StageAlign.TOP_LEFT;
?balls = new Array();
?for (var i:uint = 0; i < numBalls; i++) {
?var ball:Ball3D=new Ball3D(5,Math.random()*0xffffff);
?
balls.push(ball);???????????????
?addChild(ball);
}
?initVelocity();
?addEventListener(Event.ENTER_FRAME, onEnterFrame);
?}
?private function initVelocity():void{
?for (var i:uint = 0; i < numBalls; i++) {
?var ball:Ball3D=balls[i];
?reset(ball);
?}???????????
?}
?private function reset(b:Ball3D):void{
?b.ypos=-250;
?b.zpos=200;?????
?b.xpos=0;
?b.vx=(Math.random()*2-1)*3 //x轴方向速度为-3到+3的随机值(即:看起来有的球向左,有的球向右,在横向扩散)
?b.vy=(Math.random()-1)*4; //y轴方向为-4到0之间的随机值(即向下掉)
?b.vz=(Math.random()-1)*3;? //z轴方向速度为-3到0的随机值(即:所有球从远处向近处喷)
?}
?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.alpha = scale;//越远的物体,越淡
?if (ball.x<0 || ball.x>stage.stageWidth || ball.y>stage.stageHeight || ball.alpha<0.05){
?reset(ball);
?}
?ball.visible=true;
} else {
?ball.visible=false;
?reset(ball);
?}
}
?private function sortZ():void {
?balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
?for (var i:uint = 0; i < numBalls; i++) {
?var ball:Ball3D=balls[i];
?