[资源管理]代码赋值资源管理

前言

在前几天做了一次友好的学术交流,在这次交流中,对方提出了一种关于资源管理的新的思路,就是自己管理asset直接释放ab。我在事后想了一下,感觉这个能省一部分的内存,但是其中的坑还是有点的,所以现在就能写一篇文章来介绍这个方案。

正文

方案

在这套方案里,我们在加载好asset的之后,我们就可以调用unload(false)把ab给卸载了,然后通过resource.unload(asset)把这个资源卸载掉,那这样的好处在哪里呢?这样的好处就在我们可以在内存中减少一部分的ab的内存。

注意点

  1. 在我上一期关于资源管理的文章上说过,我的最后一套方案对于ab的卸载还是要基于ab的依赖关系进行的。那是因为如果不做ab的依赖管理卸载,那如果ab还没卸载,但是它所依赖的ab被卸载了,那下一次再加载依赖的时候,它就没办法被自动依赖赋值上prefab上的。所以我们在使用这套方案的时候,ab是不会被ab包所依赖的
  2. 如果这个资源被ab包所依赖,那还会产生一个问题,当我们调用false卸载资源之后,下一次加载ab包的asset的时候,它所依赖的资源也被加载出来了,那这个依赖的asset就会被重新创建一个。出现内存的冗余资源。

结论

所以我们在使用这个方案的时候,最好就是为了一些头像icon这类的texture资源,然后通过我们手动维护这个asset的生命周期,这样能少一部分内存。而且icon这类资源基本上都是使用我们代码赋值的。使用这个方案的资源一定不能被ab所依赖。

[资源管理器]之前资源加载的纠错

前言

这篇文章是为了之前在做资源依赖的时候关于Asset->Assetbundle依赖加载之中错误的一个纠正

正文

在之前的文章我说了一种依赖加载的方案,就是记录用Asset资源对于它所依赖的assetbundle的依赖关系,然后通过这种的依赖关系来进行资源加载,这种方案的好处就是,当我Asset不在依赖Assetbundle的时候就可以卸载这个Assetbundle的,而不需要因为我Asset所在的assetbundle没有卸载就不去卸载依赖的Assetbundle。但是今天在和大佬在聊天的时候,他和我说了一种情况,我们选确定一个依赖关系AB1依赖AB2,然后我们先加载AB2在加载AB1然后卸载AB2再加载AB2然后去实例化AB1中的资源。是不是看起来很绕,其实很简单,就是去确定一点,在Unity中的AB包之间的依赖关系,会不会因为多次加载他们的唯一标识会修改,导致AB1中所记录到的AB2的标识没有更新,导致找不到AB2。通过测试,我发现如果第一次走这样的情况,其实AB1的资源是不会丢失的。loadAB1->instanceAB1(丢失资源)->loadAB2(资源被自动赋值上去,正常现实)。但是第二次加载了就会出现问题loadAB1->instanceAB1(丢失资源)->loadAB2(自动赋值)->DestroyAB1->unloadAB2->loadAB2->instaceAB1(丢失资源)。在这种情况下资源就不会赋值上去了。

结束

因为我在之前的测试不研究,也因为对AB的加载过程没有那么熟悉,导致得到了一个错误的结论。现在特出一篇文章作为纠错。之后我会很严谨的测试不同的情况。

【系统】Unity3D中的AssetBundle资源管理器(终)

前言

水不动了,一篇小小的资源管理器已经水了好几篇了,太让人难堪了。但是也让我知道了,沉淀没有想象的那么容易,就在以为自己思考的很全面的时候,测试下来又会冒出很多问题。

正文

问题

这次遇到的问题是在以下情况下出现的,上次我们用的异步做资源加载,但有一种情况我没有考虑到(也是我实际使用时碰到的一种情况)我们需要给一个 sprite 赋值一张图片,如果是在异步的情况下,这个资源回调还没有加载回来时,又因为刷新问题需要给这个 sprite 重新赋值一张图片,如果这时旧的图片在新的图片前面加载出来,那么最终显示的图片就是我们不想要的旧图片了。这也是异步的加载的一个缺陷,我们不能保证加载回来的顺序。

解决办法

我会在加载的时候给加载的接口一个当前需要赋值的对象的索引 id —— 这个索引 id 是唯一的。将这个 id 传到资源管理器里做一次判断,如果这个唯一索引已经对应上了一个 assetname 的话,就把之前记录下来的 assetname 所对应的 asset 卸载,然后在资源管理器里和需要加载的 asset 做一次绑定。这样的话,就能保证所加载的资源不会因为异步加载的顺序所产生一些显示错误。

更新

说明

之前在讲异步加载的时候,有一个特性我遗漏了,就是异步相当于开了一个线程池来同时加载所有的东西,这样在设备高速发展的今天,我们能高效的运用多核技术,使我们的游戏更流畅的运行。

技术更新

unity 的异步接口有个很有用的功能,就是如果你直接去获取 assetbundleCreaterequest 中的 assetbundle 属性 或者 assetbundleRequest 中的 asset 属性时,它在获取这个资源的时候,属于阻塞获取,除非 assetbundle,asset 加载回来,否则进程会一直阻塞在这里。但是我们说了,异步加载其实是线程池在跑的,所以这个阻塞不会影响到其他资源已经创建的加载。我们可以根据这个特性,来做一个同步接口,这个接口的好处就是,我们可以避免在打开 ui 界面的时候 因为资源没有加载 而实例化界面对象所产生的显示问题。这是这个特性的一种好的使用方案。

还有一种使用方案是,我们可以在界面打开的时候预加载一组 AssetbundleRequest 的对象,它是异步方法,所以当我们在界面打开的时候,它可能就已经加载了一部分了。打开的时候,我们可以直接去获取它的 asset,如果没有加载成功,它就会一直阻塞在这里,直到加载成功返回,这样做的好处是能让我们感觉界面打开的更流畅。

后语

这个资源管理器本来是为了沉淀自己的技术而做的一个技术文档,却没想到最后成了每周完成一篇blog的任务了。但是在写这篇文章的时候,我也感觉到了自己对于技术的把握还是差了一点,有些东西还是要多看、多学,很多很有用的特性都可能是自己一下子想不出来的。但是后期的文章,我将改变下方案,我会把周更变成月更,这样给自己更多的时间去对于相关的技术做一个查缺补漏。

【系统】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