使用Unity開發安卓遊戲怎麼進行性能優化

幾周前,我開始寫一款叫Sky Blocks的遊戲,使用unity引擎並且發佈在瞭安卓手機上,如果你有時間可以在Google Play上下載體驗一下。在寫這個遊戲的過程中,我遇到的問題大部分都是性能方面的。下面我來介紹一下這款遊戲,以及性能問題的解決方案。這款遊戲混合瞭《俄羅斯方塊》和《太空入侵者》這兩個遊戲的玩法。玩傢將方塊盡量的擺成一條線,方塊會從下往上運動,最後停在屏幕的最頂端,但是不會想《俄羅斯方塊》那樣連成線以後就立即消失。你有60秒的時間擺出盡量多的線,時間一到就會有UFO入侵你完成的防禦工事,一旦它們將你的防禦都摧毀以後就會攻打地球,當地球的生命值為零遊戲就結束瞭。這遊戲聽上去挺簡單的,但要做好還得下些功夫,不過,這樣才有意思,非常有意思!

寫代碼前一定要計劃好

寫代碼之前一定要計劃好,這非常重要。我在開始開發Sky Blocks之前就沒有想好要做什麼,沒有考慮清楚遊戲的玩法。在正式開發Sky Blocks之前我走瞭一些冤枉路,我用JavaScript和HTML5寫瞭一個《俄羅斯方塊》遊戲,後來又用C#將其移植到Unity,但是在移植的過程中沒有考慮二維和三維的區別,僅僅是復制粘貼的法,遇到碰撞檢測的bug也隻是做瞭小幅的調整。因為沒有簡單的在每次刷新時更新整個遊戲面板,所以我將整個面板都佈滿一個最簡單的方塊,這些方塊用來作為背景網格定位。每次網格刷新時都會將所有方塊銷毀然後重新實例化,對於我來說,這樣做已經很不錯瞭,在電腦上運行的很流暢。一般,一個面板上會有20行10列的網格,有可能會不斷地有200個背景方塊需要渲染、銷毀、重建。另外,還會有不斷增加的防禦線(10個方塊每行),每個方塊都會有自己的材料,每個材料都會繪制。假如,一個遊戲面板上有150-200個方塊需要渲染,那麼,就會調用大約200次的繪制函數。如果我在動手之前有明確的計劃,我就會發現這麼做遊戲是不會運行太久,有可能就會在一開始就省去我大部分時間。

解決

在動手之前畫一個草圖是一個好主意,這樣就可以通過整體分析找到哪些內容是不變的?這些內容有什麼不同?在Sky Blocks這遊戲中是遊戲面板、防禦線和UFO。遊戲面板用來控制遊戲,包括可移動的方塊和已經鎖定住的靜態方塊。防禦線由10個方塊組成並排在一條線上。UFO都是由網格組成,它們可以在防禦線上面移動。抓住這幾點,我就接近成功瞭!

減少繪制函數調用

我之前也提到過,我的實現方式會大量調用繪制函數。當在安卓設備(三星galaxy S4)上運行遊戲,隨著遊戲內容越來多,我發現遊戲變得越來越卡瞭。    為瞭讓遊戲運行流暢,我首先考慮減少繪制函數的調用次數。我在網上查相關資料,繪制函數會消耗多少資源?怎麼造成的?該怎麼優化?    意識到效率問題,我不能出一個好的測試方案,找到一些測例會對CPU和GPU造成嚴重的壓力就行。一些測例沒有太大的影響,但有些則會讓我的幀率變得很低。

