Unity AssetBundle batch packaging and loading (scene, Prefab) complete process

Directory

1. Article introduction

2. Specific ideas and writing methods

(1) Packing of AB bags

(2) Loading of AB package

(3) AB package uninstallation

3. Conclusion

1. Article introduction

This blog is mainly for recording and learning. It briefly introduces the batch packaging of AB packages and the method of loading AB packages. If you are lucky enough to see this blog, I hope it will be helpful to you.

2. Specific ideas and writing methods

(1) Packaging of AB package

First introduce the api BuildPipeline.BuildAssetBundle(string outputPath,AssetBundleBuild[] builds,BuildAssetBundleOptions assetBundleOptions,BuildTarget targetPlatform) used to build ab packages

Parameter 1: ab package output path Parameter 2: ab package information (mainly assetBundleName=ab package name, assetNames=resource name) Note: assetNames must be the path under Assets, do not use the windows path, otherwise the packaging will report an error! ! !

 /// <summary>
    /// <para>Build AssetBundles from a building map.</para>
    /// </summary>
    /// <param name="outputPath">Output path for the AssetBundles.</param>
    /// <param name="builds">AssetBundle building map.</param>
    /// <param name="assetBundleOptions">AssetBundle building options.</param>
    /// <param name="targetPlatform">Target build platform.</param>
    /// <returns>
    /// <para>The manifest listing all AssetBundles included in this build.</para>
    /// </returns>
    public static AssetBundleManifest BuildAssetBundles(
      string outputPath,
      AssetBundleBuild[] builds,
      BuildAssetBundleOptions assetBundleOptions,
      BuildTarget targetPlatform)
    {
      BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(targetPlatform);
      return BuildPipeline.BuildAssetBundles(outputPath, builds, assetBundleOptions, buildTargetGroup, targetPlatform);
    }

Preparation work before packing:

Generally, small ToB projects will have some resource iteration requirements, so scene resources are managed in separate folders. Each time there is a new iteration, only the scene resources in the latest version are incrementally packaged.

The same is true for UI resources, but UI resources for small projects do not need to be managed by versions. Unless enterprise-level projects require hot updates or version management, they are managed by versions.

The following is the specific code:

Things to note when packaging, the scene package must not be compressed, otherwise it will not be loaded and you must use BuildAssetBundleOptions.None. When playing other resources, you can choose LZ4 compression BuildAssetBundleOptions.ChunkBasedCompression. LZ4 compression is a compromise between LZMA and no compression. The constructed AssetBundle resource file will be slightly larger than LZMA compression, but there is no need to load all resources when loading resources, so the speed will be faster than LZMA. It is recommended to use it in projects.

