对于一款游戏来说,如果想要一个好的游戏体验需要一个稳定帧数。如果游戏跑60帧的话,那每帧只有16.67ms,但很多时候加载一个资源就花费了100ms甚至1000ms。这就是正常游戏中常见的卡顿问题,资源加载引起的卡顿带来很多的困扰,如何提高资源在速度是一个有意义的问题。由于不了解资源加载类背后的实现逻辑,这里通过实验来推测影响资源加载的因素。
下面分享一个简单的实验数据,来认识资源结构对加载速度的影响,首先看定义的数据类。
public class SingleData : MonoBehaviour {
public int data;
public string strData;
}
public class SingleDataArray : MonoBehaviour
{
public List<int> data;
public List<string> strData;
}
然后生成需要的数据,这里为了使结果对比明显设置对象数量为1W个。
第一个数据类型为多数据,单GameObject对象,一个GameObject挂载1W个mono。
static void MultData_OneObject()
{
GameObject go = new GameObject("MultData-OneObject");
GameObject child = new GameObject("ChildData");
child.transform.parent = go.transform;
for (int i = 0; i < MAX_COUNT; ++i)
{
SingleData data = child.AddComponent<SingleData>();
data.data = i;
data.strData = i.ToString();
}
PrefabUtility.CreatePrefab(PATH_ROOT + "MultData_OneObject.prefab", go, ReplacePrefabOptions.ReplaceNameBased);
}
第二个数据类型为多数据,多GameObject对象,每个GameObject挂载1个mono。
static void MultData_MultObject()
{
GameObject go = new GameObject("MultData-MultObject");
GameObject child = new GameObject("ChildData");
child.transform.parent = go.transform;
for (int i = 0; i < MAX_COUNT; ++i)
{
GameObject subGo = new GameObject("subGo" + i);
subGo.transform.parent = child.transform;
SingleData data = subGo.AddComponent<SingleData>();
data.data = i;
data.strData = i.ToString();
}
PrefabUtility.CreatePrefab(PATH_ROOT + "MultData_MultObject.prefab", go, ReplacePrefabOptions.ReplaceNameBased);
}
第三个数据类型为数据数组,单GameObject对象,数据数组参见SingleDataArray 定义。
static void DataArray_OneObject()
{
GameObject go = new GameObject("DataArray-OneObject");
GameObject child = new GameObject("ChildData");
child.transform.parent = go.transform;
SingleDataArray data = child.AddComponent<SingleDataArray>();
data.data = new List<int>();
data.strData = new List<string>();
for (int i = 0; i < MAX_COUNT; ++i)
{
data.data.Add(i);
data.strData.Add(i.ToString());
}
PrefabUtility.CreatePrefab(PATH_ROOT + "DataArray_OneObject.prefab", go, ReplacePrefabOptions.ReplaceNameBased);
}
第四个数据类型为数据数组,多个GameObject对象,数组数据挂载在其中一个GameObject上。
static void DataArray_MultObject()
{
GameObject go = new GameObject("DataArray-MultObject");
GameObject child = new GameObject("ChildData");
child.transform.parent = go.transform;
SingleDataArray data = child.AddComponent<SingleDataArray>();
data.data = new List<int>();
data.strData = new List<string>();
for (int i = 0; i < MAX_COUNT; ++i)
{
data.data.Add(i);
data.strData.Add(i.ToString());
GameObject subGo = new GameObject("subGo" + i);
subGo.transform.parent = child.transform;
}
PrefabUtility.CreatePrefab(PATH_ROOT + "DataArray_MultObject.prefab", go, ReplacePrefabOptions.ReplaceNameBased);
}
然后就是测试资源加载速度了,这四类数据数据内容相似有较大的对比意义。下面的测试数据验证了一些想法,但是结果还是和预想有较大的差异。
MultData_OneObject
TotalCount=10004,FileSize=1099KB,AssetBundleSize=392KB
PC Resources.Load 2058ms, Instantiate 1758ms
PC AssetBundle.LoadFromFile 3ms,AssetBundle.Load 2325ms,Instantiate=1797ms
MultData_MultObject
TotalCount=30004,FileSize=3677KB,AssetBundleSize=1276KB
PC Resources.Load 414ms, Instantiate 61ms
PC AssetBundle.LoadFromFile 10ms,AssetBundle.Load 1481ms,Instantiate=72ms
DataArray_OneObject
TotalCount=5,FileSize=122KB,AssetBundleSize=81KB
PC Resources.Load 0ms, Instantiate 2ms
PC AssetBundle.LoadFromFile 0ms,AssetBundle.Load 2ms,Instantiate=2ms
DataArray_MultObject
TotalCount=20005,FileSize=2732KB,AssetBundleSize=853KB
PC Resources.Load 262ms, Instantiate 25ms
PC AssetBundle.LoadFromFile 7ms,AssetBundle.Load 782ms,Instantiate=29ms
数据分析
数据比较多,也只是测试了在PC上执行的速度。由于资源是放在SSD上的所以在I/O不会出现性能问题,结果可以认为是CPU的开销。手机上由于I/O以及更弱的CPU加载速度回更慢,不过这里的数据对比已经非常有参考意义与价值了。
对比MultData_OneObject和MultData_MultObject可以发现单个GameObject挂载1W的mono对性能有极大的影响,而1W个GameObject每个各挂1个mono则有较好的表现。虽然在加载上依旧很慢,但是资源实例化(Instantiate)的提升是显著的。
接着对比MultData_MultObject和DataArray_MultObject可以发现,通过把mono的数据合并到一个对象,并以数组的方式存储也能对资源加载和资源实例化有一个较大的提升。
最后对比DataArray_MultObject和DataArray_OneObject可以发现,当把1W个无用的GameObject对象移除后的资源加载和资源实例化有一个极大的提升,即使同步加载这么多数据也不会出现什么问题了。
通过对比不同资源组织方式对性能的影响,我们清晰的认识到了一些对资源性能影响的关键性要素。对象数量极大的影响资源加载速度,在AssetBundle打包后更加明显。同时GameObject数量对资源加载速度的影响要大于mono对象对加载速度的影响。最后如果一个GameObject挂载过多的mono则会导致资源加载与资源实例化性能急剧降低。
更进一步
在了解对象数量对加载速度影响后,我们想通过把资源按数组的方式组织来提升资源加载速度。然而很多情况下这是很难达成的,也有诸多不方便的地方。
之后在Unity文档中发现了ScriptableObject能帮助我们减少对象数量提高加载速度,同时还能帮助我们节省内存。
ScriptableObject is a class that allows you to store large quantities of shared data independent from script instances. Do not confuse this class with the similarly named SerializableObject, which is an editor class and fills a different purpose. Consider for example that you have made a prefab with a script which has an array of a million integers. The array occupies 4MB of memory and is owned by the prefab. Each time you instantiate that prefab, you will get a copy of that array. If you created 10 game objects, then you would end up with 40MB of array data for the 10 instances.
ScriptableObject主要的作用是帮助减少重复对象,把数据直接以资源的形式保存以复用资源。当然比较好的做法是在制作的时候用mono保存数据,最后导出为文本数据。这样也能极大的提高资源加载速度。
不同的资源加载方式对资源加载速度也有较大差异,资源的形式也是多种多样的。通过对不同事物建立对应模型并测试,可以帮助认识事物、改善事物。最近比较忙没做资源加载方式对不同资源类型资源的影响,有兴趣的同学可以尝试下,相信会有很大的收获。
[完 2017-07-23 Carber]
网友评论