影響效率的問題還是比較容易發現,其根源就是不同的方塊有不同的材質。要減少繪制函數的調用就得減少object的數量和材質數量。因此,我寫瞭一段代碼用來生成一個網格,這個網格就可以替代原來有200個立方體組成的遊戲面板。然後,我用頂點著色的方法替代貼圖方法。最後,我將材質的著色器換成在網上找到的一個不發光的頂點著色。現在我將所有重復的繪制函數調用都減少為隻調用一次!對於防禦線的處理,我用瞭類似的方法。我將所有材質都替換成瞭不發光的頂點著色,我沒將它們用一個網格代替,而是保留瞭10個立方體,因為,替換瞭材質就已經不再多次調用繪制函數瞭。非常遺憾,我事先沒有準備,所以這裡沒有提供優化前後的區別對比圖片。下面是一個已經優化好的遊戲截圖:5上圖目前這個方塊隻調用瞭一次繪制函數(Batches:20、SetPass calls:20),之前提到每個方塊由4-5個立方體組成,這個方塊就處理瞭4次。6現在,已經放置瞭兩個方塊,但處理次數隻增加瞭一次。若是之前的版本,每激活一個方塊中的一個立方體都會處理一次。7防禦線也是同一種模式,但實際上有10個四邊形,它們采用頂點著色法並且共享材質。這個例子中,我們沒必要將防禦線做成一個網格來繪制,因為,unity會通過“Saved by batching”來自動處理這個問題。UFO比較棘手。每一個UFO都由上中下三塊網格組成,因為,我想讓UFO的外觀是隨機組成的。UFO的每一部分包含3-4個材質,那麼一個UFO很有可能就會調用12-17次繪制函數,而實際上會調用17-30次。會有2-3個UFO同時出現在屏幕上,那麼就會調用50-100次繪制函數,該死!8對於這點,我迫切的想要減少繪制函數的調用次數,所以我在網上找到瞭一個腳本,它可以將這些網格合並為一個。但是這個腳本不可以處理材質的問題,於是我最終將UFO都著成一個顏色,這樣UFO變得非常醜。我手動改瞭一下那個腳本,現在UFO可以繪制最多2種顏色瞭。來看看行得通嗎?現在隻調用瞭30次繪制函數,但是我也不確定運行效率是否真的有所提升,不過至少我將每個UFO繪制函數的調用次數減少到瞭10次以下。9減少繪制函數的調用次數是不是適合所有的情況呢?當然不是。如果你可以減少當然最好,但是對於有些非常棘手的部分,隻有通過犧牲顯示效果來提升效率。現在,對於繪制函數調用次數的優化,我已經從150-200次減少到瞭75-90次,還是很不錯的!最後,輪到UFO發射的激光,它們也都是單獨的材質,當UFO進入狂暴狀態時,繪制函數需要調用30-40次。非常幸運,這次隻需要建立一個原始的四邊形,它使用不發光的頂點著色法,這樣,所有的激光隻需要調用一次繪制函數,即使是UFO都進入瞭狂暴狀態。現在,遊戲中所有的繪制函數減少到瞭30-45次,不錯不錯!剩下的就是UI中的繪制函數瞭,但是UI的處理我沒有找到好的辦法,不過我還是非常滿意現在的效率瞭,現在遊戲比之前運行得流暢許多。10UFO還是占瞭一部分繪制函數,但是考慮到已經從30減少到瞭不到10次,還是有很大進步。一個首要的原則就是使用盡量少的材質,如果可以,最好共享材質而不是每一個都實例化,這樣,會減少很多繪制函數調用次數。

資源隻加載一次

在我的一些代碼中,我使用瞭一些Resource.Load函數,但是由於Unity不會將已經加載的資源緩存下瞭,所以遊戲運行中就會反復加載資源,這樣非常消耗效率。我之前就會把激光的材質反復加載,這樣雖然不會在PC上有什麼影響,但是在安卓設備上就不行瞭。為瞭避免反復加載遊戲資源,我建立瞭一個靜態的Dictionary(資源名稱為Key,資源為value),當我要用某個資源的時候我會先去查詢字典中是否有對應的Key,若是沒有才會加載資源,反之,我就直接用緩存的資源。

避免多次調用Instantiate函數

我沒有考慮過GameObject.Instantiate函數的開銷,在代碼中大量的使用瞭它,我本以為這個函數的開銷也就和new一個類對象一樣,但是,我錯瞭!在GameObject的新建與銷毀時都比較消耗CPU。然而,我在實現UFO攻擊的時候就遇到瞭這個問題,每當UFO開火時都會新建一個激光束,然後再非常短的時間內又將其銷毀,這樣,在短短1.5秒內就有可能調用20-40次新建與銷毀函數。太棒瞭!我終於找到UFO攻擊時性能不高的原因瞭。解決這個問題的方法就是做一個對象池。我構建瞭一個空的對象池,當我需要新建激光束的時候,我會先去檢查對象池中是否已經存在,如果存在,改變一下它的位置與血量就可以直接用瞭。如果對象池中沒有我想要的,我就會像之前一樣新建一個。當需要銷毀這個對象時,我並沒有直接銷毀,而是又將其交還給對象池,然後將其設置為非激活狀態。通過對象池的方法,在UFO攻擊的時候CPU的開銷降低瞭25-30%!

總結

繪制函數開銷非常大,你應該盡量減少調用次數。減少材質數量可以非常有效的減少繪制函數調用。少用不同的貼圖,可以將多張貼圖烘培在一個圖集裡面。如果是2D遊戲就少用光照,甚至可以像我這樣做,不要光照隻用色彩描述就可以瞭。著色器不要帶有參數,而是用不發光的頂點著色,我這個項目中就是這樣做的。以上這些可以大大減少繪制函數的調用次數。如果你覺得這樣視覺效果達不到你的要求,可以試試增加一些網格。Instantiate函數非常慢,所以盡量少用它。在遊戲一開始你可以將大部分經常需要用到的對象構建好放在對象池中,要用的時候將其激活。繪制函數和Instantiate函數並不是唯一優化對象,繪制函數銷耗CPU和GPU,Instantiate函數消耗CPU。如果你某個非常精細的模型或者過程非常耗時,這時減少繪制函數並不能起到什麼作用。優化CPU的消耗是重中之重,你可以梳理代碼,看看是否有大量重復執行的內容,然後想辦法優化它們。from:騰訊遊戲開發者平臺

Comments are closed.