如何优化cocos2d程序的内存使用和程序大小:第一部分_(转)

译者:html

在我完成第一个游戏项目的时候,我深切地意识到“使用cocos2d来制做游戏的开发者们,他们大多会被cocos2d的内存问题所困扰”。而我刚开始接触cocos2d的时候,社区里面的人们讨论了一个很是有意义的话题:“请简单地讲述你认为新手cocos2d程序员在他开始编码以前,最应该先知道,或者应该关注和注意的事项。”这个问题的答案不少,有人讲是“如何加载和保存游戏数据”,有人讲的是“如何实现有限状态机”等等。而最吸引个人则是,有一我的讲到,新手cocos2d程序员或者新手cocoa程序,他们所遇到的80%的问题都与内存相关。node

由于有着c/c++背景的我,看到这句话的时候,非常赞同,所以刚开始cocos2d编程的时候格外注意内存方面的问题。即使如此,在我完成本身第一个游戏的过程当中,仍是遇到了大量的内存问题,它们让我头疼,让我睡不着觉。庆幸的是,我经过社区都找到了答案而且解决了个人问题。ios

我在《个人第一个游戏FoodieTheBug完成以后的几点心得体会》这篇博文中也讲述过一些内存方面的使用心得。可是,不够具体,我当时想讲的内容有不少。由于有些难以用文字具象化,我也就偷了一回懒了。此次,当我看到Steffen Itterheim写了两篇这么经典的优化cocos2d内存使用和程序大小的文章以后,我有一种“于我心有戚戚焉”的感受。我火烧眉毛地想跟你们分享,惋惜不少人抱怨说访问不了,被墙了等等。可能也有一些同行,对E文不是很感冒。趁着周末,我花一个下午的时间,给你们翻译一下,与你们共勉。c++

全文以下:git

我目前正完成个人最后一个合约项目。在这个项目的最后阶段,我须要考虑的一件事情就是如何优化游戏的内存使用。程序员

在今天的iDevBlogADay文章中,我将向你们讲述,我是如何减小25-30MB游戏内存消耗的(如今游戏消耗内存90-95MB,我还经过这个过程,消除了一些因为内存警告而引发的程序崩溃问题)。同时,我还将游戏程序的大小从25MB减小到了20MB如下(若是苹果没有在不久前将蜂窝网下载应用的限制从20MB提升到50MB的话,那么我这个小的优化就太棒了,它能够潜在地给我带来更多的下载量)。web

我还会给你们介绍,如何在你加载游戏资源的时候展现一个带有动画的Loading界面,我还会加入一些最佳实践和小技巧。算法

什么消耗了90%的内存?

你们猜一下:)编程

在大部分状况下,是纹理(textures)消耗了游戏程序大量的内存。所以,纹理是咱们首要考虑优化的对象,特别是当你碰到内存警告的问题的时候。api

避免一个接一个地加载PNG和JPG纹理(他们之间至少等待一帧)

cocos2d里面纹理加载分为两个阶段:1.从图片文件中建立一个UIImage对象。2.以这个建立好的UIImage对象来建立CCTexture2D对象。这意味着,当一个纹理被加载的时候,在短时候内,它会消耗两倍于它自己内存占用的内存大小。(译注:为何只是短期内呢?由于autoRelease pool和引用计数的关系,临时建立的UIImage对象会被回收。)

当你在一个方法体内,连续不断地加载4个纹理的时候,这个内存问题会变得更加糟糕。由于在这个方法还没结束以前,每个纹理都会消耗两倍于它自己的内存。

我不是很肯定,如今的cocos2d是否仍然如此。或者这种状况是否只适用于手工引用计数管理,或许ARC不会如此呢?我习惯于按顺序加载纹理,可是在加载下一个纹理以前要等待一帧。这将会使得任何纹理加载的消耗对内存的压力下降。由于等待一帧,引用计数会把临时的UIImage对象释放掉,减小内存压力。此外,在后续的文章中,若是你想在背景线程中按序加载纹理的话,也能够采用这种方法。

不要使用JPG图片!

cocos2d-iphone使用JPG纹理的时候有一个问题。由于JPG纹理在加载的时候,会实时地转化为PNG格式的纹理。这意味着cocos2d-iphone加载纹理是很是慢的(这里有演示),并且JPG纹理将消耗三倍于自己内存占用大小的内存。

