避免同时下载多个资源
下载一个资源,就需要建立一个连接,而建立连接的过程是需要消耗性能的。像下面这种做法是不推荐的:
//需要加载的资源列表 var assetList:Array = ["1.jpg", "2.jpg", "3.jpg", ......]; var loader:Loader; for each(var asset:String in assetList) { loader = new Loader(); loader.load(new URLRequest(asset)); }
这种做法会一次性同时加载过多资源,由于在短期内造成巨大的性能消耗,这有可能会导致Flash Player卡死,影响用户体验不说,也不见得加载速度会快多少。
使用队列加载方式是一种两全其美的办法,即可以保证不影响性能又能方便监测加载进度。实现思路就是将所有要加载的资源路径放在一个数组中,加载完一个就将该项目从数组中剔除并开始加载数组中下一个资源直到数组中不再存在元素为止。为了方便使用,网上存在很多实现了队列加载的第三方类库。比较有名的一个叫做BulkLoader(类库地址:http://code.google.com/p/bulk-loader/)。这个类库在我的项目中被使用,该类功能强大且使用方式比较简单,还带了对象池,确保了在多次使用同一个资源时不会被反复加载。
名词解释——对象池:所谓对象池,就是在要使用一个对象o时,会从一个Object或者Dictionary对象cache中取出o的缓存,若o不存在于cache中,则想办法创造一个o对象(可以从外部加载或者实例化)。
private var cache:Object = {}; private var asset:String = "1.jpg"; public function Test() { var bmd:BitmapData = getBMD(asset); if( !bmd ) generateBMD(asset); else addBitmap(bmd); } private function generateBMD(assetName:String):void { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComp); loader.load(new URLRequest(assetName)); //将加载的资源名称保存在Loader的name属性中,这样在onComp方法中就能获知我加载得是哪个资源 loader.name = assetName; } private function onComp( e:Event ):void { var loaderInfo:LoaderInfo = e.currentTarget as LoaderInfo; loaderInfo.removeEventListener(Event.COMPLETE, onComp); var assetName:String = loaderInfo.loader.name; var bmp:Bitmap = loaderInfo.content as Bitmap; var bmd:BitmapData = bmp.bitmapData; //将加载得到的BitmapData缓存起来 if( !cache[assetName] ) { cache[assetName] = bmd; } //得到BitmapData就可以往舞台上添加图片了 addBitmap(bmd); //若要再次往舞台上添加同样的图片,则直接就可以从缓存中拿出被缓存的BitmapData,而不需要再次加载 addBitmap( getBMD(asset) ); } private function getBMD(bmdName:String):BitmapData { //若缓存中存在该资源则直接从缓存中取出.否则返回null if( cache[bmdName] ) { return ( cache[bmdName] as BitmapData ).clone(); } return null; } private function addBitmap(bmd:BitmapData):void { var bmp:Bitmap = new Bitmap(bmd); addChild(bmp); }
对于BitmapData对象来说,它可以通过clone方法来不断复制自己,所以可以缓存起来进行重用,但是对于Sprite、MovieClip、Sound这类的对象就不能够这么做了,它们可没有clone方法可以复制自己。因此,对于这些对象,我们可以缓存它们的类定义。那么何谓类定义呢?创建类定义的方法有两种,一,就是我们最熟悉的,创建一个as文件,像这样:
package { import flash.display.MovieClip; public class A extends MovieClip { } }
就创建了一个名为“A”的类定义。二,在fla的库中设置一个元件的属性,让其导出为ActionScript:
这样同样也创建了一个名为“A”的类定义。
有了类定义我们能干嘛呢?类定义就像一个印钞机,我们可以用它无限创建对象
var clz:Class = A; for(var i:int=0; i<5; i++) { var ins:MovieClip = new clz(); }
如果是在同一项目目录下的类,我们可以直接直接拿到其类定义并import进来。那么对于外部加载进来的类定义,我们需要访问其应用域(ApplicationDomain)并使用getDefinition方法来获取到。比如之前我们在lib.fla的库中创建了一个名字叫做A的元件并设置其为ActionScript导出,且导出的ActionScript类名为A。此时我们在我们的项目中若要使用到这个类A,那么我们首先要加载lib.fla发布出来的lib.swf文件,加载完毕后可以拿到它的应用域:
public class Test extends Sprite { public function Test() { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComp); loader.load(new URLRequest("lib.swf")); } private function onComp(e:Event):void { var loaderInfo:LoaderInfo = e.currentTarget as LoaderInfo; var appDomain:ApplicationDomain = loaderInfo.applicationDomain; } }
在得到应用域后,我们需要把应用域中我们需要用到的类取出来并缓存到我们的项目中,这样就方便以后多次使用了
public class Test extends Sprite { private var cache:Object = {}; private var mcName:String = "A"; public function Test() { var clz:Class = getDef(mcName); if( clz ) { addMovie( new clz() as MovieClip ); } else { generateDef(); } } private function generateDef():void { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComp); loader.load(new URLRequest("lib.swf")); } private function onComp(e:Event):void { var loaderInfo:LoaderInfo = e.currentTarget as LoaderInfo; var appDomain:ApplicationDomain = loaderInfo.applicationDomain; cache[mcName] = appDomain.getDefinition(mcName) as Class; //有了类定义之后就可以无限次地往舞台上添加MovieClip对象了 var clz:Class = getDef(mcName); addMovie( new clz() as MovieClip ); addMovie( new clz() as MovieClip ); } private function getDef(name:String):Class { if( cache[name] ) { return cache[name] as Class; } return null; } private function addMovie(mc:MovieClip):void { addChild(mc); } }
在一个大项目中往往需要加载多个swf文件作为外部资源库,那么此时为了知道我某个swf文件中包含哪些类定义,我需要写一张资源配置表,格式类似于:
<assetConfig> <s id='ui' group='ui' type='swf' path='assets/ui.swf'> <i id='WorldPanel' classes='WorldPanel'/> <i id='IslandPanel' classes='IslandPanel'/> <i id='WaitPanel' classes='WaitPanel'/> </s> <s id='ui_friend' group='ui' type='swf' path='assets/ui_friend.swf'> <i id='HomePanel' classes='HomePanel'/> <i id='HomeFriendItem' classes='HomeFriendItem'/> </s> </assetConfig>
每加载完一个swf文件就把它应用域中存放的我们需要用到的类定义缓存起来,以便以后使用。注意,类定义不可重名哦,不然不方便缓存!
合并你的资源
正因为建立连接需要消耗性能,加长用户等待时间,所以我们需要尽可能地削减需要加载的资源数量。把所有相关的图片合并成一个,如下图:
假如进入一个场景后需要加载那么多NPC素材,那么把它们都集成在一张图片中会非常高效,即节省了总体资源尺寸,又减少了资源数量。这种图片资源往往需要配套一个xml配置表记录其中所包含资源的位置、尺寸以便加载完毕后对该图片进行切片使用。
<?xml version="1.0" encoding="UTF-8"?> <TextureAtlas imagePath="Actor1.png"> <SubTexture name="0_0" x="0" y="0" width="32" height="32"/> <SubTexture name="0_1" x="32" y="0" width="32" height="32"/> <SubTexture name="0_2" x="64" y="0" width="32" height="32"/> <SubTexture name="1_0" x="96" y="0" width="32" height="32"/> <SubTexture name="1_1" x="128" y="0" width="32" height="32"/> (省略若干条……) </TextureAtlas>
这种将多个图片素材合并成一个并配以xml配置表的资源形式被称为SpriteSheet,被广泛使用。在Flash CS Professional 6或以上版本中有SpriteSheet生成功能,非常方便。或者你可以选择下载TexturePacker工具来生成SpriteSheet。
如果你不愿意制作SpriteSheet,你可以把多个资源放在一个fla的库中,然后让你的项目来加载该fla生成的swf文件,这样也可以有效地把资源进行打包。
最后,如果你够牛逼,你完全可以用AS代码来进行绘图!