zoukankan      html  css  js  c++  java
  • AssetBundleMaster_ReadMe_CN

      在开始使用之前, 建议先导入到一个空的工程里, 通过ReadMe的一步步引导使你对整个框架以及文件结构进行熟悉, 之后再考虑导入到现有工程中使用, 完整看完教程大概需要2个小时左右. 先看看文件夹结构:

     

    它在AssetBundleMaster文件夹下有几个子文件夹:

      AssetBundleMasterExample 是基本的API使用Demo, 包括资源加载, 场景加载, 以及相关的卸载操作.

      AssetBundleMasterExample2 是针对Built-In Shader 的多次编译以及其优化的测试场景.

      Editor 是编辑器脚本文件夹. 包含打包面板等.

      Plugins 是运行时脚本文件夹. 包含所有核心代码. 因为资源加载是最基础的逻辑层, 所以放在Plugins里面.

    让我们先在编辑器下运行基础API场景, 工具栏上打开AssetBundleMaster的编辑器面板:

    外观如图:

    我们在编辑器运行, 只需要设定两个变量即可, 对开发者比较友好, 其它设定在打包的时候才需要了解, 我们在后面进行说明, 现在先忽略:

      1. Editor Asset LoadMode : 编辑器下的资源读取方式. 先把它设为AssetDataBase_Editor, 直接读取资源.

      2. Build Root : 它是资源读取的根文件夹, 按照图中设置即可进行Demo场景读取, 点击[Change Build Root...]按钮, 选择AssetBundleMasterExample文件夹即可. 接下来打开初始场景 StartScene1, 它在AssetBundleMasterExample/Scenes 下面:

     

    运行后左下角有场景选择 : 

     

       这些就是所有API的Demo场景了, 选择任意一个场景可以看到测试代码与场景同名. 找到场景下同名的GameObject, 脚本就在上面, 打开查看即可.

      我们先说明一下场景加载的过程, 在我们从下拉列表中选择某个场景之后, 它会把之前加载的所有东西都卸载掉, 所以读取新场景之后每个场景中加载的资源都是全新的, 方便测试. 建议一直打开Profiler全程查看. 卸载资源会有一个等待过程, 这在你切换场景之后可以在Hierarchy的 [AssetLoadManager] 这个对象的检视面板中查看到, 比如我们切换一下场景 :

    然后就可以在[AssetLoadManager]组件面板上看到一个倒计时Slider, 它表示卸载资源的等待时间 :

      因此如果你使用Profiler 的 Memory > Detailed > Take Sample 查看资源是否被正确卸载, 在它执行之后查看.

      下面来说明各个场景以及API的使用, 我们刚才在编辑器面板设置了Build Root 这个路径, 所有资源的读取路径都是跟它的相对路径, 这些资源被称为主要资源, 是通过代码加载的资源, 比如场景等, 而放在这个文件夹之外的资源为引用资源, 比如场景中引用到的Mesh等不会直接通过代码加载的资源, 不需要将所有资源都放到Build Root文件夹下, 这里只是放在一起方便用户删除Demo资源. 之后不另外说明.

      PS : 在使用AssetDataBase_Editor 模式时, 是没有异步加载的, 所有异步加载都会变为同步加载, 因为使用的加载方式为AssetDataBase.LoadAssetAtPath的编辑器加载方式. 在资源的卸载方式上也不同, 它是靠 Resources.UnloadUnusedAssets(); 的方式卸载资源的, 而AssetBundle模式的话是通过 AssetBundle.Unload(); 和 Resources.UnloadUnusedAssets(); 的组合方式进行卸载的, 效率更高.

      会被加载到的资源文件就在AssetBundleMasterExample文件夹下, 请自行查看.

       这样编辑器下的运行设置就完成了, 对于开发人员来说并没有任何学习成本, 只需要修改Build Root为你们的工程的资源文件夹即可, 然后你可以看到编辑器下多出来几个文件夹和文件, 这些就是AssetBundleMaster的配置信息了, 只要上传到SVN等就可以同步设置给其他人了.

      下面是Demo场景以及API的说明, 最终发布AssetBundle包的过程在文章末尾.

    一. Example_LoadAsset (脚本 Example_LoadAsset.cs)

      此Demo主要演示怎样读取一般资源, 读取控制由 ResourceLoadManager 控制.

      

      

    读取API (命名空间 AssetBundleMaster.ResourceLoad): 

      ResourceLoadManager.Instance.Load<T>(string loadPath)

      ResourceLoadManager.Instance.LoadAsync<T>(string loadPath, System.Action<T> loaded)

      ResourceLoadManager.Instance.LoadAll<T>(string loadPath)

      它主要读取了Sprites 和 Textures 文件夹下的文件, 使用方法很容易理解, "Sprites/Pic1" 这个路径下的资源比较特殊, 特别说明一下:

      它有三个相同名称的文件 Pic1.jpg / Pic1.png / Pic1.txt, 这是非常特殊的情况, 因为我们当前读取资源的路径是不带后缀名的( 与Resources.Load等API统一 ), 所以这三个资源的读取路径是一样的, 而我们的泛型读取API可以通过类型限定来进行读取, 比如 ResourceLoadManager.Instance.Load<TextAsset>("Sprites/Pic1").text; 它就读取了这个路径下的第一个TextAsset类型的资源, 而ResourceLoadManager.Instance.LoadAll<Sprite>("Sprites/Pic1"); 则是返回了所有该路径下的Sprite资源数组, 如果你把"Sprites/Pic1" 改为 "Sprites", 它是一个文件夹 , 也是一样能读取出文件夹下的资源的, 逻辑上与UnityEngine.Resources 提供的API相似. 这样就解决了Android这种在StreamingAssets下无法获取某个文件夹下面的文件, 进而无法实现LoadAll方法的问题. 

      想当然的, 通过不带后缀名的路径读取资源的话, 如果有同名资源, 想要读出指定类型的资源肯定要进行一次厉遍读取, 这样就增加了无谓的资源读取开销. 而如果带后缀名的话, 在操作系统层面就保证了资源的唯一性(不能有重名文件), 因此我们强化了读取API, 添加了带后缀名的读取路径的支持, 这样在像上面的TextAsset读取 : 

      ResourceLoadManager.Instance.Load<TextAsset>("Sprites/Pic1").text;

      就可以写成 :

      ResourceLoadManager.Instance.Load<TextAsset>("Sprites/Pic1.txt").text;

      这样就没有额外的资源厉遍开销了, 在Demo中所有的资源读取都不带后缀名是为了符合用户习惯与 Resources.Load 等API统一 , 在实际使用中如果文件夹有同名资源并且只读取单个资源, 请使用带后缀名的路径, 以减少开销 ( 良好的命名以及资源分类才是最好的减少运行开销的方法 ).

      特别说明的是, AssetBundle模式下, 在异步读资源时, 用户又同时请求了同步读取的话, 异步回调和同步回调都能正确触发, 只是会在编辑器Console面板中出现错误信息, 不过不影响程序的正确性, 请看下图:

      代码在Demo中没有提供, 仅供参考.

      这是因为Unity没有提供停止异步读取AssetBundle的API, 并且在异步读取中进行同步读取就会报错, AssetBundleMaster 的Low-Level API对此进行了处理, 保证了回调的正确性, 用户不需要对此担心, 你可以在发布AssetBundle之后进行测试.

      其余API在ResourceLoadManager.cs 中可查, 看下图, 泛型和非泛型版本都有, 方便使用Lua脚本语言的用户 : 

    二. Example_LoadPrefab (脚本 Example_LoadPrefab.cs)

      此Demo演示怎样读取和实例化GameObject对象, 使用的是对象池. 由 PrefabLoadManager 进行控制. 实例化对象有同步和异步读取模式, 在异步读取逻辑中可以有多种方法, 会对性能有些微影响, 请看下图 : 

    读取API (命名空间 AssetBundleMaster.ResourceLoad): 

      PrefabLoadManager.Instance.Spawn(string loadPath, string poolName = null, bool active = true)

      Spawn是直接实例化GameObject.

      PrefabLoadManager.Instance.SpawnAsync(string loadPath, System.Action<GameObject> loaded = null, string poolName = null, bool active = true)

      SpawnAsync是异步读取资源后实例化GameObject.

      PrefabLoadManager.Instance.LoadAssetToPoolAsync(string loadPath, System.Action<GameObject, GameObjectPool> loaded = null, string poolName = null)

      LoadAssetToPoolAsync是将资源读取到对应的对象池.

      上图中第二种异步读取方法只有在资源还未读取时, 相对第一种异步方法有微小性能优势, 不过使用起来比较麻烦, 推荐直接使用 PrefabLoadManager.Instance.SpawnAsync 减少代码复杂度. 其它接口请查看PrefabLoadManager.cs, 我们强烈建议所有需要实例化的GameObject通过PrefabLoadManager 这个类进行加载以及实例化(UI, 特效, 武器等等), 这样用户不仅获得了对象池的控制, 也获得了资源的自动控制. 资源的自动控制逻辑请参看附录. 注意这些API的读取路径都可以添加文件后缀名.

    三. Example_LoadScene (脚本 Example_LoadScene.cs)

      此Demo演示怎样读取场景以及读取场景的一些特点, 注意所有被加载的场景, 都不需要添加到Build Settings里面, 在开发时可以减少这方面的工作, 在发布时如没有特殊需求不要添加任何场景, 请用户注意:

      当前编辑器运行不需要添加任何场景, 跟资源一样直接使用相对路径加载.

    读取场景使用的API (命名空间 AssetBundleMaster.ResourceLoad): 

      SceneLoadManager.Instance.LoadScene(string sceneLoadPath,

        AssetBundleMaster.AssetLoad.LoadThreadMode loadMode = AssetBundleMaster.AssetLoad.LoadThreadMode.Asynchronous,

        UnityEngine.SceneManagement.LoadSceneMode loadSceneMode = LoadSceneMode.Single,

        System.Action<int, Scene> loaded = null )

      它与资源一样通过相对路径来进行读取, 可以选择同步异步以及加载模式, Unity在读取场景时无论是同步或是异步, 都需要等待场景读取完成的回调 UnityEngine.SceneManagement.SceneManager.sceneLoaded 的触发, 所以无论同步异步, 都建议用户通过 loaded 回调进行下一步操作. 此函数的返回是一个哈希码, 它代表的就是此场景的ID, 用户可以通过这个ID对场景进行控制, 详见Demo七 Example_UnloadScene. 注意这些API的读取路径都可以添加文件后缀名.

    四. Example_SpawnDespawn (脚本 Example_SpawnDespawn.cs)

      此Demo演示受对象池控制的GameObject的实例化和归还到对象池的操作.

    主要API (命名空间 AssetBundleMaster.ResourceLoad): 

      PrefabLoadManager.Instance.Spawn(string loadPath, string poolName = null, bool active = true);

      PrefabLoadManager.Instance.Despawn(GameObject go, string poolName = null);

      我们自动加载并实例化Prefab的时候可以设定对象池的名称poolName, 并且指定对象的active, 在我们归还到对象池的时候, 也需要归还到对应名称的池中去, 遵循从哪个池中来, 回到哪个池中去的原则. 归还到池中的GameObject, active会被自动设置为false, 如果用户有特殊的需求, 可以自行修改相关代码 : AssetBundleMaster.ObjectPool.GameObjectPool.Despawn 函数.

      PS : 当poolName不正确的时候, 它会自动查询所有的pool, 直到找到正确的pool然后归还对象, 如果对象池数量非常多的时候, 用户要注意不要传错以免自动查询造成性能问题.

    五. Example_SpawnPoolAndUnload (脚本 Example_SpawnPoolAndUnload.cs)

      此Demo演示了怎样销毁对象池内的资源, 以及自动资源控制是怎样工作的.

     

    主要API (命名空间 AssetBundleMaster.ResourceLoad): 

      PrefabLoadManager.Instance.DestroyTargetInPool(string loadPath, string poolName = null, bool tryUnloadAsset = true);

      此API的功能是清除目标对象池中的目标资源, 资源是通过读取路径进行标记的, 如果tryUnloadAsset 那么PrefabLoadManager 会对引用资源进行检查, 如果没有被使用的话, 会进行资源清除.

      操作: 点击Spawn Cubes按钮之后, 场景中实例化了两个对象池, 并且每个对象池创建了一些Cube, 看Hierarchy面板中显示的, 有些Cube的主节点被故意修改了, 这里是告诉用户对象池与实例化对象的关联性与节点无关, 即使对象不在对象池节点下, 仍然是被对象池控制的 :

      点击DestroyCubesInPool:MyCubes[1] 按钮, 此时将MyCubes[1]对象池中的Cubes删除了, 可以看到对象池还在, 可是实例化的对象被删除了.

      因为Cube的资源仍然被MyCubes[2]对象池引用, 此时不触发任何删除资源的操作, 再点击DestroyCubesInPool:MyCubes[2]按钮, 此时所有实例化对象都被销毁了,  查看AssetLoadManager 的检视面板, 可以看到资源删除倒计时:

      所以PrefabLoadManager 对GameObject的资源能够实现自动清除控制. 在团队开发的时候每个人都可以命名自己的对象池, 并只需要对自己的对象池进行控制即可, 资源是否需要卸载的控制交给PrefabLoadManager.

      PS : 当前仍然使用的是AssetDataBase_Editor模式, 资源卸载仍然使用的是 Resources.UnloadUnusedAssets(); 如果在AssetBundle模式下, 由于GameObject以及场景的资源是完全自动控制模式, 所以卸载资源使用的是 AssetBundle.Unload(true); 是更精准和快速的资源卸载方式( 也有其它情况, 请参看附录 ) , 

    六. Example_UnloadAsset (脚本 Example_UnloadAsset.cs)

       此Demo演示一般资源的卸载过程. 加载过程与Demo1 Example_LoadAsset 没有太大差别, 在这里不再进行描述, 下图为卸载资源的API:

    主要API (命名空间 AssetBundleMaster.ResourceLoad): 

      ResourceLoadManager.Instance.UnloadAsset(string loadPath, System.Type systemTypeInstance, bool inherit = false);

      ResourceLoadManager.Instance.UnloadAsset<T>(string loadPath, bool inherit = false);

      卸载资源的逻辑跟 UnityEngine.Resources.UnloadAsset 不同, 我们传入的参数不是资源对象, 而是资源的读取路径以及资源的类型. 比如读取资源的时候对同一个资源读取的类型可能不同, 比如图片可以读成Sprite或是Texture2D或是Object等, 那么释放的时候也需要所有加载出来的类型都被卸载才能触发资源卸载逻辑. 而卸载的类型也是可以继承的, 比如上图中 ResourceLoadManager.Instance.UnloadAsset<Object>("Sprites/Pic1", true); 意思是卸载所有"Sprites/Pic1"路径读取出来的继承于Object类型的资源, 而 ResourceLoadManager.Instance.UnloadAsset<Sprite>("Sprites/Pic2", false); 是只删除"Sprites/Pic2"读取出来的Sprite类型的资源, 这样的功能就等于是对 UnityEngine.Resources.UnloadAsset 的限定和扩大化, 实际使用上来说要比它更准确.

      此API的主要逻辑就是通过资源的读取路径以及读取类型来唯一确定需要卸载的资源对象, 然后在底层的逻辑中减少对该资源的引用计数, 如果资源的引用计数为零的话, 就会触发资源卸载的过程, 在上图中我们将引用到图片的对象都置空了, 这样在后面的资源卸载中就能正确被卸载了. 而如果用户并没有将引用资源的对象置空, 卸载过程也会正常运行, 只不过运行之后也不会将资源卸载, 因为通过ResourceLoadManager加载的资源, 卸载是基于 Resources.UnloadUnusedAssets();调用的, 有强引用的资源将不会被卸载. 而在中间层ResourceLoadManager 中, 该资源是被弱引用对象引用的, 也就是说在这种情况下弱引用对象也是能一直存在的, 那么在下一次用户请求加载这些没有被释放的资源的时候, 返回的就是这个资源, 而不会触发资源加载的逻辑, 这就是保证资源在内存中不会重复的核心逻辑. 因此开发团队的成员都不需要对资源的控制进行额外的工作了, 只要在需要时加载, 不用时卸载即可, 因为资源不会因为错误的请求而在内存中重复, 所有人对资源的交叉控制都是非耦合的 ( 也有例外的情况, 请参看附录 ).

      PS: 注意资源卸载过程在AssetDataBase_Editor和AssetBundle模式下是不一样的, AssetDataBase_Editor是通过 Resources.UnloadUnusedAssets(); 实现的, 而AssetBundle模式下是通过自动释放逻辑决定使用 AssetBundle.Unload(true); 或是 AssetBundle.Unload(false); 与 Resources.UnloadUnusedAssets(); 的组合来释放资源的,  在AssetBundle模式下更高效且是免维护的, 用户如果在使用过程中造成卡顿的话, 你可以根据项目特点在合适的时间再进行资源卸载, 虽然没有了随时卸载资源的灵活性, 不过你仍然可以受益于框架对于资源不会重复加载的支持, 并且只需要一个人员进行相关模块的维护即可.

      每当你通过ResourceLoadManager释放一个资源, 会给AssetLoadManager的计数器增加一个计数, 如果大于 AssetLoadManager.Instance.unloadAssetCountGreater 的话就会触发Resources.UnloadUnusedAssets();操作并重置计数, 你可以修改这个数值以符合你的工程要求, 默认值是0, 即每次通过ResourceLoadManager请求卸载资源都会触发. 用户自行可修改的变量也只有两个, 很简单 :

      1. AssetLoadManager.Instance.unloadAssetCountGreater : 请求卸载多少个资源后触发卸载逻辑

      2. AssetBundleMaster.AssetLoad.AssetLoadManager.Instance.unloadPaddingTime : 触发卸载逻辑到执行卸载的等待时间, 默认为1.5秒, 因为异步加载通过回调返回资源, 一般回调函数是System.Action或是Lambda, 它们对返回的资源进行了内部引用, 理论上必须等待GC将它们回收之后才能执行资源卸载, 可是CG触发的不确定性, 我们只能添加一个等待时间, 经过测试1.5秒是一个比较正确的时间, 大部分回调函数能够被回收, 用户可以自行调整. 实际项目运行时因为经常会触发卸载逻辑, 当前无法卸载的下次触发也会被卸载, 所以不需要太在意. 

    七. Example_UnloadScene (脚本 Example_UnloadScene.cs)

      此Demo演示卸载场景的一些特性, 因为场景有同步和异步加载过程, 并且场景加载完成都是通过SceneManager.sceneLoaded 回调触发的, 所以场景的卸载过程有些复杂( 过程复杂, 可是用户调用很简单 ):

    主要API (命名空间 AssetBundleMaster.ResourceLoad, AssetBundleMaster.AssetLoad): 

      AssetLoadManager.Instance.UnloadLevelAssets(int hashCode);

      这是Low-Level API 提供的清除场景资源的API, 在AssetBundle模式下只能清除一些AssetBundle的序列化数据, 不建议使用, 在其它模式下没有什么意义, 只有在序列化数据占据大量内存导致内存问题的情况下再使用, 然而这会增加资源在内存中被重复加载的风险. 一般情况下请不要使用.

      SceneLoadManager.Instance.UnloadScene(int id, System.Action<Scene> unloaded = null);  

      场景读取API ( SceneLoadManager.Instance.LoadScene ) 返回的是一个HashCode, 它指代的就是被加载出来的场景, 所以卸载场景的API参数也是该HashCode, 用来卸载指定场景. 场景资源也是完全自动控制的, 卸载场景都应该使用这个API. 因为读取场景有很多异步过程, 所以卸载流程如下图( 如果非AssetBundle模式则没有AssetBundle加载步骤 ):

      场景读取可能的异步过程有

      1. 异步读取AssetBundle

      2. 异步读取场景

      3. 等待异步回调, 不管同步加载还是异步加载场景, 都需要等待这个回调

      所以UnloadScene函数内对其中的各个步骤都进行了封装, 即使在各个异步过程还在执行中进行场景卸载, 也能保证场景能够正确被卸载.

      这个测试功能就是对卸载的测试, 它在开始异步加载场景后经过Wait Frame 10帧之后执行场景卸载, 把这个修改为1, 2, 3...等数值可以看到在异步过程中卸载场景也是正确的.

      

      以上就是全部的资源加载, 实例化, 场景读取以及卸载逻辑, 我们如果运行在AssetDataBase_Editor模式下, 有些功能可能无法测试, 比如资源异步加载和资源重复的测试, 接下来就进行资源打包创建AssetBundle, 并在编辑器下加载AssetBundle测试, 之后再进行发布测试.

      PS : StartScene1 左下角的下拉列表里还有一个场景 Example_UnloadAssetEfficiency 是用于说明资源卸载的相关特点的, 在附录中进行说明.

    --------------------------------------- Build AssetBundles -----------------------------------------

      还是先打开编辑器界面

     我们的读取模式有5种, 而发布后的支持的模式有3种

      1. Resoueces 模式 : 这个是使用Resources文件夹下的资源的模式, 没有太多意义, 只是作为保留模式

      2. AssetBundle_StreamingAssets 模式 : 从StreamingAssets路径下读取AssetBundle.

      3. AssetBundle_PersistentDataPath 模式 : 作为可更新资源模式, 在读取资源时会先检查PersistentDataPath下是否有资源, 没有的话会到StreamingAssets路径下读取资源. 查看附录的资源更新支持.

      4. AssetBundle_EditorTest 模式: 打包的AssetBundle会被放到临时路径下, 这个就是从临时路径读取AssetBundle.

      5. AssetDataBase_Editor 模式 : 不需要打包直接进行编辑器下资源加载, 这个是开发者模式.

      接下来我们把面板上的所有设置看一遍:

      1. Editor Asset LoadMode (EnumPop) : 编辑器下加载资源方式

      2. Runtime Asset LoadMode (EnumPop) : 发布后的资源加载方式

      3. Platform Selection (EnumPop) : AssetBundle的目标平台, 点击 [Set Current Platform] 即可设置为你当前的平台

      4. Set Bundle Version (Text) : 可以设置不同版本的AssetBundle

      5. BuildAssetBundleOptions (EnumFlagPop) : 打包设置

      6. Build Update File (CheckBox) : 自动创建版本Patch文件, Patch是AssetBundleMaster.AssetLoad.LocalVersion.UpdateInfo 的Josn序列化.

      7. Clean TempBuildFolder (CheckBox) : AssetBundle被打包到临时文件夹, 临时文件夹可能含有非当前版本文件, 清除冗余文件.

      8. Built-In Shader Collection (Scroll View CheckBox) : 对使用相同內建shader的材质进行整合, 减少重复编译.

      9. Build Root (Label, Button) : 资源文件夹根目录, 所有此文件夹下的资源可以被读取.

      10. Step1 Clear Old Datas (Button) : 清除已经被设置的AssetBundleName. 可以减少冗余资源被打包.

      11. Step 2 Set AssetBundle Names (Button) : 自动设置包名, 其中包含了资源的自动处理, 处理过程参看附录.

      12. Step 3 Start Build AssetBundles (Button) : 开始打包.

      13. Custom BundleNameSettings (Scroll View) : 可以添加一些文件夹, 使文件夹下的所有文件设置为统一包名, 作为一些特殊扩展.

      14. Search Target Asset By AssetBundle Name :  通过AssetBundleName来查找资源并自动选择, Debug用.

      以上就是所有设置项. 我们先把编辑器和发布的加载选项都选为AssetBundle_StreamingAssets, 这个是最简单的发布方式. 然后点击

      Step1...

      Step2...

      Step3...

      这三步就会自动设置和打包AssetBundle了. 每一步都有提示操作.

    Step1

    Step2

    Step3

      当创建完成后会询问是否从临时文件夹复制AssetBundle到StreamingAssets文件夹下, 选择Yes即可.

      可以看到StreamingAssets下面有打包好的文件:

       

      这样就打包好了, 如果在编辑器下运行, 只需要再打开 StartScene1 场景即可, 重复上面的API测试步骤, 可以看到异步读取打印的Log信息确实是异步的了.

      

      我们在AssetDataBase_Editor模式和AssetBundle_StreamingAssets模式下读取资源的内容也不一样了, 对比一下检视面板[AssetLoadManager] : 

    AssetDataBase_Editor 没有AssetBundle相关资源

    AssetBundle_StreamingAssets模式下读取了AssetBundle

       这样就完成了打包和在编辑器下读取AssetBundle的流程了, 如果要发布到目标平台, 那么在打包面板的Platform Selection 选项中选择目标平台打包即可, 接下来是最重要的一点, 场景与Build Settings : 

      在发布时, AssetBundle模式是不需要把场景加到Build Settings中的, 因为如果场景在Build Root文件夹中, 场景会被打包成AssetBundle, 而加入到Build Settings中的场景也会被自动打包到Resouces里面, 这样场景就会被打包两次, 浪费时间和空间. 比如我们当前的工程, 如果想发布的话, 我们怎样让程序启动之后自动加载初始场景StartScene1呢?

      第一种方法是把StartScene1加到Build Settins中, 因为StartScene1很小很简单, 被两次打包也不会产生什么影响:

      第二种方法是使用初始化调用属性来读取初始场景(可以写在任何代码中), 这种方式更好, 因为规避了二次打包的风险 :

      这段代码请自行添加到任意脚本中. Build Settings里面就不需要加场景了.

      以上就是所有API以及AssetBundle创建和发布的流程了.

      现在开始说明打包功能 8. Built-In Shader Collection 的作用, 我们直接修改Build Root读取AssetBundleMasterExample2 : 

     

      只简单修改了一下版本, 打包2.0.0版本, 因为我们之前Build了1.0.0版本, 所以Exists Versions里面显示出了1.0.0版本, 这里我们试一试Update文件创建, 勾选它, 修改Build Root为AssetBundleMasterExample2, 我们看看这个资源文件夹下的文件:

      Example_ShaderCollection场景中有一些建筑, 使用了大量标准材质(Standard Shader): 

      这个场景引用了很多标准材质, 会造成一些性能问题. 我们现在要加载的初始场景为StartScene2(它自动读取Example_ShaderCollection场景), 把刚才的初始场景改一改:

      第一种方式 :

      第二种方式 :

      再次执行

      Step1...

      Step2...

      Step3...

      这次在执行完Step3时出现了新的提示信息:

      这是因为我们已经有1.0.0版本的AssetBundle包了, 如果我们是增量打包, 复制 1.0.0 的文件到 2.0.0 版本文件夹下, 再开始打包会比重新打包快很多, 不过 2.0.0 打包的是AssetBundleMasterExample2的资源而 1.0.0 打包的是AssetBundleMasterExample的资源, 完全不同. 所以功能 7. Clean TempBuildFolder (CheckBox) 的功能就体现出来了, 它能清除打包完后不属于这个版本的文件. 不管你选择哪个都相当于重新打包, 等到打包结束, 就开始Build一个app来查看这个场景的特点吧.

    AssetBundle 文件占用空间 11.2MB

    任务管理器 运行内存219.7MB

    Profiler

    Profiler : ShaderLab 62.9MB

    Profiler : Standard Shader 进行了多次编译

      问题出在Standard Shader上, 我们使用Shader Collection的方式再打一个3.0.0的版本看看, 这次把Shader Collection中的Standard选上, 因为Shader的变更不会造成引用资源的变更, 所以我们把BuildAssetBundleOption的选项加上ForeRebuildAssetBundle, 否则会造成材质丢失 :

      PS : 默认BuildAssetBundleOption选项ChunkBasedCompression是为了测试时更快速的打包, 用户可以自己进行修改. 编辑器下的配置文件在下图的文件夹内, 你可以上传至SVN等同步给其他人:

      同样重新执行

      Step1...

      Step2...

      Step3...

      然后Build出来运行查看:

    AssetBundle 文件占用空间 2.62MB vs 11.2MB

    任务管理器 运行内存 81.6MB vs 219.7MB

    Profiler

    Profiler : ShaderLab 4.8M VS 62.9MB

    Profiler : Standard Shader 只编译了一次, 复用了44次.

      你可以看到这两个版本巨大的差别. 这是由于內建Shader的多次编译造成的, 所以我们在这里提供了针对內建Shader的整合功能 ( 內建Shader的问题在Unity5, Unity2017, Unity2018, Unity2019都存在 ) .

      回头看一下AssetBundle打包的临时文件夹, 它跟Assets文件夹在同一层, 叫AssetBundles:

      它创建了版本间的Patch文件, 可以作为更新列表使用, 它是AssetBundleMaster.AssetLoad.LocalVersion.UpdateInfo的Json序列化:

      

      AssetBundle_EditorTest 模式就是从临时文件夹内读取资源的, 所以可以很方便地在已经打包好的各个版本间切换, 方便测试. 读取的平台和版本就是编辑器面板中的当前设定:

      工具栏其余功能: 

      1. 从临时文件夹复制AssetBundle到StreamingAssets文件夹 :

      

      一般在打包完成后会出现提示框提示用户是否要复制到StreamingAssets文件夹, 这个是手动复制.

      

      2. 清除AssetBundleMaster产生的数据 :

      

      它会清除一些编辑器产生的序列化文件. 没有这些文件AssetBundleMaster将无法运行.

      以上就是AssetBundleMaster的全部说明, 它就像介绍说的, 是一个自动打包以及资源读取的整合解决方案, 提供了开发者很友好的开发流程, 并实现了大部分的资源自动管理逻辑, 提供了一些资源自动处理方案以及对资源更新的友好支持. 还是那句话 : 我们建议在任何情况下都不应该人为地手动去设置AssetBundle分包, 或不设置任由其自动分包, 即使你基于游戏更新或其它方面的考虑, 大部分情况下都与引擎特性不符. Have Fun.

    附录

      资源自动处理, 编辑器代码在AssetBundleBuildWindow.cs 文件中, 其中资源处理有几个部分:

      1. SpriteAtlas, 在Unity5中无法自己创建SpriteAtlas, 在Unity2017之后可以创建, 代码在AssetBundleBuildWindow::1762行 CreateSpriteAtlasAssets 函数内, 通过把所有相同Packing Tag的Sprite资源设置到同一个AssetBundle包内, 规避了SpriteAtlas可能被重复打包以及造成额外DrawCall的问题(Unity5中同样适用). 这里使用了Packing Tag作为分包的依据, 可是在Unity2019中检视面板没有显示Packing Tag 不过它仍然存在于序列化数据之中, 因此AssetBundleMaster提供了一个重载的检视面板, 添加了Packing Tag设置, 编辑器代码为 TextureImporterInspector.cs :

    Unity5

    Unity2019

      

    AssetBundleMaster重载了Unity2019检视面板

      注意图集如果因为一些原因导致Include in Build的信息丢失(比如资源更新等), ResourceLoadManager通过对UnityEngine.U2D.SpriteAtlasManager.atlasRequested 添加了回调, 对这种情况作了补偿操作, 最大限度保证读取的正确性, 代码在 ResourceLoadManager.RequestAtlas, 用户如果碰到此问题请到这里进行调试(Unity2017以及之后的版本).

     SpriteAtlas会被自动创建到 Assets/AssetBundleMasterSpriteAtlas 文件夹下(Unity2017以及之后的版本). 

      2. EditorConfigSettings, 自动调整一些编辑器下的设置, 用户可以根据自己的工程修改此处代码. 代码在AssetBundleBuildWindow::1679行 EditorConfigSettings 函数内, 它首先修改了UnityEditor.EditorSettings.spritePackerMode以方便创建SpriteAtlas, 并修改了GraphicsSettings的相关变量, 防止打包后场景中物体LightMap信息被错误剥离. 见图 :

        

      3. TerrainData, 它使用的贴图需要设置isReadable属性, 如果不设置在编辑器下不会报错, 直到打包运行之后才会报错. 将自动把被引用到的贴图设置isReadable.

      AssetBundle模式下的自动资源释放逻辑, 我们可以通过Demo : Example_UnloadAssetEfficiency 进行说明. 用户请选择AssetBundle模式并打包, 然后打开此场景进行资源读取. 我们在读取资源的时候, 只有三个读取资源的Manager类( 都在 AssetBundleMaster.ResourceLoad 命名空间下 ) :

      1. ResourceLoadManager  非完全控制类资源, 不能通过 AssetBundle.Unload(true); 释放资源

      2. PrefabLoadManager    完全控制类资源, 通过 AssetBundle.Unload(true); 释放资源

      3. SceneLoadManager    完全控制类资源, 通过 AssetBundle.Unload(true); 释放资源

      也就是说在一般情况下, PrefabLoadManager 和 SceneLoadManager 进行读取的对象, 同样通过它们进行卸载的话, 只会调用 AssetBundle.Unload(true); 释放资源, 具有很高的效率. 而ResourceLoadManager读取的资源一般作为公共资源, 通过它卸载的资源调用的是 AssetBundle.Unload(false); + Resources.UnloadUnusedAssets(); 的方式, 在低效率的同时保证了团队开发中多个开发人员交叉控制资源的安全性, 不会因为其它人的资源卸载导致资源上的问题.

      在这些引用资源有交叉的时候, AssetBundle的释放方式也可能会被改变. 比如某个Prefab使用了图片Pic3, 被 PrefabLoadManager 加载出来后如果卸载那么Pic3的AssetBundle应该通 AssetBundle.Unload(true); 释放资源, 而在被释放之前如果通过 ResourceLoadManager 对Pic3进行显式加载, 那么Pic3就成了非完全控制资源了, 这时候它的释放就成了AssetBundle.Unload(false); 了. 相关逻辑在 AssetBundleMaster.AssetLoad.AssetBundleTarget 中, 并且自动释放受成员变量 unloadable 控制.

      我们打开场景Example_UnloadAssetEfficiency, 注意要用AssetBundle加载模式:

      点击LoadCubeButton读取GameObject, 可以看到所有AssetBundle都是完全控制的.

      点击UnloadCubeButton, 看到删除资源逻辑使用的是AssetBundle.Unload(true);

      我们这次把Cube和Pic3都显式加载出来, 点击LoadCubeButton, LoadTextureButton:

      可以看到这时textures/pic3.ab被设置为了 unloadable : False, 意思是这个Pic3已经成了非完全控制资源了, 如果我们点击UnloadCubeButton, 看到删除资源逻辑变成了:

      textures/pic3.ab这个AssetBundle被保留了, 因为它被 ResourceLoadManager 显式加载了, 并且有引用, 我们继续点击UnloadTextureButton:

      结果就是Pic3资源通过 AssetBundle.Unload(false); + Resources.UnloadUnusedAssets(); 的方式进行了卸载.

      以上就是卸载的逻辑基础了, 用户通过自己的修改以及对自己项目的整合, 可以很容易地支持大型项目.

      资源更新支持, 在上文我们已经看到每次打包之后可以创建Update文件, 并且它不管哪个版本进行了重新打包, 都会把所有版本的Update文件更新一遍, 并且它生成的是所有低版本到高版本的更新列表, 也就是说更新信息上支持任何版本到比它高的任何版本都能进行跳跃版本的更新.

      1.0.0 到 3.0.0 的更新列表表示可以直接从1.0.0 更新到3.0.0版本. 这是更新列表对资源更新的支持, 资源读取系统对资源更新的支持可以通过把读取模式选择为AssetBundle_PersistentDataPath 模式来实现, 简单来说它在读取任何资源文件的时候, 都会首先检查 Application.persistentDataPath下是否有资源文件可以读取, 如果没有文件的话, 会取 Application.streamingAssetsPath 的路径进行读取, 这样在发布时只要将最基础的版本包包含在 StreamingAssets 文件夹下, 启动时检查版本是否需要更新, 然后下载更新包到Application.persistentDataPath 文件夹下即可, 版本获取从 AssetBundleMaster.AssetLoad.LocalVersion 中获取:

      获取当前资源版本 : LocalVersion.Instance.versionInfo.BundleVerison

      它对应的是在打包时自动生成的版本信息:

      在生成AssetBundle之后是:

      那么资源更新就简单了, 首先获取本地版本信息 (比如 1.0.0), 然后远程获取最新版本号 (比如 3.0.0), 如果需要进行更新的话, 远程获取更新列表 (Update_1.0.0_to_3.0.0.txt), 更新列表是 AssetBundleMaster.AssetLoad.LocalVersion.UpdateInfo的Json序列化, 直接使用 UnityEngine.JsonUtility 就可以反序列化 通过CDN等对应版本的地址按照列表中的更新和删除操作就行了( Application.persistentDataPath ), 因为是增量打包所以可以跳版本进行更新.

      

  • 相关阅读:
    关于MapReduce中自定义分区类(四)
    关于MapReduce中自定义分组类(三)
    UiAutomator2.0
    Java_集合框架
    Python爬取指定重量的快递价格
    Java_面向对象
    Java_异常以及处理
    Java_File类
    Java_Scanner和System类
    Java_Runtime&Process&ProcessBuilder
  • 原文地址:https://www.cnblogs.com/tiancaiwrk/p/11431208.html
Copyright © 2011-2022 走看看