jjzjj

Unity中AB包详解(超详细,特性,打包,加载,管理器)

窗外听轩雨 2023-04-13 原文

Unity中的AssetBundle详解

AssetBundle的概念

AssetBundle又称AB包,是Unity提供的一种用于存储资源的资源压缩包。

Unity中的AssetBundle系统是对资源管理的一种扩展,通过将资源分布在不同的AB包中可以最大程度地减少运行时的内存压力,可以动态地加载和卸载AB包,继而有选择地加载内容。

AssetBundle的优势

  1. AB包存储位置自定义,继而可放入可读可写的路径下便于实现热更新

  2. AB包自定义压缩方式,可以选择不压缩或选择LZMA和LZ4等压缩方式,减小包的大小,更快的进行网络传输。

  3. 资源可分布在不同的AB包中,最大程度减少运行时的内存压力, 可做到即用即加载,有选择的加载需要的内容

  4. AB包支持后期进行动态更新,显著减小初始安装包的大小,非核心资源以AB包形式上传服务器,后期运行时动态加载,提高用户体验。

AssetBundle和Resources的比较

AssetBundleResources
资源可分布在多个包中所有资源打包成一个大包
存储位置自定义灵活必须存放在Resources目录下
压缩方式灵活(LZMA,LZ4)资源全部会压缩成二进制
支持后期进行动态更新打包后资源只读无法动态更改

AssetBundle的特性

  1. AB包可以存储绝大部分Unity资源但无法直接存储C#脚本,所以代码的热更新需要使用Lua或者存储编译后的DLL文件。

  2. AB包不能重复进行加载,当AB包已经加载进内存后必须卸载后才能重新加载。

  3. 多个资源分布在不同的AB包可能会出现一个预制体的贴图等部分资源不在同一个包下,直接加载会出现部分资源丢失的情况,即AB包之间是存在依赖关系的,在加载当前AB包时需要一并加载其所依赖的包。

  4. 打包完成后,会自动生成一个主包(主包名称随平台不同而不同),主包的manifest下会存储有版本号、校验码(CRC)、所有其它包的相关信息(名称、依赖关系)

AssetBundle全打包流程

本次主要介绍Unity官方提供的AB包管理插件AssetBundle Browser 进行打包

1、AssetBundleBrowser插件的获取

  1. Unity 2019版本可以直接在Windows —> PackageManager里面找到此插件并直接安装

  2. 2020版本之后或其它版本可能在1方法中找不到此插件,可以通过去github搜索下载压缩包,下载地址https://github.com/Unity-Technologies/AssetBundles-Browser

将下载后的安装包解压到Unity工程的Packages文件夹下 (一定要解压)

2、AssetBundleBrowser面板的使用

  • 正确获取到并安装完插件后,通过 Windows ----> AssetBundle Browser下打开AB包管理面板 一共有三个面板

  • Configure面板 :能查看当前AB包及其内部资源的基本情况(大小,资源,依赖情况等)

  • Build面板:负责AssetBundle打包的相关设置 按Build即可进行打包

    三种压缩方式

    1. NoCompression:不压缩,解压快,包较大,不建议使用。
    2. LZMA: 压缩最小,解压慢,用一个资源要解压包下所有资源。
    3. LZ4: 压缩稍大,解压快,用什么解压什么,内存占用低,更建议使用。

    一般需要进行更改的设置即为图中勾选的相关选项设置。

  • Inspect面板:主要用来查看已经打包后的AB包文件的一些详细情况(大小,资源路径等)

3、设置资源所属的AssetBundle包

在需要打包的资源的Inspector面板下方即可选择其应放在哪个AB包下,也可通过New新建AB包将资源放入,放入后再次Build打包即可将此资源打入相应的AB包中。

AssetBundle管理器

​ 利用AssetBundleBrowser可以轻松实现AB包的打包工作,更重要的是如何将AB包中的资源加载出来并使用它们,Unity已经提供了一组API能够实现AB包的加载和资源的加载。

AB包管理器的主要需求:加载指定AB包下的指定资源,基本步骤如下:

  1. 加载资源所在AB包及其的所有依赖包(根据主包的manifest信息找依赖包名称)
  2. 从AB包中加载指定的资源(根据名称,类型)
  3. 不再使用时卸载已经加载了的AB包

主要相关API

//AB包加载所需相关API
//1. 根据路径进行加载AB包 注意AB包不能重复加载
AssetBundle ab = AssetBundle.LoadFromFile(path);
//2. 加载ab包下指定名称和类型的资源
T obj = ab.LoadAsset<T>(ResourceName); //泛型加载
Object obj = ab.LoadAsset(ResourceName); //非泛型加载 后续使用时需强转类型
Object obj = ab.LoadAsset(ResourceName,Type); //参数指明资源类型 防止重名
T obj = ab.LoadAssetAsync<T>(resName); //异步泛型加载
Object obj = ab.LoadAssetAsync(resName); //异步非泛型加载
Object obj = ab.LoadAssetAsync(resName,Type); //参数指明资源类型 防止重名
//3. 卸载ab包 bool参数代表是否一并删除已经从此AB包中加载进场景的资源(一般为false)
ab.UnLoad(false); //卸载单个ab包
AssetBundle.UnloadAllAssetBundles(false); //卸载所有AB包

