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

如何优化cocos2d程序的内存使用和程序大小:第一部分,第1张

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

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

因为有着c/c++背景的我,看到这句话的时候,很是赞同,因此刚开始cocos2d编程的时候格外注意内存方面的问题。即便如此,在我完成自己第一个游戏的过程中,还是遇到了大量的内存问题,它们让我头疼,让我睡不着觉。庆幸的是,我通过社区都找到了答案并且解决了我的问题。

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

全文如下:

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

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

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

什么消耗了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的内存!

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

使用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_RGBA4444generate 16-bit textures: kCCTexture2DPixelFormat_RGB5A1generate 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是最灵活的纹理文件格式。除了@R_502_6881@的未压缩的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会断送你的游戏进程的小命哦!

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

我有时候看到了一种奇怪的“自己开q打自己的脚”的行为:纹理已经全部在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 KBcocos2d: "ui.png" rc=15 name=ui-hd.png ID=5 2048 x 2048 @ 16 bpp => 8192 KBcocos2d: "ui-ingame.png" rc=36 name=ui-ingame-hd.png ID=8 1024 x 1024 @ 16 bpp => 2048 KBcocos2d: "digits.png" rc=13 name=digits-hd.png ID=10 512 x 64 @ 16 bpp => 64 KBcocos2d: "hilfe.png" rc=27 name=hilfe-hd.png ID=6 1024 x 2048 @ 32 bpp => 8192 KBcocos2d: "settings.png" rc=8 name=settings-hd.png ID=9 1024 x 1024 @ 16 bpp => 2048 KBcocos2d: "blitz_kurz.png" rc=1 name=(null) ID=12 50 x 50 @ 32 bpp => 9 KBcocos2d: "gameover.png" rc=8 name=gameover-hd.png ID=7 1024 x 2048 @ 32 bpp => 8192 KBcocos2d: "home.png" rc=32 name=home-hd.png ID=4 2048 x 2048 @ 16 bpp => 8192 KBcocos2d: "particleTexture.png" rc=2 name=(null) ID=11 87 x 65 @ 32 bpp => 22 KBcocos2d: "stern.png" rc=2 name=(null) ID=2 87 x 65 @ 32 bpp => 22 KBcocos2d: "clownmenu.png" rc=60 name=clownmenu-hd.png ID=1 1024 x 2048 @ 32 bpp => 8192 KBcocos2d: 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!


如何减少游戏程序大小?(以前的目标是20MB以下,现在的目标是50MB以下,为什么?你懂的!)


且听下回分解:)

原文链接:http://www.learn-cocos2d.com/2012/11/optimize-memory-usage-bundle-size-cocos2d-app/



(译)在cocos2d里面如何使用Texture Packer和像素格式来优化spritesheet

前言:这篇文章是我翻译的第一篇关于cocos2d的文章,我在翻译的时候尽量按原文意思来,但难免会加入自己一些理解进去。还有这篇文章所采用的xcode版本是3.2.5,用xcode4.0的朋友可能实现起来有点出入,我会在文章的最后给出解决办法。

  文章原来出处:http://www.raywenderlich.com/2361/how-to-create-and-optimize-sprite-sheets-in-cocos2d-with-texture-packer-and-pixel-formats



  在cocos2d里面,为了使你的游戏获得最佳性能,你需要把许多小的sprite图片组合到一张大图里面,这张大图就叫做sprite sheet。

  如果你使用cocos2d已经有一段时间了的话,你可能已经使用过了一款叫做Zwoptex的工具来帮你生成sprite sheet。Zwoptex是一个非常棒的工具--我在我的很多程序里面使用它,而且确实帮我节省了很多时间。

  然后,这里有一款新的工具,叫做Texture Packer.它类似于Zwoptex,也能创建sprite sheet,但是它还有一些非常方便的、很神奇的特性。

  这篇文章将以一种教程的形式,讲述如何在cocos2d游戏开发使用Texture Packer,同时,你还将学习到如何使用像素格式(pixel formats)、Texture Packer 如何智能地让你的游戏加载速度更快,运行更流畅,而且还能够在游戏界面看起来很不错的前提下使用尽可能少的内存。

  澄清:我在发博客之前就从Texture Packer这个工具的作者手中拿到了license key。我当时并没有保证我会发一篇博文来回报他,但是,在我使用这个工具一段时间之后,它确实为我的应用程序减少了很多需要加载的内存,因此我爱上了这款工具。所以,我想让你们都了解它。

  这篇教程是为那些熟悉cocos2d的人写的。如果你对cocos2d完全陌生的话,你应该从“怎样做一个简单的iphone应用程序”系列和其它一些cocos2d的教程开始。(目前这些链接依然是e文,但随着我翻译进度的前进,这些也会相应的更新。)

