優化層面:代碼、貼圖、框架設計
一、代碼優化
1、用for代替foreach
原因:Mono下的foreach頻繁調用容易觸及堆上限,導致GC過早觸發,出現卡頓現象。尤其在update中,用for代替foreach。
2、string
連接兩個字符串的操作使用StringBuilder.Append來代替string。
String aa="abc";
aa+="def";
StringBuilder text=new StringBuilder("abc",10);
text.Append("def");
原因:每次使用string的時候,都會在內存裏創建一個新的字符串對象,需要爲該對象分配新的空間。
3、gameObject.tag = xxx
使用gameObject.CompareTag("XXX")來代替gameObject.tag = xxx。
原因:gameObject.tag會在內部循環調用對象分配的標籤屬性以及拷貝額外的內存。
4、使用ObjectPool對象池來管理對象,避免頻繁使用的Instance,Destroy。
二、貼圖優化
貼圖優化的效果更明顯。
1、壓縮png、jpg圖片 https://tinypng.com/。
2、巧妙通過調整紋理資源,來調整圖的大小。如:ugui九宮格,部分縮小後在unity中拉大。
3、IOS平臺使用PVRT壓縮,Android使用ETC1壓縮(ETC1只能支持非Alpha通道的圖)。
4、mipMap攝像機距離遠近替換不同圖片。
5、減少色彩使用(純色)。
三、框架設計
1、場景切換時加一個loading
當前舊場景內存未釋放的時候,加載新的場景,此時前後兩個場景的內存疊加,很容易達到內存峯值,導致崩潰。如果在中間加一個loading場景,由於loading場景較小,能夠避開內存的大量疊加,當舊場景的內存釋放完,新場景的初始化結束之後,再隱藏掉loading場景。
因此loading的兩個作用是:
(1)避免場景切換時大量內存疊加導致崩潰。
(2)避免後一個場景太大加載過慢卡住的尷尬。
2、把GUI模塊加入生命週期管理
主角、強化、技能、商城、進化、揹包、任務等系統如果全部打開,內存很容易達到峯值;
需要有效地管理系統模塊生命週期:
(1)將模塊進行劃分:Cache_10(經常打開的)、Cache_5(偶爾打開的)、Cache_0(只打開一次的)
(2)創建一個MuduleMananger類,內部的render方法每分鐘輪詢一次,
如果是Cache_0,一關閉就直接destroy釋放內存;
如果是Cache_10,10分鐘後自動釋放內存;
如果是Cache_5,5分鐘後自動釋放內存;
每次打開模塊,該模塊都重新計時。
代碼佔用的內存少,資源佔用的內存多。
1.資源類型
GameObject,Transform,Mesh,Texture,Material,Shader,noxss和各種其他Assets
2.資源創建方式
(1)靜態引用(初學者):在腳本中加public GameObject變量,然後拖動資源到面板上,然後Instantiate;
(2)Resources.Load(初學者),從Assets/Resources目錄下加載資源;
(3)通過AssetBundle.Load加載(最常用),然後Instantiate;
3.資源銷燬方式
(1)Destroy
(2)AssetBundle.Unload(false),釋放AssetBundle文件內存鏡像,不銷燬Load創建的Assets對象;
AssetBundle.Unload(true),釋放AssetBundle文件內存鏡像,銷燬Load創建的Assets對象;
(3)Resources.UnloadAsset(Object),釋放已加載的Asset對象;
Resources.UnloadUnusedAssets,釋放所有未引用的Asset對象;
4.生命週期
例:創建一個場景,場景中創建一個Empty GameObject,上面掛一個腳本來加載tank資源。
在Awake函數中使用協程來創建資源;
(1)使用Resources.Load()加載資源
IEnumerator LoadResources()
{
//釋放無用資源
Resources.UnloadUnusedAssets();
//等待5秒
yield return new WaitForSeconds(5.0f);
//加載資源
GameObject tank = Resources.Load("Tank") as GameObject;
yield return new WaitForSeconds(0.5f);
//實例化一個資源
GameObject.Instantiate(tank, Vector3.zero, Quaternion.identity) as GameObject;
yield return new WaitForSeconds(0.5f);
//銷燬一個資源
GameObject.Destroy(tankInst);
yield return new WaitForSeconds(0.5f);
//釋放無用資源
tank = null;
Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(0.5f);
}
結論:Resources.Load一個資源相對於Instantiate一個資源來說消耗的內存非常少,當Destroy之後,內存佔用減少的較少,Material和Texture等都沒有還原,以便於之後繼續的實例化。
(2)使用AssetBundle.Load加載資源
IEnumerator LoadAssets(string path)
{
//清除乾淨以免影響
Resources.UnloadUnusedAssets();
//等待5秒
yield return new WaitForSeconds(5.0f);
//創建一個WWW類
WWW bundle = new WWW(path);
yield return bundle;
yield return new WaitForSeconds(0.5f);
//AssetBundle.Load一個資源
Object obj = bundle.assetBundle.Load("Tank");
yield return new WaitForSeconds(0.5f);
//實例化一個資源
GameObject tankInst = Instantiate(obj) as GameObject;
yield return new WaitForSeconds(0.5f);
//銷燬一個資源
GameObject.Destroy(tankInst);
yield return new WaitForSeconds(0.5f);
//unload resources
bundle.assetBundle.Unload(false);
yield return new WaitForSeconds(0.5f);
//釋放無用資源
obj = null;
Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(0.5f);
}
結論:通過WWW Load AssetBundle加載一個資源時,會自動加載相應的Mesh、Texture和Material;
通過Resources.Load加載一個資源的時候,只會加載Mesh;
所以通過AssetBundle的方式加載一個資源,實例化的時候內存消耗較小。
3.通過靜態引用的方式加載一個資源
IEnumerator InstResources()
{
//清除乾淨以免影響
Resources.UnloadUnusedAssets();
//等待5秒
yield return new WaitForSeconds(5.0f);
//實例化一個資源
GameObject inst = GameObject.Instantiate(tank,Vector3.zero,Quaternion.identity) as GameObject;
yield return new WaitForSeconds(1f);
//銷燬一個資源
GameObject.Destroy(inst);
yield return new WaitForSeconds(1f);
//釋放無用資源
tank = null;
Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(0.5f);
}
結論:靜態綁定的方式和Resources.Load一樣,加載一個資源的時候,只會加載Mesh;