Unity资源管理(一)

        序:之前有接做过打包的工作,但是一直也没有整理下来,现在整理一下资源管理相关的顺便整理一下吧。

Resources:Assets目录下面新建Resources文件夹,其中的所有资源,不论是否被场景用到,都会被打包到游戏中。

资源加载方式:

1.Resources.Load:加载Resources目录的一个asset
2.Resources.LoadAsync:Resources.Load的异步方法
3.Resources.LoadAll:类似Resources.Load,但是用于加载某目录下所有asset
4.Resources.LoadAssetAtPath:加载Asset/目录下的资源,只能用于编辑器模式,写打包工具时可能用到

Resources类只能加载Resources文件夹下的资源,若出现嵌套,都会加载建议在Assets下放一个Resource文件夹就好;Resources加载资源时应使用相对路径,且不包含扩展名。如 Resources.Load<Texture2D>("images/texture1"); 

卸载资源:

1.Resources.UnloadAsset(Object assetToUnload):卸载指定的asset,只能用于从磁盘加载的;如果场景中有此asset的引用,Unity会自动重新加载它,CPU开销小。

2.Resources.UnloadUnusedAssets:卸载所有未被引用的asset,可以在画面切换时调用,或定时调用释放全局未使用资源。被脚本的静态变量引用的资源不会被卸载。尽量避免在游戏进行中调用,因为该接口开销较大,容易引起卡顿,可尝试用Resources.Unload(obj)逐个卸载,保证游戏的流畅度。

AssetBundle:

上图流程:

       1.Unity 在使用 WWW 方法时会分配一系列的内存空间来存放 WWW 实例对象、 WebStream 数据。该数据包括原始的 AssetBundle 数据、解压后的 AssetBundle 数据以及一个用于解压的 Decompression Buffer 。(一般情况下, Decompression Buffer 会在原始的 AssetBundle 解压完成后自动销毁,但需要注意的是, Unity 会自动保留一个 Decompression Buffer ,不被系统回收,这样做的好处是不用过于频繁的开辟和销毁解压 Buffer ,从而在一定程度上降低 CPU 的消耗。)

       2.当把AssetBundle 解压到内存后,可以使用WWW .assetBundle属性来获取AssetBundle 对象,从而可以得到各种Asset,进而对这些Assets进行加载或者实例化操作。加载过程中,Unity 会将AssetBundle 中的数据流转变为引擎可以识别的信息类型(纹理、材质、对象等)。加载完成后,开发者可以对其进行进一步的操作,比如对象的实例化、纹理和材质的复制和替换等。

更新:

       游戏一开始运行时,通过文件里面记录的版本号,和服务器上文件中的版本号比对。如果本地版本号低,下载对应的AB包到可读写目录,并对本地资源进行替换,这样进入游戏中加载的就是新下载的AB包资源。

更新流程如下:

       1.  将更新包资源(安装包中的资源)复制到可读写目录下
       2.  复制完成开始比对哈希文件,开始更新资源
       3.  下载添加资源,替换旧资源,删除原来可读写目录下的无用资源
       4.  初始化assetbundle依赖关系
       5.  完成整个流程

更新注意:

       1.  要有下载失败重试几次机制;
       2.  要进行超时检测;
       3.  要记录更新日志,例如哪几个资源时整个更新流程失败。

