Detailed explanation of AB package in U3D (super detail, features, packaging, loading, manager)

Detailed explanation of assetbund3d in AssetBundle

Concept of AssetBundle

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, you can minimize the memory pressure at runtime, dynamically load and unload AB packages, and then selectively load content.

Advantages of AssetBundle

  1. The storage location of AB package is customized, and then it can be put into the readable and writable path to facilitate hot update

  2. AB packets can be compressed in a customized way. You can choose not to compress or LZMA, LZ4 and other compression methods to reduce the size of packets and carry out network transmission faster.

  3. Resources can be distributed in different AB packages to minimize the memory pressure during operation, so that they can be used and loaded immediately, and the required contents can be loaded selectively.

  4. AB package supports dynamic update in the later stage, which significantly reduces the size of the initial installation package. Non core resources are uploaded to the server in the form of AB package, which is dynamically loaded in the later run to improve the user experience.

Comparison between AssetBundle and Resources

AssetBundleResources
Resources can be distributed across multiple packagesAll resources are packaged into a big package
Flexible storage location customizationIt must be stored in the Resources directory
Flexible compression mode (LZMA,LZ4)All resources will be compressed into binary
Support dynamic update in later periodAfter packaging, the resource is read-only and cannot be changed dynamically

Characteristics of AssetBundle

  1. AB package can store most Unity resources, but it cannot directly store C# scripts. Therefore, Lua or compiled DLL files need to be used for code hot update.

  2. AB package cannot be loaded repeatedly. When AB package has been loaded into memory, it must be unloaded before it can be loaded again.

  3. Multiple resources are distributed in different AB packages, and some resources such as the map of a preform may not be in the same package. If they are loaded directly, some resources will be lost, that is, there is a dependency between ab packages. When loading the current AB package, the package it depends on needs to be loaded together.

  4. After packaging, a main package will be automatically generated (the name of the main package varies with the platform), and the version number, check code (CRC) and relevant information (name and dependency) of all other packages will be stored under the manifest of the main package

Whole process of AssetBundle packaging

This time mainly introduces the AB package management plug-in AssetBundle Browser officially provided by Unity for packaging

1. Acquisition of AssetBundleBrowser plug-in

  1. For Unity 2019, you can find this plug-in in Windows - > packagemanager and install it directly

  2. This plug-in may not be found in method 1 after version 2020 or other versions. You can search GitHub for the download compressed package and download address https://github.com/Unity-Technologies/AssetBundles-Browser

Unzip the downloaded installation package to the Packages folder of Unity project (be sure to unzip it)

2. Use of AssetBundleBrowser panel

  • After the plug-in is correctly obtained and installed, open the AB package management panel under windows - > AssetBundle browser. There are three panels in total

  • Configure panel: it can view the basic information (size, resources, dependency, etc.) of the current AB package and its internal resources

  • Build panel: responsible for the relevant settings of AssetBundle packaging. You can package it according to build

    Three compression methods

    1. NoCompression: no compression, fast decompression, large package, not recommended.
    2. LZMA: minimum compression and slow decompression. Use one resource to decompress all resources under the package.
    3. LZ4: the compression is slightly larger, the decompression is fast, what to use to decompress, and the memory occupation is low. It is more recommended to use it.

    Generally, the settings that need to be changed are the relevant option settings checked in the figure.

  • Inspect panel: mainly used to view some details (size, resource path, etc.) of the packaged AB package files

3. Set the AssetBundle package to which the asset belongs

Under the Inspector panel of the resources to be packaged, you can select which ab package they should be placed under. You can also create a New ab package to put the resources in. After putting them in, you can pack them again with Build to put the resources into the corresponding AB package.

AssetBundle Manager

Using AssetBundleBrowser, you can easily package AB packages. More importantly, how to load the resources in AB packages and use them. Unity has provided a set of API s to load AB packages and resources.

Main requirements of 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 (by name and type) from AB package
  3. Unload the loaded AB package when it is no longer in use

Main related API s

