Unity-AssetBundle

unity的资源管理

在Unity中,一般来说,资源加载方式主要分为Resources加载和AssetBundle加载。

Resources是Unity的一个特殊文件夹,放在这个文件夹下的资源可以通过Resources.Load()来直接加载。即Resources加载资源方式。

AssetBundle是一种Unity提供的用于存放资源的包。通过将资源分布在不同的AB包中可以最大程度地减少运行时的内存压力,并且可以有选择地加载内容。

参考链接

AssetBundle

AB包

官方文档

AB包的定义

  • AssetBundle(简称AB包)是一个资源压缩包,把一些资源文件,场景文件或二进制文件以某种紧密的方式保存在一起的,独立于游戏主包存在的资源存储文件,使用内部资源时,需要单独下载和加载;可以在游戏运行的时候被加载。
  • AssetBundle是Unity提供的一种资源更新技术,就是通过AssetBundle更新资源,可以将资源打包成AssetBundle然后更新到客户端。
  • AssetBundle自身保存着互相的依赖关系。
  • 压缩包可以使用LZMA和LZ4压缩算法,减少包大小,更快的进行网络传输。
  • 把一些可以下载内容放在AssetBundle里面,可以减少安装包的大小。
  • AssetBundle内部不能包含C#脚本文件,AssetBundle可以配合Lua实现资源和游戏逻辑代码的更新

AB包的打包与加载

打包

  • 将需要导入AB包的资源进行导入,不要将导入AB包的资源放置在Resources目录等特殊目录下,因为存储在Resources下的资源,最终会存储在游戏的主体包中,发送给用户,手机系统上,如果需要做资源的更新,是无法使用Resources即时更新。

  • 资源配置:

    alt

选中需要添加AB包的资源,填写或选择已存在的AB包名称

AB包名称如果配置为这样的结构”ui/package”,ui会作为AB包存储的父目录,package是AB包的名称

AB包配置修改后或AB内部的资源修改后,都需要重新生成AB包

打包:相关方法:Buildpipeline.BuildAssetBundles(ab包文件存储路径,导出选项,导出平台(不同平台的ab包是不一样的))

例:

public class ExportAB
{
    [MenuItem("工具/导出AB包")]
    private static void Export()
    {
        string tempPath = Application.dataPath;
        string AbPath = tempPath.Substring(0, tempPath.Length - 6) + "Ab";
        //判断是否存在路径
        if (!Directory.Exists(AbPath))//如果不存在路径
        {
            Directory.CreateDirectory(AbPath);//创建路径
        }
        #region 导出ab包核心代码
        // BuildPipeline:通过编程方式构建AB包。
        //参数一:ab包文件存储路径
        //参数二:导出选项
        //参数三:导出平台(不同平台的ab包是不一样的)
        //制作多平台导出ab包
#if UNITY_EDITOR_WIN||UNITY_STANDALONE_WIN
        BuildPipeline.BuildAssetBundles(AbPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
#elif UNITY_ANDROID
        BuildPipeline.BuildAssetBundles(AbPath, BuildAssetBundleOptions.None, BuildTarget.Android);
#elif UNITY_IPHONE//苹果机
        BuildPipeline.BuildAssetBundles(AbPath, BuildAssetBundleOptions.None, BuildTarget.iOS);
#endif
        #endregion
        Debug.Log("AB包打包成功!");
    }
}

导出选项:

  1. None:无任何选项,默认压缩模式为LZMA
  2. UncompressedAssetBundle:开启这个选项就不会压缩AB包
  3. CollectDependencies:默认开启,开启依赖记录机制
  4. DeterministicAssetBundle:将AssetBundle的哈希校验值,存储在ID中(默认开启)
  5. ForceRebuildAssetBundle:强制重新导出所有的AB包
  6. ChunkBasedCompression:使用LZ4算法压缩AB包

压缩方式