一个2048*2048大小的纹理会消耗16M的内存。当你加载它的时候,在短期内,它将消耗32MB内存。如今,若是这个图片是JPG格式,你会看到这个数字会达到48MB,由于额外的UIImage对象的建立。虽然,最终内存都会降到16M,可是,那一个时刻的内存飙高,足以让os杀死你的游戏进程,形成crash,影响用户体验。

JPG不论在加载速度和内存消耗方面都不好。因此,千万不要使用JPG!

忽视文件图片大小

这种状况,我见到不少。它乍听起来可能以为有点荒诞,但事实如此,由于它须要关于文件格式的知识,而这些知识并非每个程序员都了解的。我常常听到的论断就是“嘿!个人程序不可能有内存警告,我全部的图片资源加起来还不到30MB!”。

怎么说呢,由于图片文件大小和纹理内存占用是两码事。假设他们是账篷。图片文件就至关于账篷被装在行李箱。可是,若是你想要使用账篷的话,它必须被撑起来,被“膨胀”。

图片文件和纹理的关系与此相似。图片文件大可能是压缩过的,它们被使用的话必须先解压缩,而后才能会GPU所处理,变成咱们熟知的纹理。一个2048*2048的png图片,采用32位颜色深度编码,那么它在磁盘上占用空间只有2MB。可是,若是变成纹理,它将消耗16MB的内存!

固然,减小纹理占用内存大小是有办法滴。

使用16-bit纹理

最快速地减小纹理内存占用的办法就是把它们做为16位颜色深度的纹理来加载。cocos2d默认的纹理像素格式是32位颜色深度。若是把颜色深度减半,那么内存消耗也就能够减小一半。而且这还会带来渲染效率的提高,大约提升10%。

你可使用CCTexture2D对象的类方法setDefaultAlphaPixelFormat来更改默认的纹理像素格式,代码以下:

[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGB5A1];
[[CCTextureCache sharedTextureCache] addImage:@"ui.png"];

这里有个问题:首先,纹理像素格式的改变会影响后面加载的全部纹理。所以,若是你想后面加载纹理使用不一样的像素格式的话,必须再调用此方法,而且从新设置一遍像素格式。

其次,若是你的CCTexture2D设置的像素格式与图片自己的像素格式不匹配的话,就会致使显示严重失真。好比颜色不对,或者透明度不对等等。

有哪些比较有用的纹理像素格式呢?
generate 32-bit textures: kCCTexture2DPixelFormat_RGBA8888 (default)
generate 16-bit textures: kCCTexture2DPixelFormat_RGBA4444
generate 16-bit textures: kCCTexture2DPixelFormat_RGB5A1
generate 16-bit textures: kCCTexture2DPixelFormat_RGB565 (no alpha)

RGBA8888是默认的格式。对于16位的纹理来讲,使用RGB565能够得到最佳颜色质量,由于16位所有用来显示颜色:总共有65536总颜色值。可是,这里有个缺点,除非图片是矩形的,而且没有透明像素。因此RBG565格式比较适合背景图片和一些矩形的用户控件。

RBG5A1格式使用一位颜色来表示alpha通道,所以图片能够拥有透明区域。只是,1位彷佛有点不够用,它只能表示32768种可用颜色值。并且图片要么只能所有是透明像素,或者所有是不透明的像素。由于一位的alpha通道的缘故,因此没有中间值。可是你可使用fade in/out动做来改变纹理的opacity属性。

若是你的图片包含有半透明的区域,那么RBGA4444格式颇有用。它容许每个像素值有127个alpha值,所以透明效率与RGBA8888格式的纹理差异不是很大。可是,因为颜色总量减小至4096,因此,RBGA4444是16位图片格式里面颜色质量最差的。

如今,你能够获得16位纹理的不足之处了:它因为颜色总量的减小,有一些图片显示起来可能会失真,并且可能会产生“梯度”。

使16位纹理看起来更棒

幸运的是,咱们有TexturePacker.(后面简称TP)

TP有一个特性叫作“抖动”,它可使得本来因为颜色数量减小而产生的失真问题获得改善。(TP里面有不少抖动算法,关于这些算法,读者能够参考我翻译的另外一篇文章)。

特别是在拥有Retina显示的像素密度下,你几乎看不出16位与32位的纹理之间的显示差异。固然,前提是你须要采用“抖动”算法。

