unity内存优化教程笔记

优化层面:代码、贴图、框架设计


一、代码优化

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;

















你可能感兴趣的:(Unity3D)