譯者: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界面,我還會加入一些最佳實踐和小技巧。算法
你們猜一下:)編程
在大部分狀況下,是紋理(textures)消耗了遊戲程序大量的內存。所以,紋理是咱們首要考慮優化的對象,特別是當你碰到內存警告的問題的時候。api
cocos2d裏面紋理加載分爲兩個階段:1.從圖片文件中建立一個UIImage對象。2.以這個建立好的UIImage對象來建立CCTexture2D對象。這意味着,當一個紋理被加載的時候,在短時候內,它會消耗兩倍於它自己內存佔用的內存大小。(譯註:爲何只是短期內呢?由於autoRelease pool和引用計數的關係,臨時建立的UIImage對象會被回收。)
當你在一個方法體內,連續不斷地加載4個紋理的時候,這個內存問題會變得更加糟糕。由於在這個方法還沒結束以前,每個紋理都會消耗兩倍於它自己的內存。
我不是很肯定,如今的cocos2d是否仍然如此。或者這種狀況是否只適用於手工引用計數管理,或許ARC不會如此呢?我習慣於按順序加載紋理,可是在加載下一個紋理以前要等待一幀。這將會使得任何紋理加載的消耗對內存的壓力下降。由於等待一幀,引用計數會把臨時的UIImage對象釋放掉,減小內存壓力。此外,在後續的文章中,若是你想在背景線程中按序加載紋理的話,也能夠採用這種方法。
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位顏色深度的紋理來加載。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位紋理的不足之處了:它因爲顏色總量的減小,有一些圖片顯示起來可能會失真,並且可能會產生「梯度」。
幸運的是,咱們有TexturePacker.(後面簡稱TP)
TP有一個特性叫作「抖動」,它可使得本來因爲顏色數量減小而產生的失真問題獲得改善。(TP裏面有不少抖動算法,關於這些算法,讀者能夠參考我翻譯的另外一篇文章)。
特別是在擁有Retina顯示的像素密度下,你幾乎看不出16位與32位的紋理之間的顯示差異。固然,前提是你須要採用「抖動」算法。
cocos2d默認的顏色深度將會把全部的紋理都渲染到16位的color framebuffer裏面,而後再顯示到你的設備屏幕上面。既然這樣,咱們爲何不把全部的紋理的格式都弄成16位呢,32位又有什麼用呢?反正它原本就會渲染到16位的framebuffer上去的。這個問題有點太底層了,我不想深挖下去,並且我也不適合解釋這個問題。(譯者:哈哈,知之爲知之,不知爲不知)
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%的內存消耗。
TP讓你能夠建立PVR格式的紋理。除了PVR紋理支持NPOT外,它們不只能夠不是2的冪,並且還能夠不是方形的。
PVR是最靈活的紋理文件格式。除了支持標準的未壓縮的RGB圖片格式外,支持有損壓縮的pvrtc格式。另外,未壓縮的pvr格式的紋理的內存消耗很是地低。不像png圖片那樣要消耗2倍於自己內存佔用大小的內存,pvr格式只須要消耗紋理自己內存大小再加上一點點處理該圖片格式的內存大小。
pvr格式的一個缺點就是,你不能在Mac上面打開查看。可是,若是你安裝了TP的話,就可使用TP自帶的pvr圖片瀏覽器來瀏覽pvr格式的圖片了。(強烈建議你們購買TP,支持TP,不要再盜版了)
使用PVR格式的文件幾乎沒有缺點。此外,它還能夠極大地提升加載速度,後面我會解釋到。
在三種可選用的pvr文件格式中,優先選擇pvr.ccz格式。它是專門爲cocos2d和TP設計的。在TP裏面,這是它生成的最小的pvr文件。並且pvr.ccz格式比其它任何文件格式的加載速度都要快。
當在cocos2d裏面使用pvr格式的紋理時,只使用pvr.ccz格式,不要使用其它格式!由於它加載速度超快,並且加載的時候使用更少的內存!
PVR紋理支持PVRTC紋理壓縮格式。它主要是採用的有損壓縮。若是拿PVRTC圖片與JPG圖片做對比的話,它只有JPG圖片中等質量,可是,最大的好處是能夠不用在內存裏面解壓縮紋理。
這裏把32位的png圖片(左邊)與最佳質量的PVRTC4(4位)圖片(點擊圖片查看完整的大小)做對比:
注意,在一些高對比度的地方,明顯有一些瑕疵。有顏色梯度的地方看起來還好一點。
PVRTC確定不是大部分遊戲想要採用的紋理格式。可是,它們對於粒子效果來講,很是適用。由於那些小的粒子在不停地移動、旋轉、縮放,因此你很難看出一些視覺瑕疵。
TP提供的PVR格式不只有上面兩種,還包括TC2和TC4這兩種沒有alpha通道的格式。
這裏的alpha和16位紋理的alpha是同樣的。沒有alpha通道意味着圖片裏面沒有透明像素,可是,更多的顏色位會用來表示顏色,那麼顏色質量看起來也會更好一些。
有時候,PVRTC圖片格式指的是使用4位或者2位顏色值 ,可是,並不徹底是那樣。PVRTC圖片格式能夠編碼更多的顏色值。
就像標題所說,盡你所能,必定要預先加載全部的紋理。若是你的全部的紋理加起來不超過80MB內存消耗的話(指的是擁有Retina顯示的設備,非Retina的減半考慮),你能夠在第一個loading場景的時候就所有加載進來。
這樣作最大的好處在於,你的遊戲體驗會表現得很是平滑,並且你不須要再擔憂資源的加載和卸載問題了。
這樣也使得你可讓每個紋理都使用合適的紋理像素格式,並且能夠更方便地找出其它與紋理無關的內存問題。由於若是與紋理有關,那麼在第一次加載全部的紋理的時候,這個問題就會暴露出來的。若是全部的紋理都加載完畢,這時候再出現內存問題,那麼確定就與紋理無關了,而是其它的問題了。
若是你知道問題與紋理無關的話,那麼你查找剩下的內存問題將會變得更加簡單。並且你避免了前面說的這種狀況:當2048*2048的紋理加載的時候,它原本只須要消耗16MB內存,可是短期會衝到32MB內存。後面會提出一種方法來解決「間歇性內存飆高」(「譯者發明滴」)的方法。(譯者:但願下次開發者的對話中「間歇性內存飆高」的說法會出現,呵呵)
因爲加載紋理時額外的內存消耗問題,因此,採用按紋理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%的效率。過早和過多細節優化對於大多數程序而言,是須要極力避免的)。
上面提到的例子中,紋理的引用計數可能有點讓人看不懂。你會發現,紋理集有很高的retain count,即便你知道這些紋理集中的紋理當前並無被使用。
你可能忽略了一件事:CCSprteFrame會retain它的紋理。所以,若是你使用了紋理集,你要徹底移除它不是那麼容易。由於,由這個紋理集產生的sprite frame仍是保留在內存中。因此,你必須調用CCSpriteFrameCache的removeSpriteFramesFromTexture方法,能完全清除紋理緩存中的紋理集。(譯註:記住,不是你調用對象的release方法了,對象的內存就會被釋放掉,而是引用計數爲0了,內存纔會被刪除)
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromTexture:uncachedTexture];
你也可使用 removeSpriteFramesFromFile,並指定一個紋理集的.plist文件來清除緩存起來的精靈幀(spriteframes).
Note: 這一點只針對cocos2d v1.0有效,而cocos2d v2.x在加載以前會預先判斷。
這樣看起來有點無知(innocent):
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"ui.plist"];
可是,要注意,CCSpriteFrameCache並不會去檢查一個精靈幀是否已經被緩存起來了!這與CCTextureCache的動做方式有所不一樣,它每次都會去加載spriteframes.
這個過程到底須要耗費多少時間呢,這取決於你提供的.plist文件中精靈幀的數量。我注意到,只有14幀的plist加載與有280幀的plist加載有着很大的區別。因此,對於精靈幀的加載,你也須要謹慎。
因此,你要避免一些沒必要要的addSpriteFrames*方法調用。由於那邊致使場景切換時產生小的卡頓。
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場景調度一個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