閱讀986 返回首頁    go 阿裏雲 go 技術社區[雲棲]


coco2d-x 紋理研究

1.通常情況下用PVR格式的文件來進行圖片顯示的時候,在運行速度和內存消耗方麵都要比PNG格式要快和小。一般情況下PVR消耗的內存比PNG消耗的內存小25%左右。PVR格式可以用ZWoptex導出。PVR是apple芯片能直接讀取和顯示的文件.

 

2.圖片抗鋸齒處理。

圖片放大時的處理:

圖片在放大的時候會出現鋸齒。紋理類提供了setAntiAliasTexParameters()函數來處理抗鋸齒。當圖片放大的時候會使用相鄰的四個像素進行混合運算。從而消除鋸齒。但是會讓圖片產生模煳的感覺。

左邊使用的就是setAntiAliasTexParameters()。和右邊的圖片相比無鋸齒,但是有模煳的感覺.右邊的圖片使用的是setAliasTexParameters()。不模煳,但是有鋸齒。

圖片縮小時的抗鋸齒處理:

使用minmap,縮小時效果會更好,左邊使用minmap

 

  1. CCSprite *imgMipMap = CCSprite::create("Images/logo-mipmap.pvr");  
  2. if( imgMipMap )  
  3. {  
  4.     imgMipMap->setPosition(ccp( s.width/2.0f-100, s.height/2.0f));  
  5.     addChild(imgMipMap);  
  6.   
  7.     // support mipmap filtering  
  8.     ccTexParams texParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE };      
  9.     imgMipMap->getTexture()->setTexParameters(&texParams);  
  10. }  

3.cocos2d-x的紋理類支持從多種格式的文件加載圖片.包括:.png , .jpeg, .pvr, .gz, .webp, .tiff, .ccz等格式的文件 。

 

4.最大限度的通過一些方法和手段減小紋理對內存消耗.

(1) 通過CCTexture2D來修改紋理的像素格式,可以減少紋理占用的內存大小和渲染的速度。

使用16-bit紋理

最快速地減少紋理內存占用的辦法就是把它們作為16位顏色深度的紋理來加載。cocos2d默認的紋理像素格式是32位顏色深度。如果把顏色深度減半,那麼內存消耗也就可以減少一半。並且這還會帶來渲染效率的提升,大約提高10%。

你可以使用CCTexture2D對象的類方法setDefaultAlphaPixelFormat來更改默認的紋理像素格式,代碼如下:

  1. CCTexture2D::setDefaultAlphaPixelFormat(kCCTexture2DPixelFormat_RGB565);  

這裏有個問題:首先,紋理像素格式的改變會影響後麵加載的所有紋理。因此,如果你想後麵加載紋理使用不同的像素格式的話,必須再調用此方法,並且重新設置一遍像素格式。

其次,如果你的CCTexture2D設置的像素格式與圖片本身的像素格式不匹配的話,就會導致顯示嚴重失真。比如顏色不對,或者透明度不對等等。

有哪些比較有用的紋理像素格式呢?

  1. generate 32-bit textures: kCCTexture2DPixelFormat_RGBA8888 (default)  
  2. generate 16-bit textures: kCCTexture2DPixelFormat_RGBA4444  
  3. generate 16-bit textures: kCCTexture2DPixelFormat_RGB5A1  
  4. 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位紋理的不足之處了:它由於顏色總量的減少,有一些圖片顯示起來可能會失真,而且可能會產生“梯度”。


 

(2)避免一個接一個地加載PNG和JPG紋理(他們之間至少等待一幀)

 

cocos2d裏麵紋理加載分為兩個階段:1.從圖片文件中創建一個UIImage對象。2.以這個創建好的UIImage對象來創建CCTexture2D對象。這意味著,當一個紋理被加載的時候,在短時候內,它會消耗兩倍於它本身內存占用的內存大小。(譯注:為什麼隻是短時間內呢?因為autoRelease pool和引用計數的關係,臨時創建的UIImage對象會被回收。)

當你在一個方法體內,接二連三地加載4個紋理的時候,這個內存問題會變得更加糟糕。因為在這個方法還沒結束之前,每一個紋理都會消耗兩倍於它本身的內存。

我不是很確定,現在的cocos2d是否仍然如此。或者這種情況是否隻適用於手工引用計數管理,或許ARC不會如此呢?我習慣於按順序加載紋理,但是在加載下一個紋理之前要等待一幀。這將會使得任何紋理加載的消耗對內存的壓力降低。因為等待一幀,引用計數會把臨時的UIImage對象釋放掉,減少內存壓力。此外,在後續的文章中,如果你想在背景線程中按序加載紋理的話,也可以采用這種方法。

 

(3)iphone中不要使用jpg來創建紋理對象

cocos2d-iphone使用JPG紋理的時候有一個問題。因為JPG紋理在加載的時候,會實時地轉化為PNG格式的紋理。這意味著cocos2d-iphone加載紋理是非常慢的(這裏有演示),而且JPG紋理將消耗三倍於本身內存占用大小的內存。