  • 不压缩:AB包比较大,下载较慢,加载速度快(CPU不用运算解压缩)
  • LZMA算法压缩:默认压缩模式,流压缩方式(stream-based),文件尺寸居中,加载速度居中;使用LZMA算法压缩,压缩的包更小,但是加载时间更长,只支持顺序读取。使用之前需要整体解压。一旦被解压,这个包会使用LZ4重新压缩。使用资源的时候不需要整体解压。在下载的时候可以使用LZMA算法,一旦它被下载了之后,它会使用LZ4算法保存到本地上。
  • LZ4算法压缩:5.3以后可用,块压缩方式(chunk-based)压缩比例高,文件小,加载速度偏慢;使用LZ4压缩,压缩率没有LZMA高,但是我们可以加载指定资源而不用解压全部,可以实现实时解压随机存储。

alt

注意:使用LZ4压缩,可以获得跟不压缩想媲美的加载速度,而且比不压缩文件要小。

导出后的文件结构:

alt

和存储目录同名的文件和文件.manifest,是主AB包及主AB包配置文件(存储所有AB包的配置信息)

无扩展名文件:AB包 文件名.manifest文件:AB包对应的配置文件

加载

依赖关系

在Unity5.0后,BuildAssetBundleOptions.CollectDependencies永久开启,即Unity会自动检测物体引用的资源并且一并打包,防止资源丢失遗漏的问题出现。

如果一个AB(名称为ui)包,使用到了另一个AB(名称为big)包的资源,那么两个AB包就产生了依赖关系。也就是ui依赖于big。如果ui加载了,big没有加载,就会导致ui中的资源出现资源缺少的问题。

AB包的依赖关系存储在各AB包所对应的配置文件中(在主AB包的配置文件中存储着所有AB包的依赖关系) alt

AB包不能重复加载(需要对AB包进行卸载操作“Unload()”);当AB包使用完后需要卸载AB包;否则会妨碍其它AB包加载 报错如下:

alt

如果想处理依赖关系的加载,则必须加载主AB包(与目录名相同),因为依赖关系的存储,都存储在主AB包的配置文件中