下面是AB包管理器的详细代码和注释,里面涉及到的单例模式内容后面会简单进行介绍。

ABManager.cs

using System;
using System.Net.Mime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Common
{
    /// <summary>
    /// AB包管理器 全局唯一 使用单例模式
    /// </summary>
    public class ABManager : MonoSingleton<ABManager>
    {
        //AB包缓存---解决AB包无法重复加载的问题 也有利于提高效率。
        private Dictionary<string, AssetBundle> abCache;

        private AssetBundle mainAB = null; //主包

        private AssetBundleManifest mainManifest = null; //主包中配置文件---用以获取依赖包

        //各个平台下的基础路径 --- 利用宏判断当前平台下的streamingAssets路径
        private string basePath { get
            {
            //使用StreamingAssets路径注意AB包打包时 勾选copy to streamingAssets
#if UNITY_EDITOR || UNITY_STANDALONE
                return Application.dataPath + "/StreamingAssets/";
#elif UNITY_IPHONE
                return Application.dataPath + "/Raw/";
#elif UNITY_ANDROID
                return Application.dataPath + "!/assets/";
#endif
            }
        }
        //各个平台下的主包名称 --- 用以加载主包获取依赖信息
        private string mainABName
        {
            get
            {
#if UNITY_EDITOR || UNITY_STANDALONE
                return "StandaloneWindows";
#elif UNITY_IPHONE
                return "IOS";
#elif UNITY_ANDROID
                return "Android";
#endif
            }
        }

        //继承了单例模式提供的初始化函数
        protected override void Init()
        {
            base.Init();
            //初始化字典
            abCache = new Dictionary<string, AssetBundle>();
        }


        //加载AB包
        private AssetBundle LoadABPackage(string abName)
        {
            AssetBundle ab;
            //加载ab包,需一并加载其依赖包。
            if (mainAB == null)
            {
                //根据各个平台下的基础路径和主包名加载主包
                mainAB = AssetBundle.LoadFromFile(basePath + mainABName);
                //获取主包下的AssetBundleManifest资源文件(存有依赖信息)
                mainManifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            }
            //根据manifest获取所有依赖包的名称 固定API
            string[] dependencies = mainManifest.GetAllDependencies(abName);
            //循环加载所有依赖包
            for (int i = 0; i < dependencies.Length; i++)
            {
                //如果不在缓存则加入
                if (!abCache.ContainsKey(dependencies[i]))
                {
                    //根据依赖包名称进行加载
                    ab = AssetBundle.LoadFromFile(basePath + dependencies[i]);
                    //注意添加进缓存 防止重复加载AB包
                    abCache.Add(dependencies[i], ab);
                }
            }
            //加载目标包 -- 同理注意缓存问题
            if (abCache.ContainsKey(abName)) return abCache[abName];
            else
            {
                ab = AssetBundle.LoadFromFile(basePath + abName);
                abCache.Add(abName, ab);
                return ab;
            }


        }
        
        
        //==================三种资源同步加载方式==================
        //提供多种调用方式 便于其它语言的调用(Lua对泛型支持不好)
        #region 同步加载的三个重载
        
        /// <summary>
        /// 同步加载资源---泛型加载 简单直观 无需显示转换
        /// </summary>
        /// <param name="abName">ab包的名称</param>
        /// <param name="resName">资源名称</param>
        public T LoadResource<T>(string abName,string resName)where T:Object
        {
            //加载目标包
            AssetBundle ab = LoadABPackage(abName);

            //返回资源
            return ab.LoadAsset<T>(resName);
        }

        
        //不指定类型 有重名情况下不建议使用 使用时需显示转换类型
        public Object LoadResource(string abName,string resName)
        {
            //加载目标包
            AssetBundle ab = LoadABPackage(abName);

            //返回资源
            return ab.LoadAsset(resName);
        }
        
        
        //利用参数传递类型,适合对泛型不支持的语言调用,使用时需强转类型
        public Object LoadResource(string abName, string resName,System.Type type)
        {
            //加载目标包
            AssetBundle ab = LoadABPackage(abName);

            //返回资源
            return ab.LoadAsset(resName,type);
        }

        #endregion
        
        
        //================三种资源异步加载方式======================

        /// <summary>
        /// 提供异步加载----注意 这里加载AB包是同步加载,只是加载资源是异步
        /// </summary>
        /// <param name="abName">ab包名称</param>
        /// <param name="resName">资源名称</param>
        public void LoadResourceAsync(string abName,string resName, System.Action<Object> finishLoadObjectHandler)
        {
            AssetBundle ab = LoadABPackage(abName);
            //开启协程 提供资源加载成功后的委托
            StartCoroutine(LoadRes(ab,resName,finishLoadObjectHandler));
        }

        
        private IEnumerator LoadRes(AssetBundle ab,string resName, System.Action<Object> finishLoadObjectHandler)
        {
            if (ab == null) yield break;
            //异步加载资源API
            AssetBundleRequest abr = ab.LoadAssetAsync(resName);
            yield return abr;
            //委托调用处理逻辑
            finishLoadObjectHandler(abr.asset);
        }
        
        
        //根据Type异步加载资源
        public void LoadResourceAsync(string abName, string resName,System.Type type, System.Action<Object> finishLoadObjectHandler)
        {
            AssetBundle ab = LoadABPackage(abName);
            StartCoroutine(LoadRes(ab, resName,type, finishLoadObjectHandler));
        }
        
       	
        private IEnumerator LoadRes(AssetBundle ab, string resName,System.Type type, System.Action<Object> finishLoadObjectHandler)
        {
            if (ab == null) yield break;
            AssetBundleRequest abr = ab.LoadAssetAsync(resName,type);
            yield return abr;
            //委托调用处理逻辑
            finishLoadObjectHandler(abr.asset);
        }

        
        //泛型加载
        public void LoadResourceAsync<T>(string abName, string resName, System.Action<Object> finishLoadObjectHandler)where T:Object
        {
            AssetBundle ab = LoadABPackage(abName);
            StartCoroutine(LoadRes<T>(ab, resName, finishLoadObjectHandler));
        }

        private IEnumerator LoadRes<T>(AssetBundle ab, string resName, System.Action<Object> finishLoadObjectHandler)where T:Object
        {
            if (ab == null) yield break;
            AssetBundleRequest abr = ab.LoadAssetAsync<T>(resName);
            yield return abr;
            //委托调用处理逻辑
            finishLoadObjectHandler(abr.asset as T);
        }


        //====================AB包的两种卸载方式=================
        //单个包卸载
        public void UnLoad(string abName)
        {
            if(abCache.ContainsKey(abName))
            {
                abCache[abName].Unload(false);
                //注意缓存需一并移除
                abCache.Remove(abName);
            }
        }

        //所有包卸载
        public void UnLoadAll()
        {
            AssetBundle.UnloadAllAssetBundles(false);
            //注意清空缓存
            abCache.Clear();
            mainAB = null;
            mainManifest = null;
        }
    }
}

