GameObject, Transform, Mesh, Texture, Material, Shader, Script和各种其他Assets。
IEnumerator LoadResources()
{
// 清除干净以免影响测试结果
Resources.UnloadUnusedAssets();
// 等待5秒以看到效果
yield return new WaitForSeconds(5.0f);
// 通过Resources.Load加载一个资源
GameObject tank = Resources.Load("Role/Tank") as GameObject;
yield return new WaitForSeconds(0.5f);
// Instantiate一个资源出来
GameObject tankInst = GameObject.Instantiate(tank, Vector3.zero, Quaternion.identity) as GameObject;
yield return new WaitForSeconds(0.5f);
// Destroy一个资源
GameObject.Destroy(tankInst);
yield return new WaitForSeconds(0.5f);
//释放无用资源
tank = null;
Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(0.5f);
}
下面是统计结果:
数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
---|---|---|---|---|---|---|---|
初始 | 72.8M | 1271/8.0M | 35/223.0K | 25/10.2K | 7 | 211 | 2187 |
Resources.Load | 72.8M | 1271/8.0M | 36/0.8M | 25/10.2K | 7 | 211 | 2280 |
Instantiate | 75.3M | 1272/9.3M | 36/0.8M | 26/10.7K | 52 | 303 | 2375 |
Destroy | 74.7M | 1272/9.3M | 36/0.8M | 26/10.7K | 7 | 211 | 2283 |
Resources.UnloadUnusedAssets | 72.3M | 1271/8.0M | 35/223.0K | 25/10.2K | 7 | 211 | 2187 |
从这里我们得出如下结论:
若没有调用Resources.UnloadUnusedAssets,则结果如下:
统计结果如下:
数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
---|---|---|---|---|---|---|---|
初始 | 58.9M | 1258/7.5M | 34/219.2K | 22/9.0K | 7 | 117 | 2078 |
Resources.Load | 60.0M | 1258/7.5M | 35/0.8M | 22/9.0K | 7 | 117 | 2171 |
Instantiate | 62.5M | 1259/8.9M | 36/0.8M | 23/9.5K | 52 | 209 | 2256 |
Destroy | 61.8M | 1259/8.9M | 35/0.8M | 23/9.5K | 7 | 117 | 2174 |
得出如下结论:
如果不手动执行Resources.UnloadUnusedAssets,则多余的Mesh,Material和Object不会主动释放。
代码如下:
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);
// Instantiate一个资源出来
GameObject tankInst = Instantiate(obj) as GameObject;
yield return new WaitForSeconds(0.5f);
// Destroy一个资源
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);
}
执行结果如下:
统计结果如下:
数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
---|---|---|---|---|---|---|---|
初始 | 59.9M | 1267/7.8M | 35/223.0K | 25/10.2K | 7 | 127 | 2099 |
new WWW | 62.0M | 1267/7.8M | 35/223.0K | 25/10.2K | 7 | 127 | 2099 |
AssetBundle.Load | 64.5M | 1268/9.2M | 36/0.8M | 26/10.5K | 7 | 127 | 2196 |
Instantiate | 65.6M | 1268/9.2M | 36/0.8M | 26/10.7K | 52 | 219 | 2288 |
Destroy | 63.9M | 1268/9.2M | 36/0.8M | 26/10.7K | 7 | 127 | 2196 |
AssetBundle.Unload | 63.7M | 1268/9.2M | 36/0.8M | 26/10.7K | 7 | 127 | 2196 |
Resources.UnloadUnusedAssets | 61.8M | 1267/7.8M | 35/223.0K | 25/10.2K | 7 | 127 | 2099 |
得出如下结论:
通过WWW Load AssetBundle的方式加载一个资源时会自动加载相应的Mesh,Texture和Material,而通过Resouces.Load方式进行加载只会加载Mesh信息。因此通过AssetBundle方式加载后Instantiate一个资源的内存消耗较小,本例中AssetBundle.Load增加了2.5M的内存,而Instantiate增加了1.1M的内存。相比较Resources.Load后Instantiate的内存增量要小很多。
代码如下:
IEnumerator InstResources()
{
Resources.UnloadUnusedAssets();
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(1f);
}
执行结果如下:
统计结果如下:
数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
---|---|---|---|---|---|---|---|
初始 | 62.0M | 1268/7.9M | 36/0.8M | 25/10.2K | 7 | 134 | 2202 |
Instantiate | 64.4M | 1269/9.2M | 36/0.8M | 26/10.7K | 8 | 137 | 2207 |
Destroy | 64.0M | 1269/9.2M | 36/0.8M | 26/10.7K | 7 | 134 | 2204 |
UnloadUnused Resources | 62.3M | 1268/7.9M | 35/226.3K | 25/10.2K | 7 | 134 | 2107 |
得出结论如下:
通过静态绑定的方式各种资源的加载顺序和Resources.Load的方式是一样的,一个GameObject创建时,其Component中静态绑定的GameObject只会加载Mesh信息,只有当该GameObject Instantiate出来之后才会加载Texture和Material信息。
加载资源的过程可以分为两个阶段,第一阶段是使用Resources.Load或者AssetBundle.Load加载各种资源,第二阶段是使用GameObject.Instantiate克隆出一个新的GameObject。
Load的资源类型包括GameObject, Transform, Mesh, Texture, Material, Shader和Script等各种资源,但是Resources.Load和AssetBundle.Load是有区别的。
使用Resources.Load的时候在第一次Instantiate之前,相应的Asset对象还没有被创建,直到第一次Instantiate时才会真正去读取文件创建这些Assets。它的目的是实现一种OnDemand的使用方式,到该资源真正使用时才会去创建这些资源。
而使用AssetBundle.Load方法时,会直接将资源文件读取出来创建这些Assets,因此第一次Instantiate的代价会相对较小。
上述区别可以帮助我们解释为什么发射第一发子弹时有明显的卡顿现象的出现。
然后我们再来了解一下Instantiate的过程。Instantiate的过程是一个对Assets进行Clone(复制)和引用相结合的过程,Clone的过程需要申请内存存放自己的数据,而引用的过程只需要直接一个简单的指针指向一个已经Load的资源即可。例如Transform是通过Clone出来的,Texture和TerrainData是通过引用复制的,而Mesh,Material,PhysicalMaterial和Script是Clone和引用同时存在的。以Script为例,Script分为代码段和数据段,所有需要使用该Script的GameObject使用的代码是一样的,而大家的数据有所区别,因此对数据段需要使用Clone的方式,而对代码段需要使用引用的方式来复制。
因此Load操作其实Load一些数据源出来,用于创建新对象时被Clone或者被引用。
然后是销毁资源的过程。当Destory一个GameObject或者其他实例时,只是释放实例中那些Clone出来的Assets,而并不会释放那些引用的Assets,因为Destroy不知道是否有其他人在引用这些Assets。等到场景中没有任何物体引用到这些Assets之后,它们就会成为UnusedAssets,此时可以通过Resources.UnloadUnusedAssets来进行释放。AssetBundle.Unload(false)不行,因为它只会释放文件的内存镜像,不会释放资源;AssetBunde.Unload(true)也不行,因为它是暴力的释放,可能有其他对象在引用其中的Assets,暴力释放可能导致程序错误。
另外需要注意,系统在加载新场景时,所有的内存对象都会被自动销毁,这包括了Resources.Load加载的Assets, 静态绑定的Assets,AssetBundle.Load加载的资源和Instantiate实例化的对象。但是AssetBundle.Load本身的文件内存镜像(用于创建各种Asset)不会被自动销毁,这个必须使用AssetBundle.Unload(false)来进行主动销毁。推荐的做法是在加载完资源后立马调用AssetBunble.Unload(false)销毁文件内存镜像。
下图可以帮助理解内存中的Asset和GameObject的关系。
http://game.ceeger.com/forum/read.php?tid=4394
http://game.ceeger.com/forum/read.php?tid=4466