cocos2d默认的颜色深度将会把全部的纹理都渲染到16位的color framebuffer里面,而后再显示到你的设备屏幕上面。既然这样,咱们为何不把全部的纹理的格式都弄成16位呢,32位又有什么用呢?反正它原本就会渲染到16位的framebuffer上去的。这个问题有点太底层了,我不想深挖下去,并且我也不适合解释这个问题。(译者:哈哈,知之为知之,不知为不知)

使用NPOT纹理

NOPT是“non power of two”的缩写,译做“不是2的幂”。NPOT stands for “non power of two”. 在cocos2d1.x的时候,你必须在ccConfig.h文件中开启对NPOT的支持,可是,cocos2d 2.x就不须要了,它默认是支持NPOT的。全部3代(iphone 3GS)之后的ios设置都支持cocos2d 2.x(由于它们支持OpenGL ES2.0),因此也都能支持NPOT纹理。

若是纹理图集(texture atlas)使用NPOT的纹理,它将有一个具大的优点:它容许TP更好地压缩纹理。所以,咱们会更少地浪费纹理图集的空白区域。并且,这样的纹理在加载的时候,会少使用1%到49%左右的内存。并且你可使用TP强制生成NPOT的纹理。(你只须要勾选“allow free size”便可)

为何要关心NPOT呢?由于苹果的OpenGL驱动有一个bug,致使若是使用POT的纹理,则会产生额外33%的内存消耗

默认使用PVR格式的纹理

TP让你能够建立PVR格式的纹理。除了PVR纹理支持NPOT外,它们不只能够不是2的幂,并且还能够不是方形的。

PVR是最灵活的纹理文件格式。除了支持标准的未压缩的RGB图片格式外,支持有损压缩的pvrtc格式。另外,未压缩的pvr格式的纹理的内存消耗很是地低。不像png图片那样要消耗2倍于自己内存占用大小的内存,pvr格式只须要消耗纹理自己内存大小再加上一点点处理该图片格式的内存大小。

pvr格式的一个缺点就是,你不能在Mac上面打开查看。可是,若是你安装了TP的话,就可使用TP自带的pvr图片浏览器来浏览pvr格式的图片了。(强烈建议你们购买TP,支持TP,不要再盗版了)

使用PVR格式的文件几乎没有缺点。此外,它还能够极大地提升加载速度,后面我会解释到。

使用pvr.ccz文件格式

在三种可选用的pvr文件格式中,优先选择pvr.ccz格式。它是专门为cocos2d和TP设计的。在TP里面,这是它生成的最小的pvr文件。并且pvr.ccz格式比其它任何文件格式的加载速度都要快

当在cocos2d里面使用pvr格式的纹理时,只使用pvr.ccz格式,不要使用其它格式!由于它加载速度超快,并且加载的时候使用更少的内存!

当视觉察觉不出来的时候,能够考虑使用PVRTC压缩

PVR纹理支持PVRTC纹理压缩格式。它主要是采用的有损压缩。若是拿PVRTC图片与JPG图片做对比的话,它只有JPG图片中等质量,可是,最大的好处是能够不用在内存里面解压缩纹理。

这里把32位的png图片(左边)与最佳质量的PVRTC4(4位)图片(点击图片查看完整的大小)做对比:

注意,在一些高对比度的地方,明显有一些瑕疵。有颜色梯度的地方看起来还好一点。

PVRTC确定不是大部分游戏想要采用的纹理格式。可是,它们对于粒子效果来讲,很是适用。由于那些小的粒子在不停地移动、旋转、缩放,因此你很难看出一些视觉瑕疵。

PVRTC压缩图片格式

TP提供的PVR格式不只有上面两种,还包括TC2和TC4这两种没有alpha通道的格式。

这里的alpha和16位纹理的alpha是同样的。没有alpha通道意味着图片里面没有透明像素,可是,更多的颜色位会用来表示颜色,那么颜色质量看起来也会更好一些。

有时候,PVRTC图片格式指的是使用4位或者2位颜色值 ,可是,并不彻底是那样。PVRTC图片格式能够编码更多的颜色值。

预先加载全部的纹理

就像标题所说,尽你所能,必定要预先加载全部的纹理。若是你的全部的纹理加起来不超过80MB内存消耗的话(指的是拥有Retina显示的设备,非Retina的减半考虑),你能够在第一个loading场景的时候就所有加载进来。