using System;
using System.Collections.Generic;
using System.IO;
using NWH.Common.AssetInfo;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace editor.AssetBundle
{
    public class BuildAssetBundle : Editor
    {
        /// <summary>
        /// Scene resource path
        /// </summary>
        private static string _scenePath = $"{Application.dataPath}/AssetBundle/";

        /// <summary>
        /// UI resource path
        /// </summary>
        private static string _uiPath = $"{Application.dataPath}/AssetBundle/Resources/";

        /// <summary>
        /// Final scene package output directory
        /// </summary>
        public static string SceneOutPutPath = $"{Application.persistentDataPath}/assetbundle_orgin";

        /// <summary>
        /// Final prefab package output directory
        /// </summary>
        public static string UiOutputPath = $"{Application.persistentDataPath}/assetbundle_uiorgin";

        [MenuItem("UnityTools/Packaged Resources")]
        public static void BuildAssetsBundle()
        {
            BuildAllScenes();
            BuildAllPrefabs();
            //Refresh file
            AssetDatabase.Refresh();
        }
        
        private static void BuildAllScenes()
        {
            var directories = Directory.GetDirectories(_scenePath, "V*");
            var folder = directories[directorys.Length - 1];
            
            //Get all .unity files in the specified folder
            var sceneFiles = Directory.GetFiles(folder + $"/Scenes/", $"*.unity",
                SearchOption.AllDirectories);

            for (int i = 0; i < sceneFiles.Length; i + + )
            {
                //Packaging progress
                EditorUtility.DisplayProgressBar($"Packaging scene...", sceneFiles[i], 1.0f);

                if (!Directory.Exists(SceneOutPutPath))
                {
                    Directory.CreateDirectory(SceneOutPutPath);
                }

                //Package all .unity files in batches, set the output path and output windows platform
                AssetBundleBuild buildPacket = new AssetBundleBuild();
                buildPacket.assetBundleName = $"{Path.GetFileNameWithoutExtension(sceneFiles[i]).ToLower()}.unity3d";
                buildPacket.assetNames = new string[] { sceneFiles[i].Substring(sceneFiles[i].IndexOf("Assets/")) };

                var abManifest = BuildPipeline.BuildAssetBundles(
                    SceneOutPutPath,
                    new AssetBundleBuild[]{buildPacket},
                    BuildAssetBundleOptions.None,
                    BuildTarget.StandaloneWindows64
                );
            }
        
            EditorUtility.ClearProgressBar();
        }

        private static void BuildAllPrefabs()
        {
            //Get all .prefab files in the specified folder
            var uiFiles = Directory.GetFiles(_uiPath, $"*.prefab",
                SearchOption.AllDirectories);

            if (!Directory.Exists(UiOutputPath))
            {
                Directory.CreateDirectory(UiOutputPath);
            }

            List<AssetBundleBuild> buildInfoList = new List<AssetBundleBuild>();

            for (int i = 0; i < uiFiles.Length; i + + )
            {
                //Packaging progress
                EditorUtility.DisplayProgressBar($"Packaging presets...", uiFiles[i], 1.0f);
                
                AssetBundleBuild buildInfo = new AssetBundleBuild();
                buildInfo.assetBundleName = $"{Path.GetFileNameWithoutExtension(uiFiles[i]).ToLower()}.unity3d";
                buildInfo.assetNames = new string[] { uiFiles[i].Substring(uiFiles[i].IndexOf("Assets/")) };
                buildInfoList.Add(buildInfo);
                
                AssetBundleManifest buildManifest = BuildPipeline.BuildAssetBundles(
                    UiOutputPath,
                    buildInfoList.ToArray(),
                    BuildAssetBundleOptions.ChunkBasedCompression,
                    BuildTarget.StandaloneWindows64
                    );
            }

            EditorUtility.ClearProgressBar();
        }
    }
}

The output file after packaging is completed:

(2) Loading of AB package

I use synchronous loading to load scenes and UI resources. Students who need to use asynchronous loading or network loading can read other articles for introduction.

AssetBundle.LoadFromFile synchronously loads an AssetBundle from a file on disk. This function supports bundles of any compression type. In the case of lzma compression, the data is decompressed into memory. Bundles can be read directly from disk, both uncompressed and using block compression.

Compared with LoadFromFileAsync, this version is synchronous and will wait for the AssetBundle object to be created before returning.

This is the fastest way to load an AssetBundle.

using System.Collections;
using System.Collections.Generic;
using UnityEditor.VersionControl;
using UnityEngine;
using utils;

public class ABMgr : IMgr<ABMgr>
{
    /// <summary>
    /// package path
    /// </summary>
    private string packagePath;
    
    /// <summary>
    /// ab package cache
    /// </summary>
    private Dictionary<string, AssetBundle> abCache;

    /// <summary>
    /// Main package
    /// </summary>
    private AssetBundle mainAB = null;

    /// <summary>
    /// The configuration file in the main package ----> is used to obtain dependent packages
    /// </summary>
    private AssetBundleManifest manifest = null;

