cocos2dx 游戏内存优化

    xiaoxiao2022-07-02  130

    什么消耗了90%的内存?

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

      避免一个接一个地加载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的内存!

      按照纹理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中,如果没有就加载。你会在后面看到具体的做法。

     

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

    cocos2d有许多缓存类,比如纹理缓存、精灵帧缓存,动画缓存等。但是,如果你想清理内存的话,精灵帧缓存和动画缓存对内存的占有是非常少的,可以说是极少的。

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

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

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

    请使用MP3格式的声音文件。因为使用没有压缩的声音文件既浪费内存又占用程序大小。当你加载完一些游戏音效时,在不需要的时候,记得要卸载掉。

    使用一个Loading 场景

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

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

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

    减少声音文件大小

    有时候,我们也会忽视这个问题。如果你不考虑声音文件的格式,不管是就内存的使用还是程序的大小而言,都是一种极大的浪费。下面是一些方法可以用来减少声音文件的大小。

    立体声道变单声道 – 你的mp3文件可以采用立体声,但是,这样做值得吗?如果你听不出来差别的话,建议还是采用单一声道。这样可以把文件大小和内存使用都减少一半。

    MP3 比特率 –在iOS设备上面,任何比特率大于192kbps的声音都是浪费。你可以尽量采用低的比特率来获得最好的音质效果,这是一个折中。一般来说,96到128kbps对于mp3文件来说够用了。

    采样率 – 大部分的声音文件使用11,22,44,或者48kHz采样率。采样率越低,声音文件越小。但是,这样声音质量也会越低。44kHz已经达到了CD的音质了,而48kHz会更好(这个差别只有调音师才可以听出来)

    在大部分情况下,44kHz或者更高的比特率都有点浪费。所以,可以尝试下减小采样率(在Audacity里面:Tarck->Resample)。不要只是修改采样率,因为这样会改变声音文件的音高。

     

    Streaming MP3 Files

    mp3文件的播放,首先是加载到内存中,然后解码为未压缩的声音buffer,最后再播放。

    就我目前所知,CocosDenshion的SimpleAudioEngine的playBackgoundMusic是流式播放mp3文件的。流试处理有两个优点:1.更小的内存足迹。2.解码mp3文件采用ios硬件,而不是cpu。但是,硬件一次只能解码一个文件,如果同时播放多个,那么只有一个采用的是硬件解码,其它的都是软件解码。

     

    减少Tilemap大小

    许多开发者没有注意到,tilemap大小太大会消耗大量内存。假设你有一个1000*1000的tilemap,这个大概要消耗1M的内存--如果每一个tile消耗一个字节的内存的话。然而,如果每一个tile大概消耗64个字节的话,那么这个tilemap就会消耗60MB内存。我的天啊!

    除了写一个更优的tilemap渲染器以外,我们唯一可以做的就是减少tilemap的大小了,也可以把地图一分为二。

    最新回复(0)