Detailed explanation of AB package in Unity

0. Reference link

Original author's article link: https://blog.csdn.net/Q540670228/article/details/122795671

1. AssetBundle concept

AssetBundle, also known as AB package, is a resource compression package provided by Unity for storing resources.

The AssetBundle system in Unity is an extension of resource management. By distributing resources in different AB packages, it can minimize the memory pressure at runtime. It can dynamically load and unload AB packages, and then selectively load content. .

Advantages of AssetBundle:

  1. The AB package storage location is customized and can then be placed in a readable and writable path to facilitate hot updates.
  2. AB packet custom compression method, you can choose no compression or choose compression methods such as LZMA and LZ4 to reduce the size of the packet and enable faster network transmission.
  3. Resources can be distributed in different AB packages to minimize the memory pressure during runtime, and can be loaded immediately and selectively load the required content.
  4. AB packages support dynamic updates at a later stage, significantly reducing the size of the initial installation package. Non-core resources are uploaded to the server in the form of AB packages and dynamically loaded during later runs to improve user experience.

2. Comparison between AssetBundle and Resources

AssetBundle Resources
Resources can be distributed across multiple packages All resources are packaged into one big package
Flexible storage location customization Must be stored in the Resources directory
Flexible compression methods (LZMA, LZ4) All resources will be compressed into binary
Support dynamic updates later After packaging, resources are read-only and cannot be dynamically changed.

3. AssetBundle characteristics

  • The AB package can store most Unity resources but cannot directly store C# scripts, so hot updates of code need to use Lua or store compiled DLL files.
  • The AB package cannot be loaded repeatedly. When the AB package has been loaded into the memory, it must be unloaded before it can be reloaded.
  • Multiple resources are distributed in different AB packages. Some resources such as the texture of a prefab may not be in the same package. Direct loading may cause some resources to be lost. That is, there are dependencies between AB packages. When loading the current AB packages need to load the packages they depend on together.
  • After packaging is completed, a main package will be automatically generated (the name of the main package varies with different platforms). The version number, check code (CRC), and all other package-related information (names, dependencies) will be stored in the manifest of the main package. )

Insert image description here

4. AssetBundle full packaging process

4.1 Obtaining the AssetBundleBrowser plug-in

Method 1: Unity 2019 version can find this plug-in directly in Windows —> PackageManager and install it directly
Insert image description here

After version 2020 or other versions, you may not be able to find this plug-in in method 1. You can search and download the compressed package on github. The download address: https://github.com/Unity-Technologies/AssetBundles-Browser
Insert image description here
will download the installation package. Unzip it to the Packages folder of the Unity project (be sure to unzip it)
Insert image description here

4.2 Use of AssetBundleBrowser panel

  • Configure panel: can view the basic situation of the current AB package and its internal resources (size, resources, dependencies, etc.)
    Insert image description here
  • Build panel: Responsible for the relevant settings of AssetBundle packaging. Press Build to package.
    Insert image description here
    Three compression methods.
  1. NoCompression: No compression, fast decompression, large package size, not recommended.
  2. LZMA: has the smallest compression and slow decompression. To use one resource, you need to decompress all resources in the package.
  3. LZ4: slightly larger compression, faster decompression, use whatever you use to decompress, low memory usage, more recommended.
    Generally, the settings that need to be changed are the relevant option settings checked in the picture.
  • Inspect panel: mainly used to view some details of the packaged AB package file (size, resource path, etc.)
    Insert image description here

4.3 Set the AssetBundle package to which the resource belongs

Insert image description here
At the bottom of the Inspector panel of the resource that needs to be packaged, you can select which AB package it should be placed under. You can also create a new AB package to put the resource into it. After placing it, Build the package again to put the resource into the corresponding AB package. middle.

5. AssetBundle Manager

The main requirement of the AB package manager: load the specified resources under the specified AB package. The basic steps are as follows:

  1. Load the AB package where the resource is located and all its dependent packages (find the dependent package name according to the manifest information of the main package)
  2. Load the specified resource from the AB package (according to name, type)
  3. Unload the loaded AB package when no longer in use.
    Main related APIs
//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包

The following is the detailed code and comments of the AB package manager. The singleton mode content involved will be briefly introduced later.

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;
        }
    }
}

The script singleton class inherited by the above manager is as follows

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()
        {
    
    

        }
    }

}

Guess you like

Origin blog.csdn.net/weixin_45136016/article/details/133126168