布朗运动:
varnumDots:uint=50; varfriction:Number=0.9; vardots:Array; varlife:uint=0; functioninit(){ graphics.lineStyle(0,0xffffff,.5); dots=newArray(); for(vari:uint=0;i<numDots;i++){ vardot:Ball=newBall(2,0x00ff00); dot.x=Math.random()*stage.stageWidth; dot.y=Math.random()*stage.stageHeight; dot.vx=0; dot.vy=0; dots.push(dot); addChild(dot); checkBound(dot); } addEventListener(Event.ENTER_FRAME,enterFrameHandler); } //检查边界 functioncheckBound(b:Ball){ if(b.x<b.width/2){ b.x=b.width/2; } elseif(b.x>stage.stageWidth-b.width/2){ b.x=stage.stageWidth-b.width/2; } if(b.y<b.height/2){ b.y=b.height/2; } elseif(b.y>stage.stageHeight-b.height/2){ b.y=stage.stageHeight-b.height/2; } } functionenterFrameHandler(e:Event):void{ //trace(life); if(life>=200){ graphics.clear(); graphics.lineStyle(0,0xffffff,.5); life=0; } for(vari:uint=0;i<numDots;i++){ vardot:Ball=dots[i]; graphics.moveTo(dot.x,dot.y); dot.vx+=Math.random()-0.5; dot.vy+=Math.random()-0.5; dot.x+=dot.vx; dot.y+=dot.vy; dot.vx*=friction; dot.vy*=friction; checkBound(dot); graphics.lineTo(dot.x,dot.y); } life++; } init();
矩形分布:
vardotNumber:uint=500; vardots:Array; varcenterX:uint=stage.stageWidth/2; varcenterY:uint=stage.stageHeight/2; varlimitX:uint=50; varlimitY:uint=100; functioninit():void{ dots=newArray(); for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=newBall(3*Math.random(),0x00ff00); dot.x=centerX+(Math.random()*2-1)*limitX; dot.y=centerY+(Math.random()*2-1)*limitY; addChild(dot); dots.push(dot); } addEventListener(Event.ENTER_FRAME,enterframeHandler); } functionenterframeHandler(e:Event):void{ for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=dots[i]; dot.x=centerX+(Math.random()*2-1)*limitX; dot.y=centerY+(Math.random()*2-1)*limitY; /*varix:Number=dot.x; variy:Number=dot.y; dot.y=ix; dot.x=iy*/ } } init();
圆形随机分布:
vardotNumber:uint=500; vardots:Array; varcenterX:uint=stage.stageWidth/2; varcenterY:uint=stage.stageHeight/2; varradius:uint=75; functioninit():void{ dots=newArray(); for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=newBall(3*Math.random(),0x00ff00); varangle:Number=2*Math.random()*Math.PI; varr:Number=Math.random()*radius; dot.x=centerX+r*Math.cos(angle); dot.y=centerY+r*Math.sin(angle); addChild(dot); dots.push(dot); } addEventListener(Event.ENTER_FRAME,enterframeHandler); } functionenterframeHandler(e:Event):void{ for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=dots[i]; varangle:Number=2*Math.random()*Math.PI; varr:Number=Math.random()*radius; dot.x=centerX+r*Math.cos(angle); dot.y=centerY+r*Math.sin(angle); } } init();
更均匀的圆形随机分布:
vardotNumber:uint=200; varcenterX:uint=stage.stageWidth/2; varcenterY:uint=100; varradius:uint=50; functioninit():void{ for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=newBall(2,0x00ff00); varangle:Number=2*Math.random()*Math.PI; varr:Number=Math.random()*radius; dot.x=centerX+r*Math.cos(angle); dot.y=centerY+r*Math.sin(angle); addChild(dot); } //更均匀的随机分布 for(i=0;i<dotNumber;i++){ vardot1:Ball=newBall(2,0x00ff00); varangle1:Number=2*Math.random()*Math.PI; varr1:Number=Math.sqrt(Math.random())*radius;//关键在这里,对Math.random()取平方根后,分布变得更均匀了 dot1.x=centerX+r1*Math.cos(angle1); dot1.y=centerY+200+r1*Math.sin(angle1); addChild(dot1); } } init();
偏向分布:(即在指定的区域内,中心位置分布最密集,离中心越远,分布越稀疏)
vardotNumber:uint=600; varcenterX:uint=stage.stageWidth/2; varmaxWidth:uint=75; varballs:Array=newArray(); functioninit():void{ for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=newBall(2,0x00ff00); //在y轴方向上随便取二个值,然后计算平均值做为y坐标 vary1=stage.stageHeight*Math.random(); vary2=stage.stageHeight*Math.random(); varty=(y1+y2)/2; //x轴做类似的处理 varx1=centerX+(Math.random()*2-1)*maxWidth; varx2=centerX+(Math.random()*2-1)*maxWidth; vartx=(x1+x2)/2; dot.x=tx; dot.y=ty; addChild(dot); balls.push(dot); } stage.frameRate=1; addEventListener(Event.ENTER_FRAME,enterFrameHandler); } init(); functionenterFrameHandler(e:Event){ for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=balls[i]; vary1=stage.stageHeight*Math.random(); vary2=stage.stageHeight*Math.random(); varty=(y1+y2)/2; varx1=centerX+(Math.random()*2-1)*maxWidth; varx2=centerX+(Math.random()*2-1)*maxWidth; vartx=(x1+x2)/2; dot.x=tx; dot.y=ty; } }
多次迭代的偏向分布(类似星云分布)
vardotNumber:uint=100; variterations:uint=6; varballs:Array=newArray(); functioninit():void{ for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=newBall(2,0x00ff00); varxpos:Number=0; varypos:Number=0; for(varj:uint=0;j<iterations;j++){ xpos+=stage.stageWidth*Math.random(); ypos+=stage.stageHeight*Math.random(); } dot.x=xpos/iterations; dot.y=ypos/iterations; addChild(dot); balls.push(dot); } stage.frameRate=1; addEventListener(Event.ENTER_FRAME,enterFrameHandler); } init(); functionenterFrameHandler(e:Event){ for(vari:uint=0;i<dotNumber;i++){ vardot:Ball=balls[i]; varxpos:Number=0; varypos:Number=0; for(varj:uint=0;j<iterations;j++){ xpos+=stage.stageWidth*Math.random(); ypos+=stage.stageHeight*Math.random(); } dot.x=xpos/iterations; dot.y=ypos/iterations; } }
Timer类的重绘设置:
varball:Ball=newBall(); varvx:Number=5; vartimer=newTimer(20); stage.frameRate=1;//设置flash动画的帧数为1帧/秒 ball.y=stage.stageHeight/2; ball.vx=5; addChild(ball); timer.addEventListener(TimerEvent.TIMER,TimerHandler); functionTimerHandler(e:TimerEvent):void{ ball.x+=ball.vx; if(ball.x>stage.stageWidth+ball.width/2){ ball.x=-ball.width/2; } e.updateAfterEvent();//事件触发后,重绘整个stage(建议大家去掉这一行,再看看效果) } timer.start();
注意:timer类的计时并不象c#中那样精确,因为跟帧速有关联。
基于时间的动画:
Flash动画是基于帧的(即每进入一帧时,舞台上的对象才会重绘,并触发Enter_Frame事件),这跟Silverlight是基于时间的设计完全不同。一般情况下,这也不是什么问题,但是这样会在不同配置的机器上可能产生不一致的播放效果,比如“一个简单的小球从左运动到右”的简单动画,如果在ENTER_FRAME事件中,用ball.x+=ball.vx来处理,在老爷机上,可能swf动画只能达到每秒10帧的播放速度,而在4核的高配置机器上,能达到每秒100帧的播放速度。 问题就来了:假如ball.vx为5,则在老爷机上,小球最终每秒移动了5*10=50像素,而在高配置机器上,小球每秒移动了5*100=500像素,这与开发者期望的并不一样,下面的代码演示了如何制作基于时间的动画,最终让小球在不同配置的机器上运动速度达到一致。(注:在这一点上,不得不承认Silverlight的设计要优于Flash)
varball:Ball=newBall(); varvx:Number=5; stage.frameRate=100;//通常在基于时间的动画中,帧数可以设置得高一点(尽管机器最终可能达不到这个帧数.) ball.y=stage.stageHeight/2; ball.vx=10; addChild(ball); vartimer=getTimer(); addEventListener(Event.ENTER_FRAME,enterFrameHandler); functionenterFrameHandler(e:Event):void{ varelapsed:Number=getTimer()-timer;//计算每帧之间间隔的时间差(以毫秒为单位) ball.x+=(ball.vx*elapsed/1000);//将毫秒换算成秒,再乘“速度”,最终的效果即:如果帧数低,动画播放得太慢,则一次多移动一些距离;反之则少移动一些距离,起到了动态调整的目的. if(ball.x>stage.stageWidth+ball.width/2){ ball.x=-ball.width/2; } timer=getTimer(); }
大家可以尝试把上面的帧数设置,改成200或50,然后再测试下播放效果,会发现小球的移动速度是一致的,不受帧数的影响。(但帧数建议不要低于10,因为人眼的视觉暂留极限大概是0.1秒,低于这个值动画看起来会很卡)
另外,这里对比给出Silverlight的对比代码:
usingSystem; usingSystem.Windows; usingSystem.Windows.Controls; usingSystem.Windows.Interop; usingSystem.Windows.Threading; namespaceSilverlightApplication1 { publicpartialclassMainPage:UserControl { DispatcherTimertmr; Ballb; publicMainPage() { InitializeComponent(); this.Loaded+=newRoutedEventHandler(MainPage_Loaded); } voidMainPage_Loaded(objectsender,RoutedEventArgse) { Settingssettings=Application.Current.Host.Settings; settings.EnableFrameRateCounter=true; settings.MaxFrameRate=1; b=newBall();//ball是一个自定义控件,里面就一个圆 c.Children.Add(b); b.SetValue(Canvas.LeftProperty,c.Width/2); b.SetValue(Canvas.TopProperty,c.Height/2); tmr=newDispatcherTimer(); tmr.Interval=newTimeSpan(0,0,0,0,20); tmr.Tick+=newEventHandler(tmr_Tick); tmr.Start(); } voidtmr_Tick(objectsender,EventArgse) { double_left=(double)b.GetValue(Canvas.LeftProperty); b.SetValue(Canvas.LeftProperty,_left+5); } } }
相同质量的小球碰撞:
Flash/Flex学习笔记(43):动量守恒与能量守恒 里,我们学习了如何用AS3.0来模拟小球的运量守恒,但计算也是很复杂的,对于相同质量的碰撞,其实可以实现得更简单一些。基本原理是,两个物体沿着碰撞的线路交换它们的速度(想深究的同学们,可以自己去解方程验证)。这样我们在处理这种特殊情况时,就可以简化一部分计算,完整代码如下:(注意加的部分)
package{ importflash.display.Sprite; importflash.events.Event; importflash.geom.Point; publicclassSameMassextendsSprite{ privatevarballs:Array; privatevarnumBalls:uint=8; privatevarbounce:Number=-1.0; publicfunctionSameMass(){ init(); } privatefunctioninit():void{ balls=newArray(); for(vari:uint=0;i<numBalls;i++){ //varradius:Number=Math.random()*40+10; varradius:Number=20;//把所有质量强制为相同 varball:Ball=newBall(radius,Math.random()*0xffffff); ball.mass=radius; ball.x=i*100; ball.y=i*50; ball.vx=Math.random()*10-5; ball.vy=Math.random()*10-5; addChild(ball); balls.push(ball); } addEventListener(Event.ENTER_FRAME,onEnterFrame); } privatefunctiononEnterFrame(event:Event):void{ for(vari:uint=0;i<numBalls;i++){ varball:Ball=balls[i]; ball.x+=ball.vx; ball.y+=ball.vy; checkWalls(ball); } for(i=0;i<numBalls-1;i++){ varballA:Ball=balls[i]; for(varj:Number=i+1;j<numBalls;j++){ varballB:Ball=balls[j]; checkCollision(ballA,ballB); } } } //舞台边界检测 functioncheckWalls(b:Ball){ if(b.x<b.radius){ b.x=b.radius; b.vx*=bounce; } elseif(b.x>stage.stageWidth-b.radius){ b.x=stage.stageWidth-b.radius; b.vx*=bounce; } if(b.y<b.radius){ b.y=b.radius; b.vy*=bounce; } elseif(b.y>stage.stageHeight-b.radius){ b.y=stage.stageHeight-b.radius; b.vy*=bounce; } } privatefunctionrotate(x:Number,y:Number,sin:Number,cos:Number,reverse:Boolean):Point{ varresult:Point=newPoint(); if(reverse){ result.x=x*cos+y*sin; result.y=y*cos-x*sin; } else{ result.x=x*cos-y*sin; result.y=y*cos+x*sin; } returnresult; } privatefunctioncheckCollision(ball0:Ball,ball1:Ball):void{ vardx:Number=ball1.x-ball0.x; vardy:Number=ball1.y-ball0.y; vardist:Number=Math.sqrt(dx*dx+dy*dy); if(dist<ball0.radius+ball1.radius){ //计算角度和正余弦值 varangle:Number=Math.atan2(dy,dx); varsin:Number=Math.sin(angle); varcos:Number=Math.cos(angle); //旋转ball0的位置 varpos0:Point=newPoint(0,0); //旋转ball1的速度 varpos1:Point=rotate(dx,dy,sin,cos,true); //旋转ball0的速度 varvel0:Point=rotate(ball0.vx,ball0.vy,sin,cos,true); //旋转ball1的速度 varvel1:Point=rotate(ball1.vx,ball1.vy,sin,cos,true); /*//碰撞的作用力 varvxTotal:Number=vel0.x-vel1.x; vel0.x=((ball0.mass-ball1.mass)*vel0.x+2*ball1.mass*vel1.x)/(ball0.mass+ball1.mass); vel1.x=vxTotal+vel0.x;*/ //改成速度交换 vartemp:Point=vel0; vel0=vel1; vel1=temp; //更新位置 varabsV:Number=Math.abs(vel0.x)+Math.abs(vel1.x); varoverlap:Number=(ball0.radius+ball1.radius)-Math.abs(pos0.x-pos1.x); pos0.x+=vel0.x/absV*overlap; pos1.x+=vel1.x/absV*overlap; //将位置旋转回来 varpos0F:Object=rotate(pos0.x,pos0.y,sin,cos,false); varpos1F:Object=rotate(pos1.x,pos1.y,sin,cos,false); //将位置调整为屏幕的实际位置 ball1.x=ball0.x+pos1F.x; ball1.y=ball0.y+pos1F.y; ball0.x=ball0.x+pos0F.x; ball0.y=ball0.y+pos0F.y; //将速度旋转回来 varvel0F:Object=rotate(vel0.x,vel0.y,sin,cos,false); varvel1F:Object=rotate(vel1.x,vel1.y,sin,cos,false); ball0.vx=vel0F.x; ball0.vy=vel0F.y; ball1.vx=vel1F.x; ball1.vy=vel1F.y; } } } }
声音的使用:
声音的使用其实没什么特别的,跟图片,视频等其它资源都差不多.
如上图,在导入一个声音时,可以指定一个类名,然后在代码中,就可以new一个该类的实例了。除此之外,还可以直接加载远程声音,完整代码如下:
varbgMusic=newSound(newURLRequest("http://210.51.38.234/music/sophie_zelmani_Going_Home.mp3")); varstf:SoundTransform=newSoundTransform(); stf.volume=0.3; bgMusic.play(0,0,stf); varbing:Bing=newBing(); varball:Ball=newBall(30); ball.vx=5; ball.vy=5; ball.x=stage.stageWidth/2; ball.y=stage.stageHeight/2; addChild(ball); addEventListener(Event.ENTER_FRAME,EnterFrameHandler); functionEnterFrameHandler(e:Event):void{ ball.x+=ball.vx; ball.y+=ball.vy; if(ball.x>=stage.stageWidth-ball.radius){ ball.x=stage.stageWidth-ball.radius; ball.vx*=-1; bing.play(); } elseif(ball.x<=ball.radius){ ball.x=ball.radius; ball.vx*=-1; bing.play(); } if(ball.y>=stage.stageHeight-ball.radius){ ball.y=stage.stageHeight-ball.radius; ball.vy*=-1; bing.play(); } elseif(ball.y<=ball.radius){ ball.y=ball.radius; ball.vy*=-1; bing.play(); } }
AnimationinActionScript3.0/MakingThingsMove!一书终于全部啃完了,感谢作者“KeithPeters”大师写出这么好的书,感谢[FL基理文]历时4个月的用心翻译!强烈推荐给想研究Silverlight/Flash动画的朋友们,里面的很多思想和处理方法都是动画编程通用的,并不局限于某一种特定的语言!“师傅领进门,修行在各人”,以后在动画编程的道路上能走多远,就只能看自己的造化了。