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

Table of contents

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) Packing of AB bags

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

Parameter 1: ab package output path Parameter 2: ab package information (mainly assetBundleName=ab package name, assetNames=resource name)   Note: assetNames must be under Assets Path, do not use the windows path, otherwise the package 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 you need to pay attention to when packaging, the scene package must not be compressed, otherwise it will not be loaded and you need to 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>
        /// 场景资源路径
        /// </summary>
        private static string _scenePath = $"{Application.dataPath}/AssetBundle/";

        /// <summary>
        /// UI资源路径
        /// </summary>
        private static string _uiPath = $"{Application.dataPath}/AssetBundle/Resources/";

        /// <summary>
        /// 最终场景包输出目录
        /// </summary>
        public static string SceneOutPutPath = $"{Application.persistentDataPath}/assetbundle_orgin";

        /// <summary>
        /// 最终prefab包输出目录
        /// </summary>
        public static string UiOutputPath = $"{Application.persistentDataPath}/assetbundle_uiorgin";

        [MenuItem("UnityTools/打包资源")]
        public static void BuildAssetsBundle()
        {
            BuildAllScenes();
            BuildAllPrefabs();
            //刷新文件
            AssetDatabase.Refresh();
        }
        
        private static void BuildAllScenes()
        {
            var directorys = Directory.GetDirectories(_scenePath, "V*");
            var folder = directorys[directorys.Length - 1];
            
            //获取指定文件夹下所有的.unity文件
            var sceneFiles = Directory.GetFiles(folder + $"/Scenes/", $"*.unity",
                SearchOption.AllDirectories);

            for (int i = 0; i < sceneFiles.Length; i++)
            {
                //打包进度
                EditorUtility.DisplayProgressBar($"正在打包场景中...", sceneFiles[i], 1.0f);

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

                //批量打包所有的.unity文件  设置输出路径和输出windows平台
                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()
        {
            //获取指定文件夹下所有的.prefab文件
            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++)
            {
                //打包进度
                EditorUtility.DisplayProgressBar($"正在打包预设中...", 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>
    /// 包路径
    /// </summary>
    private string packagePath;
    
    /// <summary>
    /// ab包缓存
    /// </summary>
    private Dictionary<string, AssetBundle> abCache;

    /// <summary>
    /// 主包
    /// </summary>
    private AssetBundle mainAB = null;

    /// <summary>
    /// 主包中的配置文件---->用来获取依赖包
    /// </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>();

                //防止脚本还未挂到物体上,找不到的异常情况,自行创建空物体挂上去
                if (instance == null)
                {
                    new GameObject("IMgrTo" +typeof(T)).AddComponent<T>();
                }
                else instance.Init(); //保证Init只执行一次

                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;
            //加载资源
            var testui = Instantiate(ABMgr.Instance.LoadResources<GameObject>("testui.unity3d", "testui"));
            if (testui != null && 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 uninstalled 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-appendingly. This will destroy all objects in the current scene and automatically call Resources.UnloadUnusedAssets.

// 1.解除引用后调用
Resources.UnloadUnusedAssets();
// 2.上文提到的,卸载ab所加载的所有asset
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.

Guess you like

Origin blog.csdn.net/ThreePointsHeat/article/details/134183449