//Relevant API s required for AB package loading
//1. Load AB package according to the path. Note that ab package cannot be loaded repeatedly
AssetBundle ab = AssetBundle.LoadFromFile(path);
//2. Load resources with specified name and type under ab package
T obj = ab.LoadAsset<T>(ResourceName); //Generic loading
Object obj = ab.LoadAsset(ResourceName); //Type conversion is required for subsequent use of non generic loading
Object obj = ab.LoadAsset(ResourceName,Type); //Parameter indicates the resource type to prevent duplicate names
T obj = ab.LoadAssetAsync<T>(resName); //Asynchronous generic loading
Object obj = ab.LoadAssetAsync(resName); //Asynchronous non generic loading
Object obj = ab.LoadAssetAsync(resName,Type); //Parameter indicates the resource type to prevent duplicate names
//3. Unload the AB package. The bool parameter indicates whether to delete the resources that have been loaded into the scene from the AB package (generally false)
ab.UnLoad(false); //Uninstall a single ab package
AssetBundle.UnloadAllAssetBundles(false); //Uninstall all AB packages

The following is the detailed code and comments of AB package manager. The singleton mode 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 package manager uses globally unique singleton mode
    /// </summary>
    public class ABManager : MonoSingleton<ABManager>
    {
        //AB package cache - solving the problem that ab packages cannot be loaded repeatedly is also conducive to improving efficiency.
        private Dictionary<string, AssetBundle> abCache;

        private AssetBundle mainAB = null; //Main package

        private AssetBundleManifest mainManifest = null; //Configuration file in main package --- used to obtain dependent packages

        //Basic path under each platform - use macros to determine the streamingAssets path under the current platform
        private string basePath { get
            {
            //Use the StreamingAssets path. Note that check copy to streamingAssets when packaging AB package
#if UNITY_EDITOR || UNITY_STANDALONE
                return Application.dataPath + "/StreamingAssets/";
#elif UNITY_IPHONE
                return Application.dataPath + "/Raw/";
#elif UNITY_ANDROID
                return Application.dataPath + "!/assets/";
#endif
            }
        }
        //Main package name under each platform --- used to load the main package and obtain dependency information
        private string mainABName
        {
            get
            {
#if UNITY_EDITOR || UNITY_STANDALONE
                return "StandaloneWindows";
#elif UNITY_IPHONE
                return "IOS";
#elif UNITY_ANDROID
                return "Android";
#endif
            }
        }

        //Inherits the initialization function provided by the singleton mode
        protected override void Init()
        {
            base.Init();
            //Initialize dictionary
            abCache = new Dictionary<string, AssetBundle>();
        }


        //Load AB package
        private AssetBundle LoadABPackage(string abName)
        {
            AssetBundle ab;
            //When loading ab package, its dependent package shall be loaded together.
            if (mainAB == null)
            {
                //Load the main package according to the basic path and main package name under each platform
                mainAB = AssetBundle.LoadFromFile(basePath + mainABName);
                //Obtain the AssetBundleManifest resource file under the main package (containing dependency information)
                mainManifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            }
            //Get the fixed API names of all dependent packages according to the manifest
            string[] dependencies = mainManifest.GetAllDependencies(abName);
            //Loop load all dependent packages
            for (int i = 0; i < dependencies.Length; i++)
            {
                //Join if not in cache
                if (!abCache.ContainsKey(dependencies[i]))
                {
                    //Load according to dependent package name
                    ab = AssetBundle.LoadFromFile(basePath + dependencies[i]);
                    //Pay attention to adding to the cache to prevent repeated loading of AB package
                    abCache.Add(dependencies[i], ab);
                }
            }
            //Load the target package -- similarly, pay attention to the cache problem
            if (abCache.ContainsKey(abName)) return abCache[abName];
            else
            {
                ab = AssetBundle.LoadFromFile(basePath + abName);
                abCache.Add(abName, ab);
                return ab;
            }


        }
        
        
        //==================Three resource synchronous loading methods==================
        //Provide a variety of calling methods to facilitate the calling of other languages (Lua does not support generics well)
        #Three overloads of region synchronous loading
        
        /// <summary>
        ///Synchronous loading of resources - Generic loading is simple and intuitive without displaying transformation
        /// </summary>
        ///< param name = "abname" > AB package name < / param >
        ///< param name = "resname" > resource name < / param >
        public T LoadResource<T>(string abName,string resName)where T:Object
        {
            //Load target package
            AssetBundle ab = LoadABPackage(abName);

            //Return resource
            return ab.LoadAsset<T>(resName);
        }

        
        //It is not recommended to use if the specified type has duplicate names. The conversion type needs to be displayed when it is used
        public Object LoadResource(string abName,string resName)
        {
            //Load target package
            AssetBundle ab = LoadABPackage(abName);

            //Return resource
            return ab.LoadAsset(resName);
        }
        
        
        //The use of parameter passing type is suitable for language calls that are not supported by generic type, and the type needs to be forced when using
        public Object LoadResource(string abName, string resName,System.Type type)
        {
            //Load target package
            AssetBundle ab = LoadABPackage(abName);

            //Return resource
            return ab.LoadAsset(resName,type);
        }

        #endregion
        
        
        //================Three resource asynchronous loading methods======================

        /// <summary>
        ///Asynchronous loading is provided -- note that the loading of AB package here is synchronous loading, but the loading resources are asynchronous
        /// </summary>
        ///< param name = "abname" > AB package name < / param >
        ///< param name = "resname" > resource name < / param >
        public void LoadResourceAsync(string abName,string resName, System.Action<Object> finishLoadObjectHandler)
        {
            AssetBundle ab = LoadABPackage(abName);
            //Enable the collaboration process to provide the delegation after the resource is loaded successfully
            StartCoroutine(LoadRes(ab,resName,finishLoadObjectHandler));
        }

        
        private IEnumerator LoadRes(AssetBundle ab,string resName, System.Action<Object> finishLoadObjectHandler)
        {
            if (ab == null) yield break;
            //Asynchronous load resource API
            AssetBundleRequest abr = ab.LoadAssetAsync(resName);
            yield return abr;
            //Delegate call processing logic
            finishLoadObjectHandler(abr.asset);
        }
        
        
        //Load resources asynchronously according to 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;
            //Delegate call processing logic
            finishLoadObjectHandler(abr.asset);
        }

        
        //Generic loading
        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;
            //Delegate call processing logic
            finishLoadObjectHandler(abr.asset as T);
        }


        //====================Two unloading methods of AB package=================
        //Single package uninstall
        public void UnLoad(string abName)
        {
            if(abCache.ContainsKey(abName))
            {
                abCache[abName].Unload(false);
                //Note that the cache needs to be removed together
                abCache.Remove(abName);
            }
        }

        //Uninstall all packages
        public void UnLoadAll()
        {
            AssetBundle.UnloadAllAssetBundles(false);
            //Pay attention to emptying the cache
            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>
    ///Script singleton class, which is responsible for creating instances for unique scripts
    ///<summary>

    public class MonoSingleton<T> : MonoBehaviour where T:MonoSingleton<T> //Note that this constraint is T and must be itself or a subclass
    {
        /*
        Compared with directly creating an instance in a script that needs to be created uniquely, the Awake initialization process needs to solve problems
        1.Duplicate code
        2.Initialize in Awake, other scripts invoke abnormal situations that may be Null in Awake.
         */

        //Solution 1: use generics to create instances solution 2: use on-demand loading (that is, load in get when there are other script calls)

        private static T instance; //Create private object record values, which can be assigned only once to avoid multiple assignments

        public static T Instance
        {
            //Realize on-demand loading
            get
            {
                //When the value has been assigned, it can be returned directly
                if (instance != null) return instance;

                instance = FindObjectOfType<T>();

                //In order to prevent the abnormal situation that the script has not been attached to the object and cannot be found, you can create an empty object to hang it
                if (instance == null)
                {
                    //If an object is created, the wake of its script will be called when it is created, that is, the wake of T (t's wake is actually the inherited parent class)
                    //Therefore, there is no need to assign a value to instance at this time. It will be assigned in wake and will be initialized naturally, so init() is not required
                    /*instance = */
                    new GameObject("Singleton of "+typeof(T)).AddComponent<T>();
                }
                else instance.Init(); //Ensure that Init is executed only once

                return instance;

            }
        }

        private void Awake()
        {
            //If no other script calls this instance in Awake, you can initialize instance in Awake.
            instance = this as T;
            //initialization
            Init();
        }

        //Subclasses initialize members. If they are placed in wake, there will still be Null problems, so make an init function to solve it (available but not needed)
        protected virtual void Init()
        {

        }
    }

}

In addition to the above script singleton class singleton mode, there are two other pure C# singleton modes that do not need to inherit monobehavior. Interested readers can learn about the hungry and lazy modes under the singleton mode by themselves.

Keywords: C# Unity assetbundle

Added by learning_php_mysql on Sun, 06 Feb 2022 03:27:31 +0200