【系统】unity3D中的assetbundle资源管理器(一)

导言

assetbundle 的解释

说起 U3D 的 assetbundle 大家应该都不会很陌生吧,我的理解是 —— assetbundle 属于一种 unity 自己可认的压缩文件格式,把你想放在一个包里的资源给打包到一起,这样就形成了一个 assetbundle。

但是使用这个压缩格式还有一个注意点,就是可能你要打包的资源,它引用到了另外一处资源,即它对一个外部资源有依赖。所以你在打 assetbundle 的时候,unity 会把这个依赖关系记载在 manifest 的 Dependecies 上,虽然是对 asset 的依赖,但是 unity 帮你管理的是大范围的依赖关系,所以记录下来的是 assetbundle 对 assetbundle 的依赖。

为什么要做 assetbundle 的资源管理器

当我们在使用 assetbundle 做资源热更新的时候,我们需要了解,在使用asset资源的时候,assetbundle是怎么被使用的。比如我们需要加载一个prefab的时候,我们需要先解包这个prefab所在的assetbundle,然后我们还需要解包这个prefab所依赖的asset所在的assetbundle。这个asset到asset的依赖是unity帮我们管理的,会记录在asset中,我们在用文本打开资源的文件的时候,是可以看到在unity中这个asset的所有信息,包括它依赖的asset,(这部分就不详细展开了)。但我们把assetbundle在内存中解压之后,内存就会存放这个assetbundle的镜像,内存就会变大,如果我们只解压不去卸载这部分内存的话,那游戏内存就会持续升高,超出系统所给的内存大小,系统就会把引用杀死,造成闪退。所以我们需要在加载的时候解压assetbundle,在不用的时候把这个assetbundle给回收了。

实现方案

准备阶段

我们将在这个阶段先解压assetbundle的整个manifest,然后给所有的assetbundle给记一个int的唯一标识,这步是为了在后面使用assetbundle的时候可以使用int来做标识,减少string的使用,节省gc。再把所有assetbundle所包含的asset的资源和这个assetbundle做好一个依赖关系。

使用阶段

当我们要加载某一个asset的时候,我们先判断这个asset有被加载,加载过的,我们就asset做一个引用计数+1的操作,如果没有的话,我们就去加载这个asset所在的assetbundle,然后我们在加载assetbundle的时候,看这个assetbundle还使用了哪些assetbundle,我们都做一次加载,做一次引用计数加1的操作,然后我们在实例化出来这个asset。

使用完成阶段

但我们卸载asset的时候,会对这个asset来做一个引用计数-1的操作,如果引用计数小于等于0了,我们就可以卸载这个asset,进而对这个assetbundle来做一次-1的操作,但assetbundle的计数也小于等于0的时候,我们就可以卸载这个assetbundle了,对它所依赖的assetbundle的引用计数-1。这一整个assetbundle的使用周期就结束了

注意点

在做assetbundle的引用计数的时候,我遇到了一个问题,我是使用GetDirectDependencies来获取这个assetbundle所依赖的单一次的assetbundle,所以如果所依赖的assetbundle再依赖我这个assetbundle的时候,我这时候就会出现循环依赖(a->b->a这种情况),然后卸载就会出现问题,因为我在引用计数的时候对a其实做了两次引用计数了,卸载我其实就引用计数-1,然后因为还有一次引用计数所以b不会被卸载,所以对a的引用也不会减少,这样实际上我们其实不需要引用了,但是事实上循环引用导致我们卸载不掉。
在这里我提供两个解决方案:

  1. 我们做一次调用栈的计数,如果这次调用循环中,如果这次调用的资源是记录在栈里的,那我们就不做引用计数+1的操作了。
  2. 我们换一个接口,GetAllDependencies 这个接口返回的就是这个assetbundle所依赖的所有资源,包括它依赖的依赖。这样我们就可以一次性对所有的资源做引用计数加1了。

优化点

  1. 上面所说的assetbundle的循环依赖,其实我们可以做关于asset的依赖管理,asset的依赖是不太会出现asset之间的循环依赖。这种优化还能解决我们关于assetbundle的提早释放。比如assetbundle(a)中两个资源(a1,a2)对两个assetbundle(b,c)里的资源(b1,c1)做依赖。(a1->b1,a2->c1)这种情况的依赖在我现在的方案中,因为是记录的assetbundle的依赖关系,所以就算a1被卸载了,不使用了,但是a还有一个引用,所以a不会被卸载,b和c也就不会被卸载。其实b不再被使用了,但是还是没有办法被卸载了。如果我们做的是asset之间的依赖引用,那么我们a1被卸载了,b就不再被引用了,就可以被卸载出来

  2. 因为我们使用的assetbundle做的资源依赖管理,所以我们调用的卸载接口是直接assetbundle.Unload(true)。这个接口的问题就是,如果我们的资源还在使用,却调用到了这个接口,那么资源就会丢失,就会出现紫色的。所以最后还是要回到使用asset的依赖管理上。

demo

第一版本的demo