这样作最大的好处在于,你的游戏体验会表现得很是平滑,并且你不须要再担忧资源的加载和卸载问题了。

这样也使得你可让每个纹理都使用合适的纹理像素格式,并且能够更方便地找出其它与纹理无关的内存问题。由于若是与纹理有关,那么在第一次加载全部的纹理的时候,这个问题就会暴露出来的。若是全部的纹理都加载完毕,这时候再出现内存问题,那么确定就与纹理无关了,而是其它的问题了。

若是你知道问题与纹理无关的话,那么你查找剩下的内存问题将会变得更加简单。并且你避免了前面说的这种状况:当2048*2048的纹理加载的时候,它原本只须要消耗16MB内存,可是短期会冲到32MB内存。后面会提出一种方法来解决“间歇性内存飙高”(“译者发明滴”)的方法。(译者:但愿下次开发者的对话中“间歇性内存飙高”的说法会出现,呵呵)

按照纹理size从大到小的顺序加载纹理

因为加载纹理时额外的内存消耗问题,因此,采用按纹理size从大到小的方式来加载纹理是一个最佳实践。

假设,你有一个占内存16MB的纹理和四个占用内存4MB的纹理。若是你首先加载4MB的纹理,这个程序将会使用16MB的内存,而当它加载第四张纹理的时候,短期内会飙到20MB。这时,你要加载16MB的那个纹理了,内存会立刻飙到48MB(4*4 + 16*2),而后再降到32MB(4*4 + 16)。

可是,反过来,你先加载16MB的纹理,而后短时候内飙到32MB。而后又降到16MB。这时候,你再依次加载剩下的4个4MB的,这时,最多会彪到(4*3 + 4*2 + 16=36)MB。

在这两种状况下,内存的峰值使用相差12MB,要知道,可能就是这12MB会断送你的游戏进程的小命哦!

避免在收到内存警告消息的时候清除缓存

我有时候看到了一种奇怪的“本身开枪打本身的脚”的行为:纹理已经所有在Loading场景里面加载完毕了,这时候,内存警告发生了,而后cocos2d就会把没有使用的纹理从缓存中释放掉。

听起来不错,没有使用到的纹理都被释放掉了,可是!。。。

你刚刚把全部的纹理都加载进来,尚未进入任何一个场景中(此时全部的纹理都被看成“unused”),可是立刻被所有从texture cache中移除出去。但是,你又须要在其它场景中使用它们。怎么办?你须要接着判断,若是有纹理没有加载,就继续加载。可是,一加载,因为“间歇性内存飙高”,又立刻收到了内存警告,再释放,再判断,再加载。。。。 个人天,这是一个死循环啊!这也能解释为何有些童鞋,在loading场景完了以后进入下一个场景 的时候很卡的缘由了。

如今,当我收到内存警告的时候,个人作法是----什么也不作。内存警告仍然在发生,可是,它只是在程序刚开始加载的时候。我知道这是为何,由于“间歇性内存飙高”嘛,因此,我不去管它。(可是,若是是游戏过程当中再收到内存警告,你就要注意了,由于这时候可能你有内存泄漏了!!!)

我有时候会想办法改善一下,经过移除掉一些不使用的纹理和一些只有在很特殊的场景才会使用的图片(好比settings界面,玩家是不常常访问的)。而后,无论何时,当我须要某张图片的时候,我会首先检查一下该sprite frame是否在cache中,若是没有就加载。你会在后面看到具体的作法。

理解在何时、在哪里去清除缓存

不要随机清除缓存,也能够心想着释放一些内存而去移除没有使用的纹理。那不是好的代码设计。有时候,它甚至会增长加载次数,并屡次引起“间歇内存飙高”。分析你的程序的内存使用,看看内存里面到底有什么,以及什么应该被清除,而后只清除该清除的。

你可使用dumpCachedTextureInfo方法来观察哪些纹理被缓存了:

[[CCTextureCache sharedTextureCache] dumpCachedTextureInfo];

这个方法的输出以下:(为了清楚起见,我把那些与-hd后缀有关的信息屏蔽掉了)

 