    protected override void Init()
    {
        base.Init();

        abCache = new Dictionary<string, AssetBundle>();
        packagePath = $"{Application.persistentDataPath}/assetbundle_uiorgin/";
    }

    private AssetBundle LoadABPackage(string abName)
    {
        AssetBundle ab;

        if (mainAB == null)
        {
            mainAB = AssetBundle.LoadFromFile(packagePath + "assetbundle_uiorgin");
            manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        }

        var dependencies = manifest.GetAllDependencies(abName);

        for (int i = 0; i < dependencies.Length; i + + )
        {
            if (!abCache.ContainsKey(dependencies[i]))
            {
                ab = AssetBundle.LoadFromFile(packagePath + dependencies[i]);
                
                abCache.Add(dependencies[i], ab);
            }
        }

        if (abCache.ContainsKey(abName)) return abCache[abName];
        else
        {
            ab = AssetBundle.LoadFromFile(packagePath + abName);
            abCache.Add(abName, ab);
            return ab;
        }
    }

    public T LoadResources<T>(string abName, string resName) where T : Object
    {
        AssetBundle ab = LoadABPackage(abName);

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

The ab package loads the singleton base class:

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

namespace utils
{
    public class IMgr<T> : MonoBehaviour where T: IMgr<T>
    {
        private static T instance;

        public static T Instance
        {
            get
            {
                if (instance != null) return instance;

                instance = FindObjectOfType<T>();

                //To prevent abnormal situations where the script has not been attached to the object and cannot be found, create an empty object and hang it yourself.
                if (instance == null)
                {
                    new GameObject("IMgrTo" + typeof(T)).AddComponent<T>();
                }
                else instance.Init(); //Ensure that Init is only executed once

                return instance;
            }
        }

        private void Awake()
        {
            instance = this as T;
            
            Init();
        }

        protected virtual void Init()
        {
            
        }
    }
}

Let’s just write an example to see the loading effect:

 // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            OnLoadScene();
        }

        if (Input.GetKeyDown(KeyCode.A))
        {
            var go = GameObject.Find("Canvas")?.gameObject;
            //Load resources
            var testui = Instantiate(ABMgr.Instance.LoadResources<GameObject>("testui.unity3d", "testui"));
            if (testui != null & amp; & amp; go != null)
            {
                testui.transform.SetParent(go.transform);
                testui.transform.localPosition = Vector3.zero;
                testui.transform.localScale = Vector3.one;
            }
        }
    }

    private void OnLoadScene()
    {
        var ab = AssetBundle.LoadFromFile($"{Application.persistentDataPath}/assetbundle_orgin/scene1.unity3d");

        Debug.LogError(ab.name);
        SceneManager.LoadScene("scene1", LoadSceneMode.Additive);
    }
(3) Uninstalling AB package

After the AssetBundle resource is used, it needs to be unloaded to release the memory space it occupies. The unloading of AssetBundle is mainly implemented by the AssetBundle.Unload API. This method needs to pass in a bool type parameter. If true is passed in, the AssetBundle itself and all resources loaded from the AssetBundle will be unloaded. If false is passed in, already loaded resources will be retained.
It is recommended to use AssetBundle.Unload(true) in most cases, because passing false will cause a waste of memory resources.

If you have to use AssetBundle.Unload(false), you can only unload a single object in the following two ways:

Eliminate all references to unnecessary objects in your scene and code. Once this is done, call Resources.UnloadUnusedAssets.
Load the scene non-additively. This will destroy all objects in the current scene and automatically call Resources.UnloadUnusedAssets.

///1. Called after dereferencing
Resources.UnloadUnusedAssets();
// 2. As mentioned above, unload all assets loaded by ab
ab.Unload(true);

3. Conclusion

This article ends here. It mainly records the usage of AB packages for scenes and UIs that I used in the project. I will also conduct more in-depth research on resource encryption, decryption, classification management, etc. in the future. I hope this article is helpful to you. If you like it, please give it a like. Thanks.