资源加载:

       通过AssetBundle加载资源:首先需要获取AssetBundle对象,然后通过该对象加载需要的资源。

       获取AssetBundle对象分为两种方式,可以通过下面两种方式:

       一、先获得WWW对象,通过WWW.assetBundle获取AssetBundle对象

             1.  public WWW(string url);

             加载Bundle文件并获取WWW对象,每次加载完成后会在内存中创建较大的WebStream(解压后的内容通常为原Bundle       文件的4~5倍大小,纹理资源比例可能更大),因此后续的AssetBundle.Load可以直接在内存中进行。

             2.  public static WWW LoadFromCacheOrDownload(string url, int version, uint crc = 0);
            从缓存或网络上下载后加载Bundle并获取WWW对象,同时将解压形式的Bundle内容存入磁盘中作为缓存(如果该       Bundle已在缓存中,则省去这一步)。完成后只会在内存中创建较小的SerializedFile,而后续的AssetBundle.Load需要通过IO       从磁盘中的缓存获取。

              通过上面这两个接口获取WWW对象后,即可通过WWW.assetBundle获取AssetBundle对象。

               总结:第一种WWW方式,后续的Load都在内存中进行,相比第二种方式IO操作开销小;第一种不会形成缓存文件,而第二种需要额外的磁盘空间存放缓存;第一种能通过WWW.texture,WWW.bytes,WWW.audioClip等接口直接加载外部资源,而后者只能用于加载AssetBundle。但是,第一种每次加载都涉及到解压操作,而后者在第二次加载时就省去了解压的开销;第一种在内存中会有较大的WebStream,而后者在内存中只有通常较小的SerializedFile。(此项为一般情况,但并不绝对,对于序列化信息较多的Prefab,很可能出现SerializedFile比WebStream更大的情况)

     二、直接获取AssetBundle:

              1. public static AssetBundle CreateFromFile(string path);

              从磁盘加载一个AssetBundle,相比其他方式速度最快,但是只能加载uncompressed的AssetBundle。Unity 5.x改为              LoadFromFile,而且可以加载compressed的;

               2.  public static AssetBundleCreateRequest CreateFromMemory(byte[] binary);通过Bundle的二进制数据,异步创建            AssetBundle对象。完成后会在内存中创建较大的WebStream。调用时,Bundle的解压是异步进行的,因此对于未压缩的           Bundle文件,该接口与CreateFromMemoryImmediate等价。

               3.  public static AssetBundle AssetBundle.CreateFromMemoryImmediate(byte[] binary):                                                     AssetBundle.CreateFromMemory的同步版本。

                Unity5.3下分别改名为LoadFromFile,LoadFromMemory,LoadFromMemoryAsync并增加了LoadFromFileAsync,且机制也有一定的变化
      获得AssetBundle对象后可以通过下面API来从中加载资源

      1.  public Object LoadAsset(string name, Type type);

      通过给定的名字和资源类型,加载资源。加载时会自动加载其依赖的资源,即Load一个Prefab时,会自动Load其引用的Texture资源。

       2.  public Object[] LoadAllAssets(Type type);

       一次性加载Bundle中给定资源类型的所有资源。

       3.  public AssetBundleRequest LoadAssetAsync(string name, Type type);

       该接口是Load的异步版本。

资源卸载:

       1.  WWW对象:调用对象的Dispose函数或将其置为null;

       2.  WebStream:在卸载WWW对象以及对应的AssetBundle对象后,这部分内存即会被引擎自动卸载;

       3.  SerializedFile:卸载AssetBundle后,这部分内存会被引擎自动卸载;

       4.  GameObject :可通过Object.Destory()卸载;

       5.  Prefab:只能通过DestroyImmediate()来卸载,卸载后必须重新加载AssetBundle才能重新加载该Prefab。由于内存开销较小,通常不建议进行针对性地卸载;

       6.  普通资源(除Prefab):除了Resources类提供的卸载接口外,AssetBundle.Unload(false)在卸载AssetBundle对象时,将加载出来的资源一起卸载;

       7.  AssetBundle:

              1>AssetBundle.Unload(false):卸载AssetBundle对象时保留内存中已加载的资源,在卸载AssetBundle对象后,如果          重新创建该对象,并加载之前加载过的资源到内存时,会出现冗余,即两份相同的资源。

               2> AssetBundle.Unload(true):卸载AssetBundle对象时卸载内存中已加载的资源,由于该方法容易引起资源引用丢             失,因此并不建议经常使用。

