Flex:3D Introduction
PS:此篇为转文,原文链接:http://www.cnblogs.com/yjmyzz/archive/2010/05/08/1730697.html
三维坐标系:右手法则坐标系
三维透视基本规则:
物体坐标在Z轴上越大(越远),则看起来越小,如果距离足够远,则物体消失于屏幕上的某个特定点(“消失点”)
技术上的主要处理:动态调整物体的scaleX与scaleY(同时因物体大小改变后,相应的x,y坐标值通常也会改变,所以x,y坐标也要做相应调整以符合透视规则),基本公式如下:
scale = fl/(fl+z)
package { import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; public class Base3D extends Sprite { public function Base3D() { 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,0X000FF, 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 += 50; else if (e.keyCode == Keyboard.DOWN) zPos -= 50; } } } }
此例中,"鼠标位置"充当"观察点"(即"人眼"位置),键盘↑↓键可调整小球在Z轴上的位置。移动鼠标,通过辅助线观察变化。
package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; public class Base3D 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 Base3D() { 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 -= 10; break; case Keyboard.DOWN : vy += 10; break; case Keyboard.LEFT : vx -= 10; break; case Keyboard.RIGHT : vx += 10; break; case Keyboard.SHIFT : vz += 5; break; case Keyboard.CONTROL : vz -= 5; break; default : break; } } } }
此例中,↑↓←→控制x,y轴方向速度,shift/ctrl控制z轴速度
package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; public class Base3D 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 Base3D() { 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.getRadius(); //左右边界 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; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; public class Base3D 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 Base3D() { 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; //边界检测 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中如何做呢?)
Flash的显示列表中,最后被addChild的物体总是显示在上面,在Flash内部"舞台上的每个物体"都对应一个索引值,随着物体不断被添加到舞台上,其对应的索引值也不断增加,我们可通过调整索引值来改变物体的显示顺序
package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; public class Base3D 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 Base3D() { 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(); } 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); } } 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; } //转换化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); } } }
package { import fl.controls.Label; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; //设置动画背景为黑色 [SWF(backgroundColor = 0X000000)] //c#中的特性 public class Base3D 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 Base3D() { 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)*2 //x轴方向速度为-3到+3的随机值(即:看起来有的球向左,有的球向右,在横向扩散) b.vy = (Math.random()-1)*2; //y轴方向为-4到0之间的随机值(即向下掉) b.vz = (Math.random()-1)*2; //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]; setChildIndex(ball, i); } } } }
Z轴上的屏幕环绕:类似2d中的处理,当物体在z轴的坐标达到某一限定值时,重新将其设置为相反值,通俗点讲:物体太远(接近看不见时),重新将其放在无限近的地方(通常是观察者背后),物体太近(甚至跑到观察者背后时),重新将其放在无限远处.
package { import fl.controls.Label; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; //设置动画背景为黑色 [SWF(backgroundColor = 0X000000)] //c#中的特性 public class Base3D extends Sprite { public function Base3D() { var trees:Array; var numTrees:uint=100; var fl:Number=250; var vpX:Number=stage.stageWidth/2; var vpY:Number=stage.stageHeight/2; var floor:Number=50; var vz:Number=0; var friction:Number=0.98; var temp:Number=0; function init():void { trees = new Array(); for (var i:uint = 0; i < numTrees; i++) { var tree:Tree = new Tree(); trees.push(tree); tree.x = Math.random()*2000-1000; tree.y = floor; tree.z = Math.random()*10000; addChild(tree); } addEventListener(Event.ENTER_FRAME, EnterFrameHandler); stage.addEventListener(KeyboardEvent.KEY_DOWN, KeyDownHandler); } function EnterFrameHandler(event:Event):void { for (var i:uint = 0; i < numTrees; i++) { var tree:Tree=trees[i]; move(tree); } vz*=friction; sortZ(); //自动播放的处理 if (temp > 500) { vz-=0.5; } else { vz+=0.5; } temp++; if (temp == 1000){ temp = 0; } } //按上下键时的速度处理 function KeyDownHandler(event:KeyboardEvent):void { if (event.keyCode==Keyboard.UP) { vz-=1; } else if (event.keyCode == Keyboard.DOWN) { vz+=1; } } function move(tree:Tree):void { tree.z += vz; //如果物体跑到观察点后面了,则重新将其放在无限远处 if (tree.z<fl*-1) { tree.z+=10000; } //如果物体跑得太远了,重新将其放在观察点身后 if (tree.z>10000-fl) { tree.z-=10000; } var scale:Number = fl / (fl + tree.z); tree.scaleX=tree.scaleY=scale; tree.x=vpX+tree.x*scale; tree.y=vpY+tree.y*scale; tree.alpha=scale*0.8+0.2; } //z轴排序 function sortZ():void { trees.sortOn("z", Array.DESCENDING | Array.NUMERIC); for (var i:uint = 0; i < numTrees; i++) { var tree:Tree=trees[i]; setChildIndex(tree, i); } } init(); } } }
这个示例中用到了一个新的类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); } } }
上面这个示例的加强版:(左右键控制x轴加速度,上下键控制z轴加速度,空格键整体下落)
package { import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.ui.Keyboard; [SWF(backgroundColor=0x000000)] public class Base3D 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 Base3D() { 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, KeyDownHandler); stage.addEventListener(KeyboardEvent.KEY_UP, KeyUpHandler); } private function onEnterFrame(event:Event):void { vx+=ax; vy+=ay; vz+=az; vy-=gravity;//负重力?哈,好好理解一下,这一行结合后面的y轴坐标检测,才保证了所有树在空格键松开时,能在y轴方向上恢复原状 for (var i:uint = 0; i < numTrees; i++) { var tree:Tree=trees[i]; move(tree); } vx*=friction; vy*=friction; vz*=friction; sortZ(); } //键盘按下时,处理加速度 private function KeyDownHandler(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; } } //按键抬起时,加速度置0 private function KeyUpHandler(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; //y轴上的坐标判断,否则所有树将一直向上跑 if (tree.ypos<floor) { tree.ypos=floor; } //z轴屏幕环绕 if (tree.zpos<fl*-1) { 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*0.8 + 0.2; } //z轴排序 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); } } } }
3D缓动:
package { import flash.display.Sprite; import flash.events.Event; public class Base3D extends Sprite { private var balls:Array; private var ballNum:Number=20; 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 Base3D() { init(); } private function init():void { balls=new Array(ballNum); for (var i:int=0; i<ballNum; i++) { balls[i]=new Ball3D(20,Math.random()*0xffffff); balls[i].tx=(Math.random()*2-1)*200; balls[i].ty=(Math.random()*2-1)*200; balls[i].tz=(Math.random()*2-1)*500; addChild(balls[i]); } addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { for (var i:int=0; i<ballNum; i++) { move(balls[i]); } sortZ(); } private function move(b:Ball3D):void { var dx:Number=b.tx-b.xpos; var dy:Number=b.ty-b.ypos; var dz:Number=b.tz-b.zpos; //缓动公式 b.xpos+=dx*easing; b.ypos+=dy*easing; b.zpos+=dz*easing; var dist:Number=Math.sqrt(dx*dx+dy*dy+dz*dz); if (dist<1) { b.tx=(Math.random()*2-1)*200; b.ty=(Math.random()*2-1)*200; b.tz=(Math.random()*2-1)*500; } if (b.zpos > -fl) { var scale:Number = fl / (fl + b.zpos); b.scaleX=b.scaleY=scale; b.x=vpX+b.xpos*scale; b.y=vpY+b.ypos*scale; b.alpha=scale*0.7+0.3; b.visible=true; } else { b.visible=false; } } //z轴排序 private function sortZ():void { balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC); for (var i:uint = 0; i < ballNum; i++) { var b:Ball3D=balls[i]; setChildIndex(b, i); } } } }
这个示例中,对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 tx:Number=0; public var ty:Number=0; public var tz: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.events.MouseEvent; public class Base3D extends Sprite { private var balls:Array; private var ballNum:Number=20; 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; private var temp:Number = 0; public function Base3D() { init(); } private function init():void { balls=new Array(ballNum); for (var i:int=0; i<ballNum; i++) { balls[i]=new Ball3D(20,Math.random()*0xffffff); balls[i].tx=(Math.random()*2-1)*200; balls[i].ty=(Math.random()*2-1)*200; balls[i].tz=(Math.random()*2-1)*300; addChild(balls[i]); } addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { for (var i:int=0; i<ballNum; i++) { move(balls[i]); } sortZ(); temp++; if (temp>=250){ reset(); temp=0; } //trace(temp); } private function move(b:Ball3D):void { var dx:Number=b.tx-b.xpos; var dy:Number=b.ty-b.ypos; var dz:Number=b.tz-b.zpos; b.vx+=dx*spring; b.vy+=dy*spring; b.vz+=dz*spring; b.xpos+=b.vx; b.ypos+=b.vy; b.zpos+=b.vz; b.vx*=friction; b.vy*=friction; b.vz*=friction; if (b.zpos>- fl) { var scale:Number = fl / (fl + b.zpos); b.scaleX=b.scaleY=scale; b.x=vpX+b.xpos*scale; b.y=vpY+b.ypos*scale; b.alpha=scale*0.8+0.2; b.visible=true; } else { b.visible=false; } } private function reset():void { for (var i:int=0; i<ballNum; i++) { balls[i].tx=(Math.random()*2-1)*200; balls[i].ty=(Math.random()*2-1)*200; balls[i].tz=(Math.random()*2-1)*300; } } //z轴排序 private function sortZ():void { balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC); for (var i:uint = 0; i < ballNum; i++) { var b:Ball3D=balls[i]; setChildIndex(b, i); } } } }
3D坐标旋转:其实跟2D坐标旋转几乎完全相同,只不过要指定绕哪个轴旋转
绕z轴旋转
x1 = cos(angleZ) * x - sin(angleZ) * y;
y1 = cos(angleZ) * y + sin(angleZ) * x;
绕y轴旋转
x1 = cos(angleY) * x - sin(angleY) * z;
z1 = cos(angleY) * z + sin(angleY) * x;
绕x轴旋转
y1 = cos(angleX) * y - sin(angleX) * z;
z1 = cos(angleX) * z + sin(angleX) * y;
package { import flash.display.Sprite; import flash.events.Event; public class Base3D 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 Base3D() { init(); } private function init():void { balls = new Array(); for (var i:uint = 0; i < numBalls; i++) { var ball:Ball3D=new Ball3D(10,Math.random()*0xffffff); balls.push(ball); ball.xpos=Math.random()*200-100; ball.ypos=Math.random()*200-100; ball.zpos=(Math.random()*2-1)*100; addChild(ball); } addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { var angleY:Number = (mouseX - vpX) * .0004;//旋转的角度与鼠标的水平位置关联 for (var i:uint = 0; i < numBalls; i++) { var ball:Ball3D=balls[i]; rotateY(ball, angleY); } sortZ(); } //绕y轴旋转 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.alpha = scale*0.8; 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); } } } }
这个示例还可以扩充到3个轴的同时旋转:
package { import flash.display.Sprite; import flash.events.Event; public class Base3D extends Sprite { public function Base3D() { var balls:Array; var numBalls:uint=50; var fl:Number=250; var vpx:Number=stage.stageWidth/2; var vpy:Number=stage.stageHeight/2; function init():void { balls=new Array(numBalls); for (var i:uint=0; i<numBalls; i++) { var ball:Ball3D=new Ball3D(10,Math.random()*0xffffff); balls[i]=ball; ball.xpos = (Math.random()*2-1)*100; ball.ypos = (Math.random()*2-1)*100; ball.zpos = (Math.random()*2-1)*100; addChild(ball); } addEventListener(Event.ENTER_FRAME,EnterFrameHandler); } function EnterFrameHandler(e:Event):void { var dx:Number = mouseX - vpx; var dy:Number = mouseY - vpy; var angleY:Number =dx*0.0005; var angleX:Number =dy*0.0005; var angleZ:Number = Math.sqrt(dx*dx + dy*dy)*0.0005; if (dx>0){angleZ *=-1;}//以鼠标所在点的x坐标相对于消失点的位置为判断依据,左侧z轴正向旋转,右侧z轴反向旋转 for (var i:uint; i<numBalls; i++) { var b:Ball3D=balls[i]; rotateX(b,angleX); rotateY(b,angleY); rotateZ(b,angleZ); doPerspective(b); } sortZ(); } //x轴的坐标旋转 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; } //y轴的坐标旋转 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; } //z轴的坐标旋转 function rotateZ(ball:Ball3D, angleZ:Number):void { var cosZ:Number=Math.cos(angleZ); var sinZ:Number=Math.sin(angleZ); var x1:Number=ball.xpos*cosZ-ball.ypos*sinZ; var y1:Number=ball.ypos*cosZ+ball.xpos*sinZ; ball.xpos=x1; ball.ypos=y1; } //3D透视处理 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.alpha = scale*0.65; ball.visible=true; } else { ball.visible=false; } } //z轴排序 function sortZ():void { balls.sortOn("zpos",Array.DESCENDING|Array.NUMERIC); for (var i:uint=0; i<numBalls; i++) { setChildIndex(balls[i],i); } } init(); } } }
3D碰撞检测:当2球间距离小于二球半径和时,即认为碰撞
package { import flash.display.Sprite; import flash.events.Event; import flash.geom.ColorTransform; public class Base3D extends Sprite { private var balls:Array; private var numBalls:uint=8; 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 Base3D() { init(); } private function init():void { balls = new Array(); for (var i:uint = 0; i < numBalls; i++) { var ball:Ball3D=new Ball3D(10); 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 newTransform:ColorTransform = new ColorTransform(0, 1, 1, 1, Math.random()*255, Math.random()*255, Math.random()*255, 0); ballA.transform.colorTransform=newTransform; ballB.transform.colorTransform=newTransform; } } } 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); } } } }
本节用到的Ball类:
package{ import flash.display.Sprite; public class Ball extends Sprite{ private var radius:Number; private var color:uint; private var vx:Number = 0; //x轴速度 private var vy:Number = 0; //y轴速度 public function Ball(r:Number=50,c:uint=0XFF0000){ this.radius = r; this.color = c; init(); } private function init():void { graphics.beginFill(color); graphics.drawCircle(0, 0, radius); graphics.endFill(); } public function getRadius():Number { return radius; } } }
浙公网安备 33010602011771号