开始

  首先,确保你安装了最新版本的cocos2d(在写作这篇文章的时候,版本号是Cocos2D v0.99.5-rc1,在翻译这篇文章的时候,已经是cocos2d-iphone-1.0.0-beta.tar.gz了)。获得最新版本非常重要,因为新版本里面增加了对一些新的图片格式的支持,而这恰恰是这篇文章后面要用到的。

一旦你安装完之后,你就可以启动XCode,然后使用cocos2d应用程序模板来新建一个工程并把它命名为TextureFun。

  下一步,你需要一些图片来制作sprite sheets。你可以下载这些我收集来的样例图片,然后解压缩,并把整个解压缩后的目录拖到你的TextureFun工程的一个子文件夹下面,如下图所示:

  好,现在你拥有一个工程模板和一些样例图片以,是时候使用TexturePacker来制作Spritesheet了!

使用TexturePacker来创建Spritesheet

  你需要做的第一件事情就是下载Texture Packer的免费版本。请注意,你并不需要购买任何东西,对于这篇教程来说你只需要免费版本就足够了。

当你下载完之后双击,然后点击“TexturePacker.mpkg”,接下来就会d出一个窗口,然后按照提示一步步安装在你的mac上。

  在你完成安装之后,在你的Application文件夹下面找到它并运行。当你看到第一个提示窗口出现的时候,选择“ Use Essentia”(免费版本)继续。

  现在,点击工具栏上的“Add Folder”按钮,然后选择TextureFun\Art\Sprites文件夹。Texture Packer将会加载图片并且智能地把这些图片布局在Spritesheet中,如下图所示:

  另一方面,你能够看到所有导入到纹理集(Texture Atlas)中的图片,当你选中某一个的时候会看到有一个边框--另一个非常方便的功能!你也能够把鼠标停留在精灵(sprite)上面,看它是否创建走样(alias)(走样(alias)指那些被裁剪(cropPing)之后的图像看起来实际上是一样的)。如下图所示:

  顺便说一下通过点击“Add Folder”按钮来添加图片的一些注意事项。首先,当你像这样通过增加文件夹的方式来增加精灵后,Texture Packer并不是对每一个精灵增加一个索引(reference),而是对整个文件夹增加了一个索引。这意味着,当你以后向这个文件夹中增加精灵之后,下一次你运行Texture Packer的时候,它会重新根据文件夹下面的所有的精灵来创建Spritesheet--多么方便啊!

  同时,你也不需要非得把所有的精灵都放在同一个根文件夹下面,你可以按照自己的方式把这些精灵放在不同的子目录下(比如Sprites\animals,Sprites\monsters),之后当你从cocos2d中引用它们的时候只需指定相对路径即可。

  最后,需要注意的是,你可以包含多个精灵文件夹--这也是一个非常方便的功能,尤其是在你的游戏非常大的时候,你可以为每一个关卡制作一个Spritesheet。

  好了,现在让我们来看一下软件左边的一些选项。通过这些选项,你能够配置Spritesheet的大小、布局和输出格式。首先,让我们来快速浏览一遍控制大小和布局的选项:

@H_27_502@autosize(默认) – 这个选项会为你的Spritesheet挑选最小的2的指数倍的大小。这是一个非常方便的特性,因为它可以省去你自己去计算Spritesheet大小的时间。 @H_27_502@Min/max size让你为你的Spritesheet指定一个最大值。如果你想设置特定大小的Spritesheet的时候,这也是一个非常方便的特性。(因为对于特定的设备来说,你可不想超过设备能够支持的最大限制,比如2代touch最大支持texture大小为1024*1024) @H_27_502@Scale让你可以保存一个比原始图片尺寸要大一点、或者小一点的Spritesheet。比如,如果你想在Spritesheet中加载“@2x"的图片(也即为Retina-display设备或者ipad创建的)。但是你同时也想为不支持高清显示的iphone和touch制作Spritesheet,这时候只需要设置scale为 1.0,同时勾选autoSD就可以了。也就是说,只需要美工提供高清显示的图片,用这个软件可以自己为你生成高清和普清的图片。 @H_27_502@AlgorithmTexturePacker里面目前唯一支持的算法就是MaxRects,即按精灵尺寸大小排列,但是这个算法效果非常好,因此你不用管它。 @H_27_502@border/shape padding即在Spritesheet里面,设置精灵与精灵之间的间隔。如果你在你的游戏当中看到精灵的旁边有一些“杂图”的时候,你就可以增加这个精灵之间的间隔。 @H_27_502@Extrude精灵边界的重复像素个数. 这个与间隔是相对应的--如果你在你的精灵的边边上看到一些透明的小点点,你就可以通过把这个值设设置大一点。 @H_27_502@Trim通过移除精灵四周的透明区域使之更好地放在Spritesheet中去。不要担心,这些透明的区域仅仅是为了使Spritesheet里面的精灵紧凑一点。--当你从cocos2d里面去读取这些精灵的时候,这些透明区域仍然在寻里。(因为,有些情况下,你可能需要这些信息来确定精灵的位置) @H_27_502@Shape outlines把这个选项打开,那么就能看到精灵的边边。这在调试的时候非常有用。

  对于Spritesheet来说,上面提到的各个选项的默认值,你一个也不需要改变--因为它们本来就已经很好了。然后,在输出部分,你需要改变一些设置。但是在讲到那个之前,让我们先谈一谈cocos2d中的像素格式。

cocos2d和像素格式

  在cocos2d里面,理解像素格式非常重要。因为,像素格式会影响在你的游戏中加载一张图片到底需要多少内存。因为游戏通常要加载大量的图片资源,所以你要尽可能充分利用移动设备上面非常少的可用物理内存。

  默认情况下面,当你在cocos2d里面加载一张图片的时候,对于每一个像素点使用4个byte来表示--1个byte(8位)代表red,另外3个byte分别代表green、blue和Alpha透明通道。这个就简称RGBA8888。

  因此,如果你使用默认的像素格式来加载图片的话,你可以通过下面的公式来计算出将要消耗多少内存来加载:

  图像宽度(wIDth)×图像高度(height)×每一个像素的位数(bytes per pixel) = 内存大小

  此时,如果你有一张512×512的图片,那么当你使用默认的像素格式去加载它的话,那么将耗费

  512×512×4=1MB(好多啊!)

  这里,我们以Iphone3G为例。它总共只有128兆内存,但是系统就要占掉一大半,还有其它一些程序也要使用一些内存,实际可用的内存更少。对于单独一张Spritesheet来说那确实足够了。可是想像一下你有许许多多的Spritesheet,而且游戏里面经常需要大量的Spritesheet!

  这里就需要让像素格式来帮忙了。你可以为图片的每个像素点指定更小的字节来保存图片。(比如每个像素点2个字节,即每个像素点16位),这种方式就能够在图片质量和内存消耗之间取得一个很好的平衡点。

  通常,你是在你的游戏看起来还ok的提前下,尽可能少地使用内存。背景图片就非常适用用8位或者16位来存储,而精灵则一般要用16位或者32位。对于更多可选的像素格式和适用的场合,你可以参考Riq(cocos2d的作者)的一篇文章:understanding pixel format guide.(理解像素格式向导)

  顺便说一下,如果你注意看窗口的右下角,你会看到Texture Packer会基于你当前选择的像素格式计算出这张Spritesheet所消耗的内存大小,因此你不必手动计算了。:)