  • 第一步(加载被依赖的AB包文件): 当所需AB包文件有被依赖的AB包时
    1. 加载主AB包
      AB包 = AssetBundle.LoadFromFile(AB包文件路径)
    2. 根据主AB包的配置文件,加载配置文件:
      AssetBundleManiifest 配置文件类 = LoadAsset("AssetBundleManiifest");
    3. 获得当前需要加载的AB包所依赖的AB包:
      string[] dependencies = mainManifest.GetAllDependencies("需要加载的AB包名称");
      //dependencies中存储的为所依赖的AB包的文件名
    4. 将所有的被依赖的AB包,加载进来(为加载的AB包提供依赖的资源)
  • 第二步(加载所需AB包文件)
    • AB包 = AssetBundle.LoadFromFile(AB包文件路径)
    • AssetBundle.LoadFromFileSync(AB包文件路径) //异步加载
  • 第三步(加载所需AB包内部资源)
    • 资源对象 = AB包对象.LoadAsset<资源类型>(“资源名称”)
    • AB包对象.LoadAssetSync<资源类型>(“资源名称”) //异步加载
  • 第四步 (卸载AB包文件)
    • Unload(false); //不删除已经加载出的资源
    • Unload(true);//会将加载出的资源也进行删除

具体加载代码:

/// <summary>
/// 简单加载ab包资源
/// </summary>
public class SimpleLoad : MonoBehaviour
{
    private void Start()
    {
        //步骤一:从文件中加载ab包
        AssetBundle assetBundle = AssetBundle.LoadFromFile(AB包的文件路径);//从文件中加载ab包
        //步骤二:通过资源名称加载资源
        Sprite UISprite = assetBundle.LoadAsset<Sprite(加载资源的类型)>(加载资源的名称);
        GameObject.Find("Canvas/Image").GetComponent<Image>().sprite = UISprite;
        assetBundle.Unload(false);//资源卸载
    }
}
/// <summary>
/// 加载有依赖关系的AB包资源
/// </summary>
public class DependencyLoad : MonoBehaviour
{
    public Transform canvasTF;
    private AssetBundle dependencieAb;
    private void Start()
    {
        DependencyLoadAB();
    }
    private void DependencyLoadAB()
    {
        //第一步:加载被依赖的ab包
        //1.加载主ab包
        AssetBundle mainAb = AssetBundle.LoadFromFile(主AB包路径);
        //2.从Ab包中获取(加载)配置文件
        AssetBundleManifest mainManifest = mainAb.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        //3.加载出被依赖的ab包文件
        string[] dependencies = mainManifest.GetAllDependencies(需要加载的AB包名称);
        //语句含义:查找所需ab包的依赖ab包文件;参数:所需要的ab包文件,返回值:所有被依赖的AB包文件名称所组成的数组
        for (int i = 0; i < dependencies.Length; i++)
        {            
            dependencieAb = AssetBundle.LoadFromFile(Config.AbPath + "/" + dependencies[i]);//参数AB包的文件路径
        }
        //第二步:加载所需要的ab包
        AssetBundle needAB = AssetBundle.LoadFromFile(所需要的ab包的路径);
        //第三步:加载所需ab包中的文件
        GameObject needABGO = needAB.LoadAsset<GameObject(加载资源的类型)>(加载资源的名称);
        Instantiate(needABGO).transform.SetParent(canvasTF);
        //卸载ab包释放ab包
        dependencieAb.Unload(false);
        needAB.Unload(false);
    }
}
/// <summary>
/// 异步加载AB包资源
/// </summary>
public class AsyncLoad : MonoBehaviour
{
    private GameObject playerGO;
    public Image image;
    void Start()
    {
        //StartCoroutine(AsynLoadResources());
        StartCoroutine(AsynLoadABResources());
    }
    /// <summary>
    /// 异步加载Resources资源
    /// </summary>
    /// <returns></returns>
    private IEnumerator AsynLoadResources()
    {
        //使用一个异步加载
        ResourceRequest GoResourceRequest = Resources.LoadAsync<GameObject>("Player");
        Debug.Log(Time.time);
        //协同程序会在资源加载成功后,继续执行接下来的代码(底层封装了线程加载资源)
        yield return GoResourceRequest;
        Debug.Log(Time.time);
        playerGO = GoResourceRequest.asset as GameObject;
        Instantiate(playerGO);
    }