一個2048*2048大小的紋理會消耗16M的內存。當你加載它的時候,在短時間內,它將消耗32MB內存。現在,如果這個圖片是JPG格式,你會看到這個數字會達到48MB,因為額外的UIImage對象的創建。雖然,最終內存都會降到16M,但是,那一個時刻的內存飆高,足以讓os殺死你的遊戲進程,造成crash,影響用戶體驗。

JPG不論在加載速度和內存消耗方麵都很差。所以,千萬不要使用JPG!

(4)忽視文件圖片大小

這種情況,我見到很多。它乍聽起來可能覺得有點荒誕,但事實如此,因為它需要關於文件格式的知識,而這些知識並不是每一個程序員都了解的。我經常聽到的論斷就是“嘿!我的程序不可能有內存警告,我所有的圖片資源加起來還不到30MB!”。

怎麼說呢,因為圖片文件大小和紋理內存占用是兩碼事。假設他們是帳篷。圖片文件就相當於帳篷被裝在行李箱。但是,如果你想要使用帳篷的話,它必須被撐起來,被“膨脹”。

圖片文件和紋理的關係與此類似。圖片文件大多是壓縮過的,它們被使用的話必須先解壓縮,然後才能會GPU所處理,變成我們熟知的紋理。一個2048*2048的png圖片,采用32位顏色深度編碼,那麼它在磁盤上占用空間隻有2MB。但是,如果變成紋理,它將消耗16MB的內存!

當然,減少紋理占用內存大小是有辦法滴。

第一個辦法就是:上麵的條款(1),用SetDefaultAlphaPixelFormat()來實現.盡量使用顏色深度為16bit的圖片。但是條款 (1)也提到了,如果圖片本身的顏色深度如果是32位。但是用SetDefaultAlphaPixelFormat()設置成16位。則可能讓圖片失真.圖片看起來就很模煳,這當然是有解決的辦法,慶幸我們有TexturePacker(TP)工具.

TP有一個特性叫做“抖動”,它可以使得原本由於顏色數量減少而產生的失真問題得到改善。(TP裏麵有很多抖動算法,關於這些算法,讀者可以參考我翻譯的另一篇文章)。

特別是在擁有Retina顯示的像素密度下,你幾乎看不出16位與32位的紋理之間的顯示差別。當然,前提是你需要采用“抖動”算法。

第二個辦法是:使用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肯定不是大部分遊戲想要采用的紋理格式。但是,它們對於粒子效果來說,非常適用。因為那些小的粒子在不停地移動、旋轉、縮放,所以你很難看出一些視覺瑕疵。

預先加載所有的紋理

就像標題所說,盡你所能,一定要預先加載所有的紋理。如果你的所有的紋理加起來不超過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方法來觀察哪些紋理被緩存了

這個方法的輸出如下:(為了清楚起見,我把那些與-hd後綴有關的信息屏蔽掉了) 

  1. cocos2d: "ingamescorefont.png" rc=9 name=ingamescorefont-hd.png id=13 128 x 64 @ 32 bpp => 32 KB  
  2. cocos2d: "ui.png" rc=15 name=ui-hd.png id=5 2048 x 2048 @ 16 bpp => 8192 KB  
  3. cocos2d: "ui-ingame.png" rc=36 name=ui-ingame-hd.png id=8 1024 x 1024 @ 16 bpp => 2048 KB  
  4. cocos2d: "digits.png" rc=13 name=digits-hd.png id=10 512 x 64 @ 16 bpp => 64 KB  
  5. cocos2d: "hilfe.png" rc=27 name=hilfe-hd.png id=6 1024 x 2048 @ 32 bpp => 8192 KB  
  6. cocos2d: "settings.png" rc=8 name=settings-hd.png id=9 1024 x 1024 @ 16 bpp => 2048 KB  
  7. cocos2d: "blitz_kurz.png" rc=1 name=(null) id=12 50 x 50 @ 32 bpp => 9 KB  
  8. cocos2d: "gameover.png" rc=8 name=gameover-hd.png id=7 1024 x 2048 @ 32 bpp => 8192 KB  
  9. cocos2d: "home.png" rc=32 name=home-hd.png id=4 2048 x 2048 @ 16 bpp => 8192 KB  
  10. cocos2d: "particleTexture.png" rc=2 name=(null) id=11 87 x 65 @ 32 bpp => 22 KB  
  11. cocos2d: "stern.png" rc=2 name=(null) id=2 87 x 65 @ 32 bpp => 22 KB  
  12. cocos2d: "clownmenu.png" rc=60 name=clownmenu-hd.png id=1 1024 x 2048 @ 32 bpp => 8192 KB  
  13. 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!:)

最後更新:2017-04-03 12:56:40

  上一篇:go SQL SERVER CHARINDEX函數
  下一篇:go 金融雲——信.用.雲