像素格式和抖动

  很多时候,当你使用较小的像素格式来加载图片的时候,你会发现图片的质量也在相应的降低。这时你会看到图像存在许多颜色的梯度变化。这里有一个例子,展示了当你使用像素格式RBGA4444去显示一张图片的时候会是什么样子:

  看到没有,图像上面有许许多多的“条条”和颜色梯度变化,特别是熊和绿色的框框那里。

  这时,你可能想重新设计你的图片来确保使用更少的梯度,或者使用大一点的像素格式。但是,在这里,TexturePacker实现了另外一个杀手锏功能--图像抖动。

  当你使用TexturePacker来保存Spritesheet的时候,你可以指定目标像素格式为RGBA4444,然后选择“dithering method”。这个默认选项会修改一些你的图像的颜色,但是当有梯度变化或者其它一些会带来问题的颜色以后,图像看起来就会非常糟糕。

  继续,我们为Spritesheet选择RBGA4444格式,然后改变抖动选项为“FloydSteinberg+Alpha”。Texture Packer将会在动态修改你的图片,而且马上显示出效果来。和上面的图片相比,是不是好看多了?

  现在让我们保存这个Spritesheet。点击Texture file旁边的“...”,在d出的对话框中选择TextureFun\Resouces目录,然后命名为“Sprites-hd.pvr.ccz”。然后,TexturePack会自动为我们在Data file那里生成相应的pList文件路径。并且会命名为“Sprites-hd.pList”,这个名字是根据前面你提要的名字来命名的。

  “但是,等一下!”,你可能会说,“为什么是pvr.ccz?!”。好吧,我很高兴你会这样问。。。

PVRs和压缩

  PVR图像是专门为ios设备上面的PowerVR图形芯片指定的图像容器。它们在ios设备上非常好用,因为可以直接加载到显卡上面,而不需要经过中间的转化。

  PVR图像也可以包含许多种不同像素格式的图像数据。之前,cocos2d仅仅支持一些用 sdk指定的texturetool app来创建的图片格式,不过后来cocos2d已经扩展了许多格式了。

  而且,最近cocos2d更新到了可以支持压缩了的pvr图像格式pvr.ccz。使用这种图片格式的好处有两点:一、可以使你的应用程序更小,因为图片是压缩过了的。二、你的游戏能够启动地更快。

  总而言之,对于Spritesheet来说,你可能通过指定16位的像素格式来减少内存消耗,同时保存为pvr.ccz格式来使程序加载速度更快。最后,点击“Publish”按钮,你的Spritesheet和属性列表文件就生成好了。Teture Packer会提示你,一些精灵将会创建成红色(因为你使用的是免费版本)。

优化背景图片

  现在,让我们也来加载并优化一下我们的背景图片。点击new创建一个新的Texture Packer窗口,然后点击“Add Folder”,并且选择“TextureFun\Art\flower”文件夹。

  把图片格式改成RBG565(对于大的图片来说,你可能需要更好的质量),然后改变抖动方法为“FloydSteinberg”(为什么不是FloydSteinberg+Alpha呢?因为像素格式是RBG565,没有了Alpha通道)。然后指定保存texture file的路径为“TextureFun\Resouces\flower-hd.pvr.ccz”。最后,点击“Publish”,关闭警告信息,这时你的屏幕看起来会是下面这样:

在cocos2d里面使用Spritesheet

  现在回到我们的项目,右键点击Resources,然后选择“Add\Existing files...”。选择flower-hd.pList, lower-hd.pvr.ccz,Sprites-hd.pList,and Sprites-hd.pvr.ccz。同时,确保没有选中“ copy items into destination group’s folder (if needed)”,然后单击完成。

  下一步,打开HelloWorldScene.m,并且用下面的代码替换掉你的init方法里的内容:

- (ID) init
{
if ( (self = [super init] )) {
CGSize winSize
= [CCDirector sharedDirector].winSize;

[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGB565];
CCSprite
* background = [CCSprite spriteWithfile: @" flower-hd.pvr.ccz " ];
background.anchorPoint
= ccp( 0 , 0 );
[self addChild:background];

// More coming here soon...
}
return self;
}

  你需要做的第一件事情就是加载背景图片。首先,你告诉cocos2d使用RBG565的像素格式(你正在为你的背景图片的每个像素使用8位),然后调用spriteWithfile从磁盘上加载pvr.ccz格式的图片。注意,这里你并不需要把它当作一个Spritesheet(比如,加载pList文件),因为这里“Spritesheet”就只有一张图片。

  注意,其实你在加载pvr.ccz格式的文件的时候并不需要指定像素格式,因为这个文件格式本身就包含了这样一些信息。但是,我们还是显示地在这里指 定了像素格式,因为如果你加载png格式的图片的话,(png格式图片总是保存为每个像素32位,尽管你可能会使用不同的像素格式把它加载到内存里)。

  下面,让我们在“more coming here soon”注释的地方添加下面的代码:

[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA4444];
CCSpriteBatchNode
* SpritesBgNode;
SpritesBgNode
= [CCSpriteBatchNode batchNodeWithfile: @" Sprites-hd.pvr.ccz " ];
[self addChild:SpritesBgNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithfile:
Sprites-hd.pList " ];

  这里把像素格式设置为RBGA4444(你为主精灵所使用的每个像素点16位的像素格式),然后为这个Spritesheet创建一个batch node。你也需要加载pList文件,把每一个精灵对应的帧(frame)加载到精灵帧缓冲区(sprite frame cache)中。

  最后,紧接着上面加入下面的代码:

NSArray * images = [NSArray arrayWithObjects: bear_2x2.png " ,0); line-height:1.5!important">bird.png cat.png dog.png turtle.png ooze_2x2.png for ( int i = 0 ; i < images.count; ++ i) {
Nsstring
* image = [images objectAtIndex:i];
float offsetFraction = (( float )(i + 1 )) / (images.count + 1 );
CGPoint spriteOffset
= ccp(winSize.wIDth * offsetFraction,winSize.height / 2 );
CCSprite
* sprite = [CCSprite spriteWithSpriteFramename:image];
sprite.position
= spriteOffset;
[SpritesBgNode addChild:sprite];
}

[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_Default];

  这个循环遍历Spritesheet中的所有的图片,并把他们合理地旋转在屏幕上面。

  如果你使用iPad来编译并运行你的代码的话,你会得到下面的运行结果:

  这个不就是你想实现的效果吗--记住,这里的红色仅仅因为你使用的是免费版本。(ps:正式版本也不贵,100多块人民币可以搞定)

  但是真正让人着迷的是那些你看不到的东西。

  在背后,你的应用程序加载速度会明显比以前快很多。而且使用更少的内存,更让人心动的是,它看起来还是那么棒!而这些功能使用Texture Packer都可以很容易的完成。

不相信我?

  当我写这篇文章的时候,我做了一系列的简单的测试,从最好的情形到最坏的情形,来测试到底我的程序是如何运转的。下面是我得出的一些结论:

@H_27_502@做最原始的事情。使用默认像素格式单个单个精灵地加载,不使用任何Spritesheet。大约花费了0.73秒钟加载,消耗大约26兆内存。而且当你添加更多的精灵进去的时候,游戏就开始卡了。 @H_27_502@使用默认的像素格式,并且使用Spritesheet:前进了一大步。这样会使游戏性能更好,同时也会减少内存消耗(因为你会把所有的精灵加载到一张大小的Spritesheet的,而opengl使用纹理的大小都是2的指数幂,如果你一张精灵的大小是320×200的话,那么opengl会创建512×512的纹理来加载精灵,这样就有很多空白的地方。实际上就是浪费了内存。) @H_27_502@使用不含抖动的Zwoptex并保存为png格式,同时减少像素格式:这样可以大幅地减少内存消耗,大约只需要15兆左右)。但是却增加了程序的启动时间,上升到大约1秒钟。我认为可能是由于不得不改变颜色缓冲的缘故吧。另外,图像显示的效果并没有在“像素格式和抖动”一节中的截屏效果那么好。 @H_27_502@使用通过Texture Packer创建的抖动过的Spritesheet并且保存为pvr.ccz格式:这在启动时间和显示效果上都前进了一大步!(启动时间大约只有0.31秒左右,内存也只需要大约17兆左右,我认为这可能是由于内存泄漏,这个问题在现在的版本中已经解决了。

  好了,如果你按照上面所讲的最佳实践来做的话,我想你在大部分情况下都会做得非常好。:)如果你想看看我写的测试程序,也想拿来跑一跑的话,可以点此下载。

Texture Packer 和XCode集成

  当使用Texture Packer的时候,你可以像这里介绍的一样使用GUI工具,但是你还可以把它集成到Xcode构建过程中去。这样你每一次编译的时候,它都会自动地(如果没有更改,就不会更新)为了更新Spritesheet。

  如果你过去用cocos2d写过游戏的话,你肯定明白一遍又一遍地重新生成你的Spritesheet是那么的烦人!虽然每次可能都只需要几秒钟的时间,但是老是这样重复地做这样的事,确实很烦。

  因此,让我们更方便地构建我们的工程吧--这里只需要花几秒钟时间,但是却可以为你以后节省大量的时间。右键点击“Resources”,选择“Add\New file...“,然后选择 Mac OS X\Other\Shell Script,然后选择下一点。并命名为PackTextures.sh,单击完成。

  然后使用下面的代码替换掉PackTextures.sh里面的内容:

# !/bin/sh

TP
= " /usr/local/bin/TexturePacker "

if [ ${ACTION} " = clean " ]
then
echo
cleaning... "

rm resources
/ Sprites - hd.pvr.ccz
rm resources
/ Sprites - hd.pList

rm resources
/ flower - hd.pvr.ccz
rm resources
/ flower - hd.pList

....
add all files to be removed in clean phase else
echo
building... create hd assets
${TP} -- smart - update \
-- format cocos2d \
-- data resources / Sprites - hd.pList \
-- sheet resources / Sprites - hd.pvr.ccz \
-- dither - fs - Alpha \
-- opt RGBA4444 \
Art
/ Sprites /* .png

${TP} -- smart - update \
-- format cocos2d \
-- data resources / flower - hd.pList \
-- sheet resources / flower - hd.pvr.ccz \
-- dither - fs - Alpha \
-- opt RGB565 \
Art
/ flower /* .png

add other sheets to create here
fi
exit
0

  所有Texture Packer GUI界面能够做的事情,命令行工具也能做。如果你在命令行里面输入“TexturePacker”,你将会看到一系列它能够接收的参数说明。

  这个脚本仅仅通过运行TexturePacker来从你的Art目录下读取精灵文件并创建Spritesheet--就像你之前用GUI工具所做的一样。你可以通过查看TexturePacker命令行工具帮助来获得更多有关每个参数具体的用法。

  接下来,你需要让你的工程在编译的时间能够运行这个脚本。右键点击Targets,选择“Add\New Target...”,然后选择“External Target”(不是Shell Script Target),然后点击下一步,重命名这个Target为TexturePacker,最后点击Finish。

  然后在你新建的target上面双击,然后把里面的内容设置成下图所示:

  最后一步就是把这个target设置成你的程序的一个依赖。在TextureFun target上面双击,然后选择General tab,再在Direct DependencIEs窗口下面选择+按钮。然后从列表中选择TexturePacker,最后单击Add Target。如图下所示:

编译并运行你的程序,你将会从你的构建结果信息中看到Texture Packer的一些输出信息,由此来判断是否一切运转良好。

  如果你看到这些输出信息的话,那么意味着如果你想要增加一些新的文件到Spritesheet中,你只需要把这些新的文件拖到指定的文件下面(这里是Art目录),然后重新编译一下,那么就会自动生成新的Spritesheet。相反,如果你要移除一些精灵图片,再重新编译一下,也ok。是不是非常方便?

Texture Packer vs. Zwoptex

  首先,让我们再说一遍,我是Zwoptex的超级粉丝。我认为Robert已经做了一件非常好的事情来把这些东西整合到一起,而且我老实说,如果没有这个工具,cocos2d不会走到今天这一步!

  然后,当我们来比较Texture Packer和Zwoptex的特性的时候,Texture Packer似乎包含了Zwoptex90%的功能。但是,Texture Packer有三个杀手锏级别的功能是Zwoptex所没有的。

@H_27_502@抖动,抖动,抖动.噢,我是多么喜欢抖动啊!在过去,有时我想使用比较低的像素格式,但是我不能,因为它看起来效果很不好。但是Texture Packer内置的抖动功能使得图片看起来还是那么棒,尽管此时的图片质量很低。 @H_27_502@pvr.ccz 支持.我喜欢这个特性. 它确实能够使游戏启动得更快,而且会使你的可执行程序更小. 现在我不用花很长时间来上传和下载我以前写的应用程序了,我可以很快的下载并更新。 @H_27_502@命令行工具支持.一旦你花一点时间把Texture Packer集成到你的Xcode中去,你将会热爱生活。它是如此地方便,特别是在整个开发过程中,美工对图片改来改去的时候。

  尽管Zwoptex非常棒,但是它目前为此还是没有提供我这里列举的这些功能特性。

  Texture Packer 有一点点贵($17.95 vs. Zwoptex’s $14.95),但是,我认为多花这点钱值得。而且就像 Steffen Itterheim所说,2个工具都有所长,都做了一件了不起的工作。

接下来该怎么做呢?

  这里有上面的教程中所使用的示例代码。

  你是一个Texture Packer粉丝 还是一个Zwoptex粉丝呢?不妨在下面一起聊聊吧!或者你有其它一些很好的策略来高效地在cocos2d里面加载纹理的话,请让我知道。:)

ps:使用xcode4的朋友,主要需要注意的就是新增加的Target的参数的一些设置。因为xcode4生成的项目文件夹下并不是直接包含了所有的项目文件,而是一个工程文件和另外一个和项目名字一模一样的文件夹,然后所有的项目有关的源文件和资源文件都放在那个目录下,所以,我们增加的Target里的设置信息应该改成:

"$(PROJECT_DIR)/TextureFun/Resources/PackTexture.sh"

$(SRCROOT)/TextureFun

其实可能需要注意的就是自动生成普清Spritesheet,选中autoSD选项就行了。

但是PackTexture.sh脚本里要相应增加

${TP}--smart-update \
--format cocos2d \
--data resources/Sprites-hd.pList \
--sheet resources/Sprites-hd.pvr.ccz \
--dither-fs-Alpha \
--opt RGBA4444 \

--auto-sd \ #只要增加这一行就可以自动生成普清的图片了
Art
/Sprites/*.png

还有一些软件的设置选项,大家可以参照软件自带的帮助文档。

如果大家在实践的过程中,遇到一些问题,可以留言。

著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

总结

以上是内存溢出为你收集整理的如何优化cocos2d程序的内存使用和程序大小:第一部分全部内容,希望文章能够帮你解决如何优化cocos2d程序的内存使用和程序大小:第一部分所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1038631.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存