    /// <summary>
    /// 异步加载AB包资源
    /// </summary>
    /// <returns></returns>
    private IEnumerator AsynLoadABResources()
    {
        //异步加载ab包
        AssetBundleCreateRequest assetBundleCreate = AssetBundle.LoadFromFileAsync(加载ab包文件路径);
        yield return assetBundleCreate;
        //加载ab包中的资源
        AssetBundle assetBundle = assetBundleCreate.assetBundle;
        image.sprite = assetBundle.LoadAsset<Sprite(加载资源的类型)>(加载资源的名称);
        assetBundle.Unload(false);
    }
}

内存占用

对于GameObject来说,通常情况下需要对其进行改动,所以它是完全复制一份该资源来进行的实例化。也就是说,当AB包中的GameObject从内存中卸载后,实例化的GameObject不会因此丢失。并且对实例化对象的修改不会影响到GameObject资源。

对于Shader和Texture来说,通常情况下不需要对其进行改动,所以它是通过引用来进行的实例化。也就是说,当AB包中的Shader和Texture资源从内存中卸载后,实例化的Shader和Texture会出现资源丢失的情况。并且对实例化对象的修改会影响到Shader和Texture资源。

对于Material和Mesh来说,有时候可能需要对其进行改动,所以它是通过引用+复制来进行的实例化。也就是说,当AB包中的Material和Mesh资源从内存中卸载后,实例化的Material和Mesh会出现资源丢失的情况。并且对实例化对象的修改不会影响到Material和Mesh资源。

alt

卸载

一些ID Instance ID / GUID / Local ID(file ID) 参考链接

GUID:存在于unity中资源对应的.meta文件中,通过GUID就可以找到工程中的文件;表示这个文件,用来记录资源之间的关系。

alt

Local ID(file ID):资源内部的ID,用来记录资源之间的具体引用。

alt

Instance ID:资源的快捷访问ID,unity查找资源的时候会根据guid和local id与instance id的映射关系,直接使用instance id去查找,可以减少查找开销

卸载操作

当调用Resources.UnloadAsset()时,虽Object被销毁,但Instance ID被保留且包含有效的GUID和Local ID引用。

当调用AssetBundle.Unload(true)时,卸载从AssetBundle加载的所有游戏对象(及其依赖项)但是不包括已经实例化(复制)的对象,因为它们不再属于AssetBundle;从AssetBundle中加载的纹理(仍然属于AssetBundle)会从场景中的游戏资源消失,会被是为缺少材质。而且Instance ID的GUID和Local ID引用变无效。

当调用AssetBundle.Unload(false)时,虽Object不被销毁,但Instance ID的GUID和Local ID引用变无效。场景中的物体会与该AB包分离链接。即该物体的instance ID引用的GUID和Local ID会断开引用,无法再通过该instance ID找到GUID和Local ID。

假设材质 M 是从 AssetBundle AB 加载的,如下所示。

如果调用 AB.Unload(true),活动场景中的任何 M 实例也将被卸载并销毁。

如果改作调用 AB.Unload(false),那么将会中断 M 和 AB 当前实例的链接关系。

alt

如果稍后再次加载 AB 并且调用 AB.LoadAsset(),则 Unity 不会将现有 M 副本重新链接到新加载的材质。而是将加载 M 的两个副本。 alt

alt

如果再次加载该AB包时,分离了链接的物体不会受该新加载的AB包管理。因此如果不注意的话可能会导致一些不可控的问题。Unity中有Resources.UnloadUnusedAssets()方法可以很好地解决这个问题。

全部评论

相关推荐

2025-12-16 22:19
已编辑
南昌市第三中学 Java
个人背景:27届本科&nbsp;江西普通一本院校个人经历:小厂->用友->蔚来->美团->腾讯不知不觉已经有了五段实习经历,也快在外面漂泊一年半了,在今年也完成了两年前自己想进大厂的目标,可能在别人看来确实就是一段比较传奇的过程,一步一步都在向上走,也会有很多人来问我相关学习实习的一些问题,我看到了也会尽量去回复,但现在我想给大家说的并不是千篇一律的学习路线,而是我认为更为重要的——勇气与抉择。下面我来分享一下这些年的心路历程最初学习背景:我跟很多人一样,都是刚进入大学才开始接触计算机,也刚刚拥有自己的电脑,在刚开始学习的过程没有任何人来帮助我,给予我相关的指导,完全是自己摸索出来的一条学习路线,不会有如今这样有很多完善好的速成路线,而家里人都在想让我考研,似乎本科以我的学历就业是不现实的。我也很早意识到了学历对于我的限制,所以萌生出了大一就开始实习的想法,但这个想法在当时基本上是不存在。所有人都在抨击我(这里感兴趣的话可以看我最早发的帖子),有的人说本科想进大厂痴人说梦,有的人劝我以我的学历考研才是上策,有的人说我屁都不懂就来卷,总之我很难说去看到有支持的。我大一的时候还没卷成如今这样很多大一实习,当我想找到是否有跟我一样下定决心一步一步往上走的人,我当时是没有找到的,要么是秋招的哀嚎,要么就直接是零实习进大厂(现在我知道,这里所谓的普通学历0实习进大厂的水分有很多,排除真正意义上的运气和实力,其他基本上全是造假作弊,大家自己心知肚明,也要放平心态)这就导致了一个没有先例的情况,很多人也都是拿没有先例来抨击我,包括家里人也不支持我去实习,可能很多人的积极性就会下降,但我从来不会信所谓的不可能,如果没有先例,那我就会是第一个,他们不行,是因为他们没能力,他们坚持不下去。勇气是很重要的,当你发现你身边没有人像你一样,就很少会有人相信你,看好你,但好在,我不在乎。最初实习阶段:在最初3000沟通只有零星几个面试的时候,那感觉确实很不好受,沉没成本太大,得到的正反馈却太少,当时基本上都是一天学八个小时从来不间断,没有周末没有节假日,甚至过年我都在学习,这就导致我现在都会因为我周末偶尔休息的时候会有负罪感,我感觉已经是种病了,我也知道我也可以休息会但控制不了。当时我出去实习口袋里有1w块(这是我高中三年加大一一年存下来的,基本上是很抠很抠,一个月生活费有时候有一千多有时候就五六百,但也算得上是成功攒了一点钱)但第一次总会是很害怕,担心租房被骗,担心工作能力不行,担心被公司坑,担心学校原因导致不能实习等等,基本上在前面几段实习是根本不攒钱的,代课已经花了一万多,加上租房来回,基本上只能说堪堪不负支出,后来远赴北京,作为一个南方人,有很多不适应的地方,但现在回过头来一想,已经在北京呆了一年多了。我知道很多人要么担心学校因素,要么担心赚的还没花的多,种种因素导致了实习的困难,我也很害怕,我的钱会不会最终全部打水漂,学校会不会爆雷,我以后还能顺利实习吗等等。但对于我来说,我能对自己狠下心,我能接受通勤时间一个半小时只为节省那么几百块的房租钱,我能控制自己的消费的欲望,我能每个月大把大把把钱给代课,这可能就是我能够初期实习顺利的原因,这需要勇气,也需要对自己狠。实习中的抉择:在有了两段实习经历后,我的目标就朝着大厂进发,在去蔚来的中途,我oc了七八家中小厂公司,这里面不乏一些待遇极其优越的公司(有一家我真的差点就去了),但我最终还是都拒了,因为我清楚的明白想往上走的,只有公司title会帮你说话,没有人有义务理解你的困难你的坚持,好在最后去了蔚来,也算如愿以偿。从蔚来到美团倒是没有过多纠结,因为在最开始的梦中情厂就是美团,但从美团去腾讯这个决定或许是我人生中的转折点。美团多次挽留我,帮我沟通问hr,基本上就是一定能转暑期然后成功转正,仿佛这年薪40w的工作已经触手可得,所以在拿到腾讯offer的那一刻并没有多高兴,因为我意识到这可能是我此生最接近大厂的一次机会,可能大部分人都会选择留在美团,我也认为这一定是一个好的选择。我能够走到如今,是永远相信自己的判断,我的每一步都是在赌一个好的未来,只不过,这次赌注大了点而已,或许未来我再也进不了这些所谓的大厂,但我赌的不是选择错对,我赌我不后悔。所谓信念支撑:都说人要为自己而活,但我或许做不到,毕竟我身处人情社会,有许多爱我的人在等着我成长,我也不能接受因为能力而再次放弃一段感情,最近喜欢一段歌词:爱我的人相信我我一直在努力改变所有失败为你们而存在爱我的人感谢你你们的爱就算人生不是精彩我也要勇敢的姿态最后的最后,我想给大家传递的从来都不是一个普通学历进入大厂的意气风发,我想给大家传递的,是一股相信自己能够向上的信念和可能性。在没有打比赛能力,没有开源能力,没有学历等各个限制下,我帮大家试出了一条能够向上的路。如果没有先例,那我会是第一个。我们不需要弄虚作假,只靠自己一步一步脚踏实地,哪怕慢一点,不赌自己是否成功,只赌自己不后悔,问心无愧。最后送给大家,也送给自己一段话结束2025:生活可能没你想的那么好,也不会像你想的那么糟,人的脆弱和坚强,都超乎了你的想象,有时候可能脆弱的一句话就泪流满面,有时候你发现自己咬咬牙已经走了很长的路了
等闲_:感觉咱们双非的同学都有一个共性,想证明双非也是能进大厂的,我之前所有的标签都喜欢带着双非,仿佛这样可以像别人证明自己的实力,现在我却不再想证明双非到底能不能进大厂,我的生活的所有者是我自己,享受生活,接受结果
2025年终总结
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务