目前开发的游戏在地图部分的加载是一次性完全加入内存中。随着项目的发展,目前已经有6块区域,平均一块区域要占用2-4MB内存,6块一共大概也有15MB左右。这个对于手机游戏来说已经是一个不小的数目了。更重要的是这种方式是不可持续的,随着区域的增加,内存占用还会不停地增长。因此必须对现有的方法进行替换。
之前最早的想法是移动到后面区域的时候,动态释放前面的区域。这个想法也就是一般端游的动态资源管理的办法。但由于我们使用unity3.5.7开发,在底层开发上存在诸多限制,比如加载Perfab和释放Prefab以及资源的过程虽然可以在携程中进行,但无法在后台进行,(我们区域间的切换是连续和平滑的,一旦出现顿卡,会比较明显)而且在需求上也没有这样的要求,因此不必要将问题复杂化。
现在的做法是这样的,以n个小区域为一个大单位,一次性加载这部份的资源。当达到目前区域的尽头时,提示用户操作切换到下一个大区域。这个想法第一个能想到方案就是将n个大区域划分为n个Scene。但是Scene的单位还是太大了,而且按目前的现状,这样的修改会带来复杂性。因为不同的大区域其实是共用了UI的界面的。因此还是将不同小区域划分单独的Prefab,按大区域分批加载,进入下一个大区域时,将之前的创建的区域删掉,并删掉他的资源。
这个方案其实很简单,但实现的过程中还是遇到一些小问题。最明显的一个是问题就是删除资源调用Resources.UnloadUnusedAssets。这个函数很慢,在红米手机上面执行时间超过了2-3秒,也就是说我们一次大区域切换就必须等待3秒钟,这个体验很差。按我的理解Resources.UnloadUnusedAssets这个函数实现应该有两种可能性,一种就是类似于Ogre,判断所有资源的引用计数,如果计数等于1,则表示没有任何外部对象引用。一种就是类似于gc,对场景内的GameObject引用的资源遍历,打标记。如果结束后,有资源对象没有被打标记,则代表没有引用了。当然不管是哪一个实现方式,这个都是很慢的操作,必须舍去这个。其实资源最大的部分就是贴图,删除了贴图,我们的目地应该也就达到了。因此可以在离线的时候收集该区域Prefab引用的贴图资源,然后使用Resources.UnloadAsset去手动卸载。这样做了还有一个问题,就是小区域是使用的Resources.Load进行加载的,这个操作会缓存之前加载过的Prefab。由于贴图被释放了,而Prefab并没有被释放,就会造成老的Prefab引用的贴图错误。如果我们不清理掉这些老的Prefab的话,这些Prefab创建出来的小区域都将会是错误的贴图显示。但是清理这些缓存Prefab的接口还是只有用Resources.UnloadUnusedAssets。这样一来又一次回到了原点了。怎么来解决这个问题呢?最后想到的办法是将小区域打包成AssetBundle,这样就不会遇到需要清理缓存Prefab的问题了。