压缩格式:

        1.  LZMA格式

        在默认情况下,打包生成的AssetBundle都会被压缩。在U3D中AssetBundle的标准压缩格式便是LZMA(流式序列化文件)。因此在默认情况下,打出的AssetBundle包处于LZMA格式的压缩状态,在使用AssetBundle前需要先解压缩。使用LZMA格式压缩的AssetBundle的包体积最小(高压缩比),但是相应的会增加解压缩时的时间。

        2.  LZ4格式

        Unity 5.3之后的版本增加了LZ4格式压缩,由于LZ4的压缩比一般,因此经过压缩后的AssetBundle包体的体积较大(该算法基于chunk)。但是,使用LZ4格式的好处在于解压缩的时间相对要短。若要使用LZ4格式压缩,只需要在打包的时候开启BuildAssetBundleOptions.ChunkBasedCompression即可。

         3.  不压缩

         我们也可以不对AssetBundle进行压缩。没有经过压缩的包体积最大,但是访问速度最快。若要使用不压缩的策略,只需要在打包的时候开启BuildAssetBundleOptions.UncompressedAssetBundle即可。

打包:

        Unity5.x打包:在资源的Inpector界面最下方可设置一个abName,每个abName(包含路径)对应一个Bundle,即abName相同的资源会打在一个Bundle中。如果所依赖的资源设置了不同的abName,则会与之建立依赖关系,避免出现冗余。

        接口:public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions      assetBundleOptions = BuildAssetBundleOptions.None,BuildTarget targetPlatform = BuildTarget.WebPlayer);

打包颗粒度:

       AssetBundle粒度过大,会影响打包、上传、下载的效率,并且浪费用户流量,同时在runtime时候IO这个Assetbundle会非常吃力,导致卡顿严重。

        Assetbundle粒度过小,整个工程有数百上千个Assetbundle,维护上会产生极大的不便,另外Runtime实例化一个角色时也会发生多次IO,这严重影响游戏性能。在Unity 5.3 ~ 5.5 版本中,Android平台上在不Unload的情况下,每个AssetBundle的加载,其每个文件的SerializedFile内存占用均为512KB(远高于其他平台),所以当内存中贮存了大量AssetBundle时,其SerializedFile的内存占用将会非常巨大同时。不过该问题已在Unity5.6中进行完善。

       划分Assetbundle的粒度主要考虑4个方面:资源类型、冗余大小、程序性能、后期维护。在最终资源粒度划分成型前,大概经历了3个阶段,每个阶段所考虑的要素不同。

一、探索阶段

       在热更新模块设计初期就划分出合适的粒度是比较困难的,在这个阶段主要的目的是模块的实现,只需按照类型进行简单划分即可。资源的类型有:音乐(音效)、配置文件、特效、Item、NPC、Monster、Role、UI、Scene、脚本(Lua)。

       打包的策略可以参考如下几项:

       1.  通常情况下,1M左右的AssetBundle包加载性能最好,冗余也可以接受,但是在Unity 5.3版本之后,对于AB文件的文件大小其实不必再限定于1MB之内。使用LZ4压缩,基于其Chunk的加载特点,AB加载很快,且内存占用要比之前小很多。所以LZ4的AB其实可以考虑更加粗粒度一些。
       2.  根据依赖树进行的最优打包策略,公共资源单独打ab,独立资源打到一起。
       3.  shader字体等其他细碎并且需要常驻内存的资源打包到一起,启动游戏的时候常驻内存。
       4.  根据项目实际需求将需要经常热更新的资源进行单独打包。

二、优化阶段

        以常见的MMORPG项目来说,这个阶段热更新模块已经通过了内部测试。但是在实际使用过程中,会发现部分Assetbundle过大,加载时间较长,出现了明显的卡顿,这时可以考虑拆分Monster、Role、Scene这3个焦点Assetbundle。

        1.  Monster的拆分标准是骨骼重定向,unity支持多个模型共用一套骨骼,从而共享一套模型动画。使用这个机制会节省大量资源,按照这个原则,我们把所有共享骨骼的模型放在一个Assetbundle里,这样既减小了IO的压力,又使冗余做到了最小。
        2.  Role的拆分同样遵循上述规则,同时由于role的武器是单独的模型(支持换装),也就是一个类型的Role对应两个AssetBundle,Role本身+武器。
        3.  Scene的拆分比较简单,之前是所有的Scene一个Assetbundle,现在改为一种类型scene一个Assetbundle。

三、测试阶段

       此阶段主要对项目进行加载速度、IO占用、资源冗余来做整体测试,发现局部问题并针对性解决。

内存占用

1.  对于需要常驻内存的Bundle文件来说,

       优先考虑减小内存占用,因此对于存放非Prefab资源(特别是纹理)的Bundle文件,可以考虑使用WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile加载,从而避免WebStream常驻内存;而对于存放较多Prefab资源的Bundle,则考虑使用new WWW加载,因为这类Bundle用WWW.LoadFromCacheOrDownload加载时产生的SerializedFile可能会比new WWW产生的WebStream更大。

2.  对于加载完后即卸载的Bundle文件

       分两种情况:优先考虑速度(加载场景时)和优先考虑流畅度(游戏进行时)。

       1)加载场景的情况下,需要注意的是避免WWW对象的逐个加载导致的CPU空闲,可以考虑使用加载速度较快的WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile,但需要避免后续大量地进行Load资源的操作,引起IO开销(可以尝试直接LoadAll)。

       2) 游戏进行的情况下,则需要避免使用同步操作引起卡顿,因此可以考虑使用new WWW配合AssetBundle.LoadAsync来进行平滑的资源加载,但需要注意的是,对于Shader、较大的Texture等资源,其初始化操作通常很耗时,容易引起卡顿,因此建议将这类资源在加载场景时进行预加载。

3.  只在Bundle需要加密的情况下,考虑使用CreateFromMemory,因为该接口加载速度较慢。

资源冗余

       Unity 5.x版本里会自动收集并分析其依赖的资源,如果该资源依赖的某个资源没有被显式指定打包到ab中,就将其依赖的这个资源打包进该资源所在的ab里。如果已经被指定打包进其他ab里,那么这两个ab之间就会构成依赖关系,加载ab时,先加载其依赖的ab即可。

       虽然这种依赖管理机制使用方便,但是会引起资源冗余问题:如果两个ab包A和B,其中的一些资源都依赖了一个没有被指定要打包的资源C,那么C就会同时被打进ab A和B中,增大ab和安装包的体积。而这个被A,B依赖的资源C又可以分为两种类型,一种是Assets下外部导入的资源,即开发者导入或创建的资源;另一种则是Unity内置的资源,例如内置的Shader,Default-Material和UGUI一些组件如Image用的一些纹理资源等等。下面分析这两种情况处理:

       一、被依赖的外部资源

       将那些被多个ab包依赖的资源打包到一个公共ab包中。处理过程如下:

       1.  使用EditorUtility.CollectDependencies()得到ab依赖的所有资源的路径,其中会收集到不需打包进ab中的脚本、dll、编辑器资源,需要手动剔除;
       2.  统计资源被所有ab引用的次数,将被多个ab引用的资源打包为公共ab包。

       二、Unity内置资源

            可以通过提取出Unity内置的资源,在打ab前进行预处理,修改引用为提取出的资源。

            使用AssetDataBase.LoadAllAssetsAtPath() 可以加载出Resources/unity_builtin_extra下所有的Object,可以发现共有4种      类型的资源:Shader,Material,Texture以及Sprite。对于内置Shader,可以直接从Unity官方网站下载;而对于后三种,可        以通过AssetDataBase提供的相关API来进行创建:
                Object[] UnityAssets = AssetDatabase.LoadAllAssetsAtPath("Resources/unity_builtin_extra");
                foreach (var asset in UnityAssets)
                {
                     // create asset...
                }

................................................

参考:https://blog.uwa4d.com/archives/ABTheory.html

            https://blog.uwa4d.com/archives/ABTheory.html

猜你喜欢

转载自blog.csdn.net/jfy307596479/article/details/84975736