cocos2d: "ingamescorefont.png" rc=9 name=ingamescorefont-hd.png id=13 128 x 64 @ 32 bpp => 32 KB
cocos2d: "ui.png" rc=15 name=ui-hd.png id=5 2048 x 2048 @ 16 bpp => 8192 KB
cocos2d: "ui-ingame.png" rc=36 name=ui-ingame-hd.png id=8 1024 x 1024 @ 16 bpp => 2048 KB
cocos2d: "digits.png" rc=13 name=digits-hd.png id=10 512 x 64 @ 16 bpp => 64 KB
cocos2d: "hilfe.png" rc=27 name=hilfe-hd.png id=6 1024 x 2048 @ 32 bpp => 8192 KB
cocos2d: "settings.png" rc=8 name=settings-hd.png id=9 1024 x 1024 @ 16 bpp => 2048 KB
cocos2d: "blitz_kurz.png" rc=1 name=(null) id=12 50 x 50 @ 32 bpp => 9 KB
cocos2d: "gameover.png" rc=8 name=gameover-hd.png id=7 1024 x 2048 @ 32 bpp => 8192 KB
cocos2d: "home.png" rc=32 name=home-hd.png id=4 2048 x 2048 @ 16 bpp => 8192 KB
cocos2d: "particleTexture.png" rc=2 name=(null) id=11 87 x 65 @ 32 bpp => 22 KB
cocos2d: "stern.png" rc=2 name=(null) id=2 87 x 65 @ 32 bpp => 22 KB
cocos2d: "clownmenu.png" rc=60 name=clownmenu-hd.png id=1 1024 x 2048 @ 32 bpp => 8192 KB
cocos2d: CCTextureCache dumpDebugInfo: 13 textures using 60.1 MB (纹理总共占用的内存大小!!!)

 

上面包含了很是多有用的信息。纹理的大小、颜色深度(bpp)和每个被缓存的纹理在内存中所占用大小等。这里的“rc”表明纹理的“引用计数”。若是这个引用计数等于1或2的话,那么意味着,这个纹理当前可能不会须要使用了,此时,你能够放心地把它从纹理cache中移除出去。

你只移除你知道在当前场景下不太可能会被使用的纹理(即上面介绍的引用计数为1或2的状况),这是一个明智的作法。另外,只移除那些占用内存大的纹理。若是一个纹理只占几个kb的内存,其它移不移除都没什么太大的影响。(译注:这就和程序优化同样,不要作过多的细节优化,不要过早优化,要找到性能的瓶颈,而后再重点优化,以20%的时间换取80%的效率。过早和过多细节优化对于大多数程序而言,是须要极力避免的)。

SpriteFrames retain textures!

上面提到的例子中,纹理的引用计数可能有点让人看不懂。你会发现,纹理集有很高的retain count,即便你知道这些纹理集中的纹理当前并无被使用。

你可能忽略了一件事:CCSprteFrame会retain它的纹理。所以,若是你使用了纹理集,你要彻底移除它不是那么容易。由于,由这个纹理集产生的sprite frame仍是保留在内存中。因此,你必须调用CCSpriteFrameCache的removeSpriteFramesFromTexture方法,能完全清除纹理缓存中的纹理集。(译注:记住,不是你调用对象的release方法了,对象的内存就会被释放掉,而是引用计数为0了,内存才会被删除)

[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromTexture:uncachedTexture];

你也可使用 removeSpriteFramesFromFile,并指定一个纹理集的.plist文件来清除缓存起来的精灵帧(spriteframes).

添加 SpriteFrames 很是耗时, 每次都是!

Note: 这一点只针对cocos2d v1.0有效,而cocos2d v2.x在加载以前会预先判断。

这样看起来有点无知(innocent):

[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"ui.plist"];

可是,要注意,CCSpriteFrameCache并不会去检查一个精灵帧是否已经被缓存起来了!这与CCTextureCache的动做方式有所不一样,它每次都会去加载spriteframes.

这个过程到底须要耗费多少时间呢,这取决于你提供的.plist文件中精灵帧的数量。我注意到,只有14帧的plist加载与有280帧的plist加载有着很大的区别。因此,对于精灵帧的加载,你也须要谨慎。

因此,你要避免一些没必要要的addSpriteFrames*方法调用。由于那边致使场景切换时产生小的卡顿。

你能够清除任何缓存(好比animation,sprite frames等),可是请不要轻易清除纹理缓存

cocos2d有许多缓存类,好比纹理缓存、精灵帧缓存,动画缓存等。可是,若是你想清理内存的话,精灵帧缓存和动画缓存对内存的占有是很是少的,能够说是极少的。

