您現在的位置是:首頁 > 棋牌

最佳化程式的記憶體使用和程式大小

  • 由 感說敢愛 發表于 棋牌
  • 2021-10-16
簡介而且,這樣的紋理在載入的時候,會少使用1%到49%左右的記憶體

蘋果遊戲檔案怎麼檢視

(參考大神文章)

在大部分情況下,是紋理(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_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會在每一幀渲染之前被清空。

減少你的程式的大小

把紋理的顏色位深度減少到16位,不僅可以減少記憶體壓力,還可以有效地減少程式的體積。但是,我們還有其它方法可以更進一步地減少程式的大小。

TexturePacker PNG 圖片最佳化

如果你有某些原因,讓你堅持要使用PNG檔案格式而不是我之前極力向你推薦的pvr。ccz檔案格式,那麼TexturePacker有一個選項,叫做“Png Opt Level”(Png最佳化級別),可以幫助我們減少png檔案的大小(注意:這樣並不會影響圖片載入時間)

就我目前的理解來看,最大的最佳化級別可以生成最小的檔案大小。但是,它有一個缺點,就是非常耗時。對於2009年出的27寸的iMac來說,處理尺寸稍大的紋理,需要耗費10-20的時間來處理。由於該最佳化過程採用了多執行緒的方式,所以,如果你有機器是四核的,那麼速度應該會快一些。

當然,你只有在真正釋出應用的時候才需要利用這個最佳化特性。現在的問題是,它到底可以減少多少檔案體積呢?

我最大的一張png圖片從2。4MB減少到了2。2MB。小一些的紋理從180kb減至130kb。可能單個檔案減少的量並不是很多,可是當你的png圖片的總大小有18MB時,它可以使之減少至16MB。注意,在xcode裡面有一項設定,你可能會把它忽略掉。你需要關閉“Compress PNG files”開關,因為這個選項有可能會使你的png圖片膨脹。你可以在xcode的build settings裡面設定,如下所示:

如果啟用此png壓縮選項,xcode會在png檔案打包程序序的時候執行自帶的png最佳化程式。所以,有可能會使我們先前使用TP最佳化過的png圖片再次膨脹。因此,再次確保這個選項已關閉!

不過即使你沒有禁用此選項,你的程式大小還是會有所減小。因為,你有可能使用一些沒有被TP最佳化過的png圖片。

檢查你的程式在App Store 裡面的大小

在Xcode裡面,執行Archive build(在選單中選擇Product->Archive)。當build成功的時候,Xcode的Organizer視窗會開啟,然後你會看到一個“Estimate Size”(評估大小)的按鈕,可以用來估算你的應用程式大小:

移除未使用的資原始檔

在開發遊戲的過程中,你會經常新增、移除和替換遊戲資源。所以,你可能會因為某些原因,忘記移除一些不用的圖片資源。所以,你需要額外注意把它們都從專案中移除出去,至少要從程式的target中出去。

尤其是你使用多個target的時候(比如,你同時維護ipad和mac版本),你就極有可能會在一個target裡面新增一些錯誤的資源。

當然,在移除資源之後,你一定要充分測試你的遊戲。切記!一定要充分測試。

減少聲音檔案大小

有時候,我們也會忽視這個問題。如果你不考慮聲音檔案的格式,不管是就記憶體的使用還是程式的大小而言,都是一種極大的浪費。下面是一些方法可以用來減少聲音檔案的大小。我推薦大家使用一款免費的聲音編輯工具。

立體聲道變單聲道

– 你的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的大小了,也可以把地圖一分為二。

Top