上一章教程主要讲了一些在写代码过程中可优化的一些方面,本章我讲着重讲一下在Flash Player渲染方面可提高性能的地方。
灵活使用CacheAsBitmap
在显示对象中,不论是Sprite,MovieClip还是Loader都不及Bitmap的渲染效率高。
“为包含复杂的矢量图形(例如文本或渐变)的动画对象打开位图缓存可提高性能。但是,如果在显示对象(如播放其时间轴的影片剪辑)中启用了位图缓存,您将获得相反的效果。”
这句话的意思是说,对于一个无动画的静态对象,如背景图,静态文本还有一些只可能改变x,y位置而不改变外观的显示对象等设置CacheAsBitmap属性为true后可明显减小CPU负荷,在每一帧都来后FP不会渲染那些设置了CacheAsBitmap的静态图像(除非它的外形发生了改变,改变rotation、scaleX、scaleY等属性也等同于改变了外形),但是会增加内存的占用量,不过这绝对是一个划算的买卖。上面这句话的后半句意思是不要为一个频繁改变外形的显示对象设置CacheAsBitmap属性,否则将会让CPU使用量不降反升。
误区:不在屏幕上显示的对象也会一直消耗CPU
为一个比屏幕尺寸大的容器(如Sprite或MovieClip等)增加多个子显示对象,比如屏幕大小500 * 500,我为它addChild一个2000 * 2000的Spirte,这个Sprite中存在多个DisplayObject并分布于各个位置。有人认为那些不在屏幕上显示的对象也会消耗CPU,降低性能,所以想把超出屏幕范围的显示对象removeChild掉,不过经实验证明这是多此一举,FP只渲染屏幕可见的区域,即使你移除了屏幕外的显示对象,CPU消耗率也不会下降。
避免大材小用
像Sprite,MovieClip,Loader这些类属于比较高等的显示对象,其中会包含更多的信息占用更多的内存和CPU使用量。如果Shape类足矣满足需求就不要用Sprite,如果Bitmap类足矣满足需求就不要用MovieClip,如果Loader加载完毕请不要直接把Loader加入到显示列表中而是把LoderInfo中的信息content取出,加载的是图片就存储成Bitmap,是声音就存储成Sound,是动画就存储成MovieClip,是简单图像就存储成Shape,之后把存储的对象添加到显示列表中去。如果一定要用到这些高等显示对象,也请在可能的情况下设置CacheAsBitmap。
用Bitmap取代MovieClip做动画
正如刚才所说,如果Bitmap能做到其他显示对象能做的事那我宁愿使用Bitmap来做,这样能大大提高性能。在之前的案例10中我讲过怎样用Bitmap做动画,制作要点是自定义一个Bitmap类并在里面设置一个数组存储所有动画要用到的图片,之后设置一个Timer来让Bitmap在一定时间间隔后切换bitmapData。再次搬出我已搬出过多次的AnimationObject对象代码:
- /**
- * 动画对象
- * @author S_eVent
- *
- */
- public class AnimationObject extends Bitmap
- {
- public static const PLAY_OVER_EVENT:String = "play over event";
- public var imgList:Array = new Array();
- private var timer:Timer = new Timer( 100 );
- private var currentFrame:int = 0;
- private var _stopIndex:int = 0;
- public function AnimationObject(bitmapData:BitmapData=null, pixelSnapping:String="auto", smoothing:Boolean=false)
- {
- super(bitmapData, pixelSnapping, smoothing);
- timer.addEventListener(TimerEvent.TIMER, onTimer);
- }
- /**
- * 播放动画
- *
- */
- public function play():void{
- timer.reset();
- timer.start();
- }
- /**
- * 停止动画
- *
- */
- public function stop():void{
- timer.stop();
- }
- /**
- * 设置动画帧率
- * @param value 多少时间切换一副图片,单位毫秒
- *
- */
- public function set frameRate( value:int ):void{
- timer.delay = value;
- }
- /**
- * 设置动画播放次数 ,若为0,则不断运行,默认为0
- * @param value 动画播放次数,单位:次
- *
- */
- public function set repeatCount( value:int ):void{
- timer.repeatCount = value * imgList.length;
- }
- /**
- * 设置静止时动画所处帧
- * @param value
- *
- */
- public function set stopIndex( value:int ):void{
- _stopIndex = value;
- currentFrame = _stopIndex;
- this.bitmapData = imgList[_stopIndex];
- }
- private function onTimer(event:TimerEvent):void{
- this.bitmapData = imgList[currentFrame];
- currentFrame ++;
- if( imgList.length == 6 )trace( currentFrame );
- if( currentFrame > imgList.length - 1 ){
- currentFrame = _stopIndex;
- this.bitmapData = imgList[_stopIndex];
- dispatchEvent( new Event(PLAY_OVER_EVENT) );
- }
- }
- }
这个类已经拥有了足矣替代MovieClip做动画的功能。在案例10中我讲的是一种称为“块传输技术”的技术,在案例10的最后我也给出了一篇专门介绍此技术的文章地址。这种技术能够提高Flash应用的效率,原因有二,一是使用了Bitmap替代了MovieClip;二是用一张图片集合了相当多的素材,比起一个素材用一个图片来存储,这可以节省相当多的容量,提高加载速度。
不过对于美工来说通常会提供一个swf作为动画素材,用swf作为素材有两点好处,一是美工用Flash CS工具做起动画来比较直观,他们喜欢用这种方式做动画素材;二是用swf做素材可以把其中用到的图片进行压缩,加载一个有40帧的swf动画比直接加载40张图片要快好几倍。那如果我们的素材类型一定是swf怎么办呢,还能用Bitmap来替代MovieClip做动画么?答案是肯定的,我们写一些代码来遍历一个MovieClip对象中的每一帧并把每一帧转换成一个BitmapData。看下列代码:
- /**
- * MovieClip to Bitmap
- * @author xiaoyi
- *
- */
- public class M2B
- {
- public function M2B()
- {
- }
- /**
- * 转换MovieClip为BitmapData数组
- * @param mc 目标MovieClip对象
- * @param useCenterTranslate 是否使用中心点偏移
- * @param xOffset 横向偏移量
- * @param yOffset 纵向偏移量
- * @return
- *
- */
- public static function transformM2B( mc:MovieClip,
- useCenterTranslate:Boolean=false, xOffset:Number=0, yOffset:Number=0 ):Array
- {
- var result:Array = new Array();
- var bmd:BitmapData;
- for(var i:int=1; i<mc.totalFrames; i++)
- {
- mc.gotoAndStop( i );
- var m:Matrix = new Matrix();
- if( useCenterTranslate )
- {
- m.translate( (mc.width + xOffset) / 2, (mc.height + yOffset) / 2 );
- }
- bmd = new BitmapData( mc.width + xOffset, mc.height + yOffset, true, 0 );
- bmd.draw( mc, m );
- result.push( bmd );
- }
- return result;
- }
- }
这段代码是我从小懿手上摘抄过来并加以改造的,用它我们可以把一个MovieClip转换成一个BitmapData数组供给动画Bitmap对象AnimationObject使用。接下来我们测试一下直接使用MovieClip和改用Bitmap来取代之的运行效率比拼。此时屏幕上出现如下字样“ MovieClip VS Bitmap”两者眼中都冒着火光,很有杀气的样子,OK,LET`S DO IT:
- public class OptimizeAnimation extends Sprite
- {
- [Embed(source="assets/Body.swf", symbol="walk")]
- private var walk:Class;
- private var count:int = 30;
- public function OptimizeAnimation()
- {
- super();
- var imgList:Array = M2B.transformM2B( new walk() as MovieClip, true );
- for(var i:int=0; i<count; i++)
- {
- //--------------------Bitmaps-------------------------//
- var animationObj:AnimationObject = new AnimationObject();
- animationObj.imgList = imgList.concat();//复制一个副本给它
- animationObj.x = Math.random() * stage.stageWidth;
- animationObj.y = Math.random() * stage.stageHeight;
- addChild( animationObj );
- animationObj.frameRate = 1000/24;//设置帧率和MovieClip中一样
- animationObj.play();
- //----------------------MovieClips--------------------//
- // var mc:MovieClip = new walk();
- // mc.x = Math.random() * stage.stageWidth;
- // mc.y = Math.random() * stage.stageHeight;
- // addChild( mc );
- }
- }
- }
这段测试代码中我放了30个动画上去,先用Bitmap来运行一下,CPU使用率长期保持在 0% - 2%;之后,去掉MovieClips部分的注释,并把Bitmap部分注释起来,测试一下,发现CPU使用率高达22%以上……
可见Bitmap替代了MovieClip的优势所在,不过Bitmap转换MovieClip的方法用的是bitmapData.draw()方法,这个方法的弱点在于它是从(0,0)坐标点开始向右下方向画一直画到右下角边缘,这极不利于不规则图形的绘制,比如下图这个元件:
<ignore_js_op>
(0,0)坐标点在人物屁股处,那么我们draw了之后只能画出它的右下半部分,要想以中心点向四周绘制就必须给draw方法传入一个偏移矩阵,但是即使有偏移矩阵也难以精确地绘制。
使用Bitmap另一个不便之处是它不接收鼠标事件,若要实现点击等鼠标功能就必须把Bitmap放入一个InteractiveObject中,比如Sprite,不过即使外面套一层Sprite也比直接用MovieClip要省资源得多……若你不愿意套一层MovieClip那就需要写个算法在鼠标点击舞台时判断是否点中了目标Bitmap,比如判断鼠标点击处是否存在某种颜色的像素……