固然,若是你想从内存中移除一个纹理,你也必须移除与之相关的精灵帧(由于精灵帧会retain纹理)。说白了,不要轻易去移除精灵帧和动画缓存,由于你有可能会使用到一个没有缓存的动画帧对象或者精灵帧对象,那样会致使程序crash。

例外:检查声音文件的内存使用!

声音文件会被缓存起来,而后能够重复播放而不会被中断。因为声音文件通常比较大,特别是,我看到有一些开发者使用没有压缩的声音文件做为游戏的背景音乐,而这些背景音乐文件很是大,它们一般会形成大量的内存消耗。

请使用MP3格式的声音文件。由于使用没有压缩的声音文件既浪费内存又占用程序大小。当你加载完一些游戏音效时,在不须要的时候,记得要卸载掉。在第二篇文章中,我会向你们介绍有于声音文件更多的知识。

如何避免缓存特定的纹理

若是你有一个纹理,你确实不想缓存起来,那怎么办呢?好比,在初始的加载场景中的图片,或者那些用户不多会在乎的图片--好比你的很是牛比的致谢场景的图片。

常常容易被误解的一点是,一个纹理显示出来了,那么它就被缓存起来了。若是你从缓存中移除此纹理,那么此时你再移除精灵就会程序崩溃。这个理解不正确。

CCTextureCache只不过是对纹理再添加了一次retain函数的调用,这样,当没有其它对象(好比sprite)持有纹理的引用的时候,纹理仍然会存在内存之间。基于这一点,咱们能够立马从缓存中移除出去,这样,当纹理不存须要的时候,立刻就会从内存中释放掉。以下代码所示:

        bg = [CCSprite spriteWithFile:@"introBG.png"];

        // don't cache this texture:
        [[CCTextureCache sharedTextureCache] removeTextureForKey:@"introBG.png"];

你须要记住,当你从CCTextureCache中移除一个纹理的时候,cocos2d下一次在调用spriteWithFile的时候,仍是会再加载该纹理的--无论是否有没有一张名字同样的图片正在被其它精灵所使用。所以,若是你不够细心的话,你有可能最后会在内存中加载两张重复的纹理。

有一个例子就是,当你在循环中加载纹理,而这些纹理你并不想缓存起来。这种状况下,你就须要在循环以外去移除此纹理的缓存,不然可能会致使多个纹理被重复加载到内存之中:

        NSArray* highscores = [Achievements sharedAchievements].highscores;
        for (HighscoreData* data in highscores)
        {
            NSString* entry = [NSString stringWithFormat:@"%05u", data.score];

            CCLabelAtlas* label = [CCLabelAtlas labelWithString:entry 
                                charMapFile:@"pipizahlen.png" 
                                  itemWidth:18
                                 itemHeight:27 
                                   startCharMap:'.'];
            [labelsNode addChild:label z:10];
        }
        
        // don't hold on to this texture:
        [[CCTextureCache sharedTextureCache] removeTextureForKey:@"pipizahlen.png"];

上面这个例子是我从highscore场景中抠出来的,一旦此场景退出,就不该该持有CCLabelAtlas纹理的引用。所以,咱们须要把它从纹理缓存中移除出去。可是,你必须防止重复加载纹理到内存中去。

经过这种方式,咱们能够很是方便地清除缓存中的纹理,并且最好是在建立纹理的时候清除,而不要在其它地方,好比dealloc或者索性让purge cache去作这个事。

使用一个Loading 场景

若是你不能预先加载全部的纹理的话,你可使用一个loading场景,同时显示一个动画来代表加载的进度。这样能够在进入下一个场景以前,让前面一个场景销毁,同时释放它所占用的内存资源。

实现起来很是简单。这个loading场景调度一个selector,而后每一帧(或者0.1秒也能够)执行一个函数,好比update。除非你前面一个场景有内存泄漏,不然的话,每一次update函数执行的时候,都会把一些引用计数为0的内存资源释放掉。在这个update方法里面,你能够建立新的场景。

这样极大地避免了“间歇性内存飙高”的问题,能够极大地减少内存压力。

在后台加载纹理

CCTextureCache类还支持异步加载资源的功能,利用addImageAsync方法。你能够很方面地给addImageAsync方法添加一个回调方法,这样,当纹理异步加载结束的时候,能够获得通知。

这一点很是重要:你必须等待一个资源加载完毕。不然的话,因为“间歇性内存飙高”,可能会引起下列问题:

1) 程序崩溃
2) 纹理被加载两次!由于异步加载并不能保证加载顺序。

在后台加载其它游戏资源

但是,咱们并无方法来异步加载sprite frames和其它资源。可是,咱们能够借助performSelectorInBackground来实现相似的异步加载的功能:

[self performSelectorInBackground:@selector(loadSpriteFrames:) withObject:nil];

里面的selector方法只接收一个object参数(可是并无使用)。而后就能够在此这方法里面异步加载资源了,以下所示:

-(void) loadSpriteFrames:(id)object
{
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"hilfe.plist"];
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"home.plist"];
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"ui.plist"];
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"gameover.plist"];
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"ui-ingame.plist"];
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"settings.plist"];
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"digits.plist"];
}

这样作最大的好处在于,你加载资源的同时,loading场景还能够播放动画,能够添加精灵并运行一些action,这一切能够处理得很平滑。这种优点甚至在单个CPU的机器上面也表现得不错,可是若是你的设备有多个cpu的话效果更佳。

可是,你须要注意,你不能在后台线程加载纹理,你必须使用addImageAsync方法。这是由于纹理必须与公共的OpenGL context在相同的线程中加载。这样,你就必须先异步加载纹理,而后再去后台加载sprite frames.你不能依靠CCSpriteFrameCache在后台线程中加载纹理。

按顺序加载游戏资源

下面的代码,是我采用的异步加载纹理和精灵帧的方法(在另一个线程中加载:)

假设loadAssetsThenGotoMainMenu方法每一帧都会被触发。assetLoadCount和loadingAsset变量被声明在类接口中,分别 是init和bool类型:

-(void) increaseAssetLoadCount
{
    assetLoadCount++;
    loadingAsset = NO;
}

-(void) loadAssetsThenGotoMainMenu:(ccTime)delta
{
    NSLog(@"load assets %i", assetLoadCount);
    switch (assetLoadCount)
    {
        case 0:
            if (loadingAsset == NO)
            {
                loadingAsset = YES;
                NSLog(@"============= Loading home.png ===============");
                [CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGB5A1];
                [[CCTextureCache sharedTextureCache] addImageAsync:@"home.png"
                                                                          target:self
                                                                        selector:@selector(increaseAssetLoadCount)];
            }
            break;
        case 1:
            if (loadingAsset == NO)
            {
                loadingAsset = YES;
                [self performSelectorInBackground:@selector(loadSpriteFrames:) withObject:nil];
            }
            break;

        // extend with more sequentially numbered cases, as needed

        // the default case runs last, loads the next scene           
        default:
            {
                [self unscheduleAllSelectors];
                MainMenuScene* mainMenuScene = [MainMenuScene node];
                [[CCDirector sharedDirector] replaceScene:mainMenuScene];
            }
            break;
    }
}

当这个方法运行到第一个case语句的时候,为了不一样的图片被加载屡次,咱们把loadingAsset标记设置为yes。当纹理加载完后,咱们就添加increaseAssetLoadCount(这个数量能够用来显示进度条加载百分比)。后面的case语句还能够加载更多的其它东西,好比声音、字体文件、粒子效果、物理配置文件、关卡信息等。无论加载多少东西,最后的default语句会执行,而后就能够进入MainMenuScene了。

这个方法的通用之处是,你能够经过case与assetLoadCount来异步加载多个纹理,同时又能避免“间歇性内存飙高”的问题。由于每帧调用一次方法的时候,前面纹理加载多出来的临时内存已经被释放掉了。由于当前线程栈顶的autoRelease pool会在每一帧渲染以前被清空。

后记:这里介绍的内容虽然是针对cocos2d-iphone的,可是,绝大部份内容是适合cocos2d-x的。所以,开发者大可放心去试用这些方法,若是你们有更好的优化游戏内存使用的方法,欢迎分享。但愿此帖能成为cocos2d内存问题的终极解决方案帖。若是你们以为我翻译的不错,但愿能点一下旁边的推荐按钮。Thanks, enjoy!:)

Happy coding!

 

原文:http://www.cnblogs.com/zilongshanren/archive/2012/12/09/2810017.html

相关文章
相关标签/搜索