上面管理器继承的脚本单例类如下

MonoSingleton.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Common
{
    ///<summary>
    ///脚本单例类,负责为唯一脚本创建实例
    ///<summary>

    public class MonoSingleton<T> : MonoBehaviour where T:MonoSingleton<T> //注意此约束为T必须为其本身或子类
    {
        /*
        相较于直接在需要唯一创建的脚本中创建实例,Awake初始化的过程需要解决的问题
        1.代码重复
        2.在Awake里面初始化,其它脚本在Awake中调用其可能会为Null的异常情况
         */

        //解决1:使用泛型创建实例   解决2:使用按需加载(即有其它脚本调用时在get中加载)

        private static T instance; //创建私有对象记录取值,可只赋值一次避免多次赋值

        public static T Instance
        {
            //实现按需加载
            get
            {
                //当已经赋值,则直接返回即可
                if (instance != null) return instance;

                instance = FindObjectOfType<T>();

                //为了防止脚本还未挂到物体上,找不到的异常情况,可以自行创建空物体挂上去
                if (instance == null)
                {
                    //如果创建对象,则会在创建时调用其身上脚本的Awake即调用T的Awake(T的Awake实际上是继承的父类的)
                    //所以此时无需为instance赋值,其会在Awake中赋值,自然也会初始化所以无需init()
                    /*instance = */
                    new GameObject("Singleton of "+typeof(T)).AddComponent<T>();
                }
                else instance.Init(); //保证Init只执行一次

                return instance;

            }
        }

        private void Awake()
        {
            //若无其它脚本在Awake中调用此实例,则可在Awake中自行初始化instance
            instance = this as T;
            //初始化
            Init();
        }

        //子类对成员进行初始化如果放在Awake里仍会出现Null问题所以自行制作一个init函数解决(可用可不用)
        protected virtual void Init()
        {

        }
    }

}

除了上面的脚本单例类 单例模式还有另外两种无需继承MonoBehaviour的纯C#单例模式 有兴趣的读者可自行了解 单例模式下的饿汉和懒汉模式。

有关Unity中AB包详解(超详细,特性,打包,加载,管理器)的更多相关文章

  1. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  2. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  5. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  6. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  7. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  8. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  9. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  10. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

随机推荐