zoukankan      html  css  js  c++  java
  • Unity资源Tree-资源打包

    1 基本API

    1.1 打包唯一API

    public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform)
    

    调用BuildPipeline.BuildAssetBundles,引擎将自动根据资源的assetbundleName属性(以下简称abName)批量打包,自动建立Bundle以及资源之间的依赖关系。

    1.2 收集依赖API

    AssetDatabase.GetDependencies(string path, [default bool recursive = true]);

    默认开启,true会收集依赖及其间接依赖,false只会收集直接依赖。

    1.3 打包选项API

    ForceRebuildAssetBundle
    用于强制重打所有AssetBundle文件;
    
    IgnoreTypeTreeChanges
    用于判断AssetBundle更新时,是否忽略TypeTree的变化;
    
    AppendHashToAssetBundleName
    用于将Hash值添加在AssetBundle文件名之后,开启这个选项包名变为{xxx_hash},就可以直接通过文件名来判断哪些Bundle的内容进行了更新(4.x下普遍需要通过比较二进制等方法来判断,但在某些情况下即使内容不变重新打包,Bundle的二进制也会变化)。
    

    5.x下默认会将TypeTree信息写入AssetBundle,因此在移动平台上DisableWriteTypeTree选项可优化。

    1.4 Manifest文件

    namespace UnityEngine
    {
      public class AssetBundleManifest : Object
      {
        public extern string[] GetAllAssetBundles();
        public extern string[] GetAllAssetBundlesWithVariant();
        public Hash128 GetAssetBundleHash(string assetBundleName);
        public extern string[] GetDirectDependencies(string assetBundleName);
        public extern string[] GetAllDependencies(string assetBundleName);
      }
    }

    在打包后生成的文件夹中,每个Bundle都会对应一个manifest文件,记录了Bundle的一些信息,但这类manifest只在增量式打包时才用到;同时,根目录下还会生成一个同名manifest文件及其对应的Bundle文件,通过该Bundle可以在运行时得到一个AssetbundleManifest对象,而所有的Bundle以及各自依赖的Bundle都可以通过该对象提供的接口进行获取。

    1.5 Variant参数

    在Inspector界面最下方,除了可以指定abName,在其后方还可以指定Variant。打包时,Variant会作为后缀添加在Bundle名字之后。相同abName,不同variant的Bundle中,资源必须是一一对应的,且他们在Bundle中的ID也是相同的,从而可以起到相互替换的作用。

    image

    variant可以根据不同性能设备设置不同分辨率的资源,借助Variant的特性,只需创建两个文件夹,分别放置两套不同的资源,且资源名一一对应,然后给两个文件夹设置相同的abName和不同的variant。在加载该abName的时候指定对应的Variant即可。

    1.6 包体结构

    image

    image

    Unity官网也给出的详细说明,上面普通包体结构文件分为两个部分:序列化信息和资源信息。下面是场景包体结构:Unity说了该图Main scene和Shared data的内容是相反的

    序列化信息包含标识符、压缩类型和内容清单。清单是一个以Objects name为键的查找表。每个条目都提供一个字节索引,用来指示该Objects在AssetBundle数据段的位置。在加载AssetBundle时会优先加载序列化信息。

    资源信息包含通过序列化AssetBundle中的Assets而生成的二进制原始数据。如果指定LZMA压缩,则对所有序列化Assets后的完整字节数组进行压缩。如果指定了LZ4,则分段压缩每个Assets的字节。如果不使用压缩,数据段将保持为原始字节流。

    2 打包需要注意的问题

    2.1 资源冗余问题

    这里说的资源冗余是指同一份资源被打包到多个AB里,这样就造成了存在多份同样资源。在Unity运行时加载后内存中会出现多份同样的资源,造成内存开销

    资源冗余造成的问题

    1. 冗余造成内存中加载多份同样的资源占用内存。
    2. 同一份资源通过多次加载,IO性能消耗。
    3. 导致包体过大。

    解决冗余方案是依赖打包

    依赖打包是指资源间虽有依赖关系,但该资源被多个assetbundle重复依赖,在打包时要将被重复依赖的资源单独打成assetbundle,这样就形成了AB与AB之间的依赖。Unity4.x版本提供的API是:

    BuildPipeline.PushAssetDependencies
    BuildPipeline.PopAssetDependencies

    Unity5.x及以后提供的是指定AssetBundle Name的形式以及增量打包,Note:关于增量打包,Unity维护了一张全局.manifest文件,它能让我们知道更新了哪些AB,但是并没有告诉更新的AB中用到了哪些Assets,所以用到哪些Assets依然需要自己解决,因为只有知道了一个AB存储了哪些Asset信息,我们才能在加载AB的时候对特定的Asset做缓存、释放等操作。毕竟只有在知道包名的前提下才能加载所需的Asset出现这种问题,一般都是人为的拖拽了某个Asset到了新目录、或者删除某些Asset但是代码却没有更新。这都是隐藏的问题需要注意。针对这个问题也有解决办法,见下文。

    第二个就是增量打包也是热更新涉及的范畴。

    2.2 打包策略问题

    虽然可以对AssetBundle打包策略自由的进行规划,但是在进行项目的资源管理的时候,Unity官网提供了一些建议可以:

    2.2.1 依据逻辑实体进行分组

      这种资源分类方式是依据资源的功能进行分类,例如 UI/角色/场景/code等具体各自功能规划的部分来进行资源分组,可以将所有的textures都打入UI相关分类中,可以将所有的模型和动画都打入角色相关的资源中,将场景相关的贴图和模型都打入场景资源中。

      采用逻辑实体分组,对于资源的下载更新更为有利,由于资源的分类,可以在进行资源更新的时候,只更新对应的资源,而无需更新冗余的其他资源。

      使用这种分类方式最合适的策略,就是将资源进行详细的分类

    2.2.2 类型分组

      主要依据资源的类型来进行分组,这样对于不同的应用平台都具有一定的适用性。比如对于audio文件的压缩设置,在mac和windows上都是一致的,那么可以将audio文件都归类为一类文件,实现文件资源的复用(不同平台的打包设置),对于shaders而言,对于不同平台需要不同的编译设置,那么就需要分类处理。这类分类方法,对于在不同的版本中变动频率较低的代码文件和prefabs显得更有优势。

    2.2.3 相互关联的内容分组

      这种策略的,就是将需要同时进行加载的资源都归类为一个分组,例如将不同场景中的角色都依据场景来进行分组,这就要求单独一个场景中的资源只能用于该场景,各个分组之间没有互相关联的关系。这种分类方式,对于资源的加载时间有较大的缩减,这种分类方式的使用场合主要在场景资源中,在不同的场景资源中,其包含的资源各自互相不关联。

    在一个项目中,可以将上述的几种策略都交互使用,对应具体的应用需求来灵活的采用分组策略,当然unity也提供了一些资源分组的tips:

    • 分离高频和低频更新的资源;
    • 将需要同时下载的资源合并进一个组,例如Model以及其关联的animations;
    • 如果出现多个bundle中的多个object都依赖于另一个完全不同的bundle,那么将这些依赖关系都移动到一个单独的bundle,这样可以降低依赖关系的复杂度;多个bundle均依赖于另一个bundle中的资源,那么将这些bundle以及其依赖的资源归类到一个资源,这样可以降低资源的重复率(避免一份资源被拷贝到多个不同的bundle中);
    • 不可能同时加载的资源,需要归类的各自的assetbundle中,例如标准和高配的资源;
    • 如果一个assetbundle中资源在加载的时候低于50%需要被加载,那么可以考虑将这些需要被加载的资源单独分类为一个资源(避免冗余的加载);
    • 如果一组Objects对应的是一个资源的不同版本,那么可以考虑assetbundle variants

    上面三点也仅是给出了一个参考方向,具体项目需要具体分析。

    2.3 AssetBundle压缩格式问题

    AB压缩不压缩问题,主要考虑的点如下:

    1. 加载时间
    2. 加载速度
    3. 资源包体大小
    4. 打包时间
    5. 下载AB时间

    到底要不要压缩,采用LZMA还是LZ4,也是需要具体项目具体分析。LZMA适合从服务器下载。

    3 实施打包

    3.1 给定入口目录,指定根资产

    入口目录和根资产决定一个包体。为什么要指定根资产?这涉及到AB包的命名,而根资产也是包体内的mainAsset。下面给出一个自定义的数据结构

    public class AssetInfo : System.IComparable<AssetTarget>
    {
            // 目标Object
            public Object asset;
            // 文件路径
            public FileInfo file;
            // 相对目录
            public string assetPath;
            // 是否是内置的
            public bool isBuiltin = false;
            // 资产类型
            public AssetBundleBuildType buildType = AssetBundleBuildType.Asset;
            // 保存地址
            public string bundleSavePath;
            // BundleName
            public string bundleName;
            // 短名
            public string bundleShortName;
            // 目标文件是否已改变
            private bool _isFileChanged = false;
            // 是否已分析过依赖
            private bool _isAnalyzed = false;
            // 依赖树是否改变(用于增量打包)
            private bool _isDepTreeChanged = false;
            // 上次打包的信息(用于增量打包)
            private AssetCacheInfo _cacheInfo;
            // .meta 文件的Hash
            private string _metaHash;
            // 上次打好的AB的CRC值(用于增量打包)
            private string _bundleCrc;
            // 是否是第一次打包
            private bool _isNewBuild;
            // 我依赖的项
            private HashSet<AssetTarget> _imDependSet = new HashSet<AssetTarget>();
            // 依赖我的项
            private HashSet<AssetTarget> _dependMeSet = new HashSet<AssetTarget>();
    }

    既然有了根资产(Root),那就能确定该Root的所有依赖资产(普通资产DepAsset),进而就能确定哪些资产能组成一个包。这就涉及到了依赖分析,见下。

    3.2 依赖分析

    上面提到了根资产Root、普通资产DepAsset能组成一个包,没错。但是由于冗余问题,DepAsset可能会被多个Root、或多个DepAsset依赖,也就是会被多个AB包依赖,那么该资产就要单独打包。伪代码

    void Analyze()
    {
      第一步
      //先获取该asset的所有依赖,它可能是root、也可能是普通depAsset
      Object[] deps = EditorUtility.CollectDependencies(new Object[] { asset });
      //然后过滤一次脚本、光照内置资源,这里也可以过滤shader资源
      list<Object> realDeps;
      for(i < deps.count)
      {
         Object o = deps[i];
         if(o is 脚本 || o is 光照 || /*o is shader*/) continue;
         //过滤内置资源 Resources/builtin 与 Library/builtin
         path = getAssetPath(o);
         if(path.StartWith("Resources" || "Library"))  continue;
         realDeps.add(o);
      }
      第二步
      //拿到所有依赖项之后,再分析依赖项的依赖
      for(i < realDeps.count)
      {
        //把object转换为assetInfo
        depAsset = Load(realDep[i]);
        //双向依赖添加
        //添加asset的"我依赖的项",这里面不能自己依赖自己
        asset._imDependSet.add(depAsset);
        //添加depAsset的"依赖我的项"
        depAsset._dependMeSet.add(Asset);
        //分析depAsset依赖。 就这样循环下去,分析出所有
        depAsset.Analyze();
      }
    }

    上面分析出了以根资产为入口的依赖,及其依赖的依赖。 接下来要分析重复依赖,伪代码

    void 合并依赖()
    {
      //该assetInfo的”依赖我的项”个数是否大于x?这里x可以配置,有些项目可能允许一定的重复打包。这里先x=1.
      //单独成包
      if _dependMeSet.count > x
      {
        //依赖我的项就只直接依赖我,而不能再间接依赖我依赖的项,就需要从它们的“我依赖的项”中删除那一部分,且双向删除
        //换句话讲,A依赖C,B依赖C,C依赖D。那么依赖C的A和B的“我依赖的项”就不需要D只要C了。A与B删除我依赖的项D,D删除依赖我的项A与B。
        //这样C单独成包,就只有包与包直接的依赖
        //取出直接依赖我的项parent 
        foreach(parent dependMeSet)
        {
          //取出我直接依赖的项
          foreach(child imDependSet)
          {
            parent._imDependSet.remove(child);
            child._depentMeSet.remove(parent);
          }
        }
      }
    }

    上面合并了依赖之后,就要识别普通depAsset是否被依赖了多次,是就要单独成包。然后执行打包。

    3.3 缓存增量打包数据

    增量打包需要记录版本号;然后是每个包的hash,以及它的依赖包。

    增量打包也涉及到资源热更新。

    if 第一次打包
      缓存所有包的hash信息,及其他必要信息
    else
      后续打包,识别hash是否更改
    
    if hash有更改
      单独记录下此包信息
    
    //收集完所有增量信息后,就可以写上传至资源服务器等相关逻辑

    3.4 缓存所有依赖信息

    缓存AB包信息,缓存的依赖信息在加载时有用

    //asset path
    //bundle name
    //rootAsset name
    //hash
    //依赖项个数
    //依赖项
    最后再删除未使用的AB,可能是上次打包出来的,而这一次没生成的。
  • 相关阅读:
    [LeetCode]2. Add Two Numbers链表相加
    Integration between Dynamics 365 and Dynamics 365 Finance and Operation
    向视图列添加自定义图标和提示信息 -- PowerApps / Dynamics365
    Update the Power Apps portals solution
    Migrate portal configuration
    Use variable to setup related components visible
    Loyalty management on Retail of Dynamic 365
    Modern Fluent UI controls in Power Apps
    Change screen size and orientation of a canvas app in Power App
    Communication Plan for Power Platform
  • 原文地址:https://www.cnblogs.com/baolong-chen/p/13407454.html
Copyright © 2011-2022 走看看