当前位置: 首页 > article >正文

Huatuo热更新--如何使用

在安装完huatuo热更新插件后就要开始学习如何使用了。

1.创建主框渐Main

新建文件夹Main(可自定义),然后按下图创建文件,注意名称与文件夹名称保持一致

 然后新建场景(Init场景),添加3个空物体分别为LoadDllManager,SceneLoadManager以及PrefabsLoadManager(这部分可根据实际开发需求拓展,此教程只做简单演示,只有切换场景,创建预制体,加载Dll需求),然后在Main文件夹下创建对应名称脚本文件并挂在相应物体上。

注意,Main里的脚本是框架类脚本,不做具体功能需求,所以不支持热更新,一般实现后不会再做修改,一旦修改了就需要重新Build。

下面是3个脚本具体实现。

注意,需要用到一个BetterStreamingAssets加载AB包的类,下载地址:Huatuo热更新使用教程-BetterStreamingAssets资源-CSDN文库

解压后放到Plugins文件夹下即可。

实现Manager脚本时会发现BetterStreamingAssets类提示报错,这是因为Main中没有添加BetterStreamingAssets,进行如下图所示操作即可,之后就会发现报错解决了。同理,其他Assembly Definition文件在使用其他Assembly Definition文件中的类时,也需要进行同样设置,比如之后添加的UIPart需要添加Main。

LoadDllManager

using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 加载Dll的管理器
/// </summary>
public class LoadDllManager : MonoBehaviour
{
    private static LoadDllManager _instance;
    /// <summary>
    /// 单例
    /// </summary>
    public static LoadDllManager Instance
    {
        get
        {
            return _instance;
        }
    }

    private void Awake()
    {
        _instance = this;
    }

    void Start()
    {
        Debug.Log("LoadDllManager start");
        BetterStreamingAssets.Initialize();
        DontDestroyOnLoad(gameObject);
        //加载初始Dll-UIPart
        LoadDll("UIPart", (value) =>
        {
            //找到MainScript脚本,执行LoadMainScene方法
            Type type = value.GetType("MainScript");
            type.GetMethod("LoadMainScene").Invoke(null, null);
        });
    }

    /// <summary>
    /// 加载dll
    /// </summary>
    /// <param name="dllName">dll名称</param>
    /// <param name="callBack">回调</param>
    public void LoadDll(string dllName, UnityAction<Assembly> callBack)
    {
#if !UNITY_EDITOR
        StartCoroutine(OnLoadDll(dllName, callBack));
#else
        var assembly = AppDomain.CurrentDomain.GetAssemblies().First(assembly => assembly.GetName().Name == dllName);
        callBack?.Invoke(assembly);
#endif
    }

    /// <summary>
    /// 协程加载dll
    /// </summary>
    /// <param name="dllName"></param>
    /// <param name="callBack"></param>
    /// <returns></returns>
    private IEnumerator OnLoadDll(string dllName, UnityAction<Assembly> callBack)
    {
        //判断ab包是否存在
        if (File.Exists($"{Application.streamingAssetsPath}/common"))
        {
            //加载ab包
            var dllAB = BetterStreamingAssets.LoadAssetBundleAsync("common");
            yield return dllAB;

            if(dllAB.assetBundle != null)
            {
                //加载dll
                TextAsset dllBytes = dllAB.assetBundle.LoadAsset<TextAsset>($"{dllName}.dll.bytes");
                var assembly = System.Reflection.Assembly.Load(dllBytes.bytes);
                //卸载ab包
                dllAB.assetBundle.Unload(false);
                //回调
                callBack?.Invoke(assembly);
            }
        }
    }
}

SceneLoadManager

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;

/// <summary>
/// 加载场景的管理器
/// </summary>
public class SceneLoadManager : MonoBehaviour
{
    private static SceneLoadManager _instance;
    public static SceneLoadManager Instance
    {
        get
        {
            return _instance;
        }
    }

    private void Awake()
    {
        _instance = this;
    }

    private void Start()
    {
        Debug.Log("SceneLoadManager start");
        BetterStreamingAssets.Initialize();
        DontDestroyOnLoad(gameObject);
    }

    /// <summary>
    /// 加载场景
    /// </summary>
    /// <param name="sceneName"></param>
    /// <param name="callBack"></param>
    public void LoadScene(string sceneName, UnityAction callBack = null)
    {
#if !UNITY_EDITOR
        StartCoroutine(OnLoadScene(sceneName, callBack));
#else
        StartCoroutine(OnLoadScene_Noab(sceneName, callBack));
#endif
    }

    /// <summary>
    /// 通过ab包加载场景
    /// </summary>
    /// <param name="sceneName"></param>
    /// <param name="callBack"></param>
    /// <returns></returns>
    private IEnumerator OnLoadScene(string sceneName, UnityAction callBack)
    {
        //判断场景ab包是否存在
        if(File.Exists($"{Application.streamingAssetsPath}/scenes"))
        {
            //加载ab包
            var dllAB = BetterStreamingAssets.LoadAssetBundleAsync("scenes");
            yield return dllAB;

            if(dllAB.assetBundle != null)
            {
                //异步加载场景
                var sceneLoadRequest = SceneManager.LoadSceneAsync(sceneName);
                yield return sceneLoadRequest;

                if(sceneLoadRequest.isDone)
                {
                    //获取加载的场景
                    Scene loadScene = SceneManager.GetSceneByName(sceneName);
                    //跳转场景
                    SceneManager.SetActiveScene(loadScene);
                    //回调
                    callBack?.Invoke();
                }
                //卸载AB包
                dllAB.assetBundle.Unload(false);
            }
        }
    }

    /// <summary>
    /// 加载场景--无需加载ab
    /// </summary>
    /// <param name="sceneName"></param>
    /// <param name="callBack"></param>
    /// <returns></returns>
    private IEnumerator OnLoadScene_Noab(string sceneName, UnityAction callBack)
    {
        //异步加载场景
        var sceneLoadRequest = SceneManager.LoadSceneAsync(sceneName);
        yield return sceneLoadRequest;

        if (sceneLoadRequest.isDone)
        {
            //获取加载的场景
            Scene loadScene = SceneManager.GetSceneByName(sceneName);
            //跳转场景
            SceneManager.SetActiveScene(loadScene);
            //回调
            callBack?.Invoke();
        }
    }
}

PrefabsLoadManager

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 加载预制体的管理器
/// </summary>
public class PrefabsLoadManager : MonoBehaviour
{
    private static PrefabsLoadManager _instance;
    public static PrefabsLoadManager Instance
    {
        get
        {
            return _instance;
        }
    }

    private void Awake()
    {
        _instance = this;
    }

    private void Start()
    {
        Debug.Log("PrefabsLoadManager start");
        BetterStreamingAssets.Initialize();
        DontDestroyOnLoad(gameObject);
    }

    /// <summary>
    /// 加载预制体
    /// </summary>
    /// <param name="prefabPath"></param>
    /// <param name="callBack"></param>
    public void LoadABPrefab(string prefabPath, UnityAction<GameObject> callBack)
    {
#if !UNITY_EDITOR
        string[] paths = prefabPath.Split('/');
        string prefabName = paths[paths.Length - 1];
        StartCoroutine(OnLoadPrefab(prefabName, callBack));
#else
        prefabPath += ".prefab";
        GameObject loadedPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
        GameObject obj = GameObject.Instantiate(loadedPrefab);
        callBack?.Invoke(obj);
#endif
    }

    /// <summary>
    /// 通过AB包加载预制体
    /// </summary>
    /// <param name="prefabName"></param>
    /// <param name="callBack"></param>
    /// <returns></returns>
    private IEnumerator OnLoadPrefab(string prefabName, UnityAction<GameObject> callBack)
    {
        //判断预制体的ab包是否存在
        if (File.Exists($"{Application.streamingAssetsPath}/prefabs"))
        {
            //加载ab包
            var dllAB = BetterStreamingAssets.LoadAssetBundleAsync("prefabs");
            yield return dllAB;

            if(dllAB.assetBundle != null)
            {
                //创建预制体
                GameObject loadedPrefab = GameObject.Instantiate(dllAB.assetBundle.LoadAsset<UnityEngine.GameObject>($"{prefabName}.prefab"));
                //卸载ab包
                dllAB.assetBundle.Unload(false);
                callBack?.Invoke(loadedPrefab);
            }
        }
    }
}

后续根据需求还会有图集的AB包,材质的AB包等,在此不做详细扩展。

至此一个主要的框架就好了,下面就要开始实现热更新的部分了。

2.实现UIPart热更新部分功能

创建UIPart文件夹(名称及内部脚本名称,方法名称可随意修改,但需要相应修改LoadDllManager对应名称字段),然后创建同名Assembly Definition文件。

创建MainScript脚本,实现如下

MainScript脚本为加载Main场景,创建Main场景,场景中添加一个Canvas,创建MainCanvas脚本,实现如下,创建MainView预制体

using UnityEngine;
using System;
using System.Linq;

public class MainCanvas : MonoBehaviour
{
    public GameObject lay_1;
    public GameObject lay_2;
    public GameObject lay_3;

    public static AssetBundle dllAB;
    private System.Reflection.Assembly gameAss;

    void Start()
    {
        PrefabsLoadManager.Instance.LoadABPrefab("Assets/UIPart/Prefabs/UI/MainView", (mainView) =>
        {
            if (mainView != null)
                mainView.transform.SetParent(lay_1.transform, false);
        });
    }
}

然后创建MainView预制体及脚本,实现自己想实现的测试功能,在此就不具体实现了。注意上述预制体,脚本都需要放在UIPart文件夹下,可自行创建区分的文件夹。

这些都完成后,需要在HybridCLR中配置一下,如图

 之后就可以进行生成DLL,打包AB包等操作

3.生成Dll文件

如图,自行选择平台

 生成Dll文件所在路径为Assets同级目录\HybridCLRData\HotUpdateDlls下对应平台内。

4.复制Dll,方便打AB包

然后就是打包AB包,打包前先将生成的Dll及部分依赖的Dll先复制到Assets内,方便打包成AB包,此处提供一个我简单实现的复制工具(使用UIToolkit实现)

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Collections.Generic;
using System.IO;

public class CopyDllEditor : EditorWindow
{
    public static readonly List<string> aotDlls = new List<string>()
            {
                "mscorlib.dll",
                "System.dll",
                "System.Core.dll",// 如果使用了Linq,需要这个
                // "Newtonsoft.Json.dll",
                // "protobuf-net.dll",
                // "Google.Protobuf.dll",
                // "MongoDB.Bson.dll",
                // "DOTween.Modules.dll",
                // "UniTask.dll",
            };

    /// <summary>
    /// 复制dll相关数据
    /// </summary>
    CopyDllData dllData = null;
    /// <summary>
    /// 用于初始化的json文件路径
    /// </summary>
    private string DllFileJsonPath = "";

    [MenuItem("CopyDllEditor/Settings")]
    public static void ShowExample()
    {
        CopyDllEditor wnd = GetWindow<CopyDllEditor>();
        wnd.titleContent = new GUIContent("CopyDllEditor");
        wnd.minSize = new Vector2(810, 540);
        wnd.maxSize = new Vector2(1910, 810);

        //wnd.position = new Rect(new Vector2(1920, 540), new Vector2(1600, 540));
    }

    public void CreateGUI()
    {
        DllFileJsonPath = $"{Application.dataPath}/Editor/CopyDll/DllFile.json";
        //初始化
        Init();
        if(dllData == null)
        {
            dllData = new CopyDllData();
            dllData.Files = new List<string>();
        }
        
        if (!File.Exists(DllFileJsonPath))
        {
            File.Create(DllFileJsonPath);
        }

        VisualElement root = rootVisualElement;
        //添加平台选择
        EnumField toType = new EnumField("选择平台");
        toType.Init(BuildTarget.StandaloneWindows64);
        //初始化平台选择
        if (!string.IsNullOrEmpty(dllData.PingTaiType))
        {
            //toType.value = (BuildTarget)System.Enum.Parse(typeof(BuildTarget), dllData.PingTaiType);
        }
        else
        {
            dllData.PingTaiType = toType.value.ToString();
        }
        //平台改变监听
        toType.RegisterCallback<ChangeEvent<string>>((evt) =>
        {
            dllData.PingTaiType = evt.newValue;
        });
        root.Add(toType);
        
        //dll原始文件所在路径输入框
        TextField formPathInput = new TextField("dll原始文件路径(无需加平台文件夹名称,末尾加\\)");
        //初始化
        if(!string.IsNullOrEmpty(dllData.FromPath))
        {
            formPathInput.value = dllData.FromPath;
        }
        //监听原始文件路径改变
        formPathInput.RegisterCallback<ChangeEvent<string>>((evt) =>
        {
            dllData.FromPath = evt.newValue;
        });
        root.Add(formPathInput);
        
        //复制到目标目录路径输入框
        TextField toPathInput = new TextField("dll保存文件路径(无需加平台文件夹名称,最好为工程Assets内路径,末尾加\\)");
        //初始化
        if (!string.IsNullOrEmpty(dllData.ToPath))
        {
            toPathInput.value = dllData.ToPath;
        }
        //监听目标路径改变
        toPathInput.RegisterCallback<ChangeEvent<string>>((evt) =>
        {
            dllData.ToPath = evt.newValue;
        });
        root.Add(toPathInput);

        //设置dll文件数量的输入框
        IntegerField filescount = new IntegerField("dll文件数量");
        //初始化
        filescount.value = dllData.Files.Count;
        root.Add(filescount);

        //滑动界面
        ScrollView scrollView = new ScrollView();
        root.Add(scrollView);
        //所有文件名称输入框
        List<TextField> dllFileField = new List<TextField>();

        //初始化文件名称输入框
        foreach (var item in dllData.Files)
        {
            TextField fileName = new TextField("dll文件名称(带后缀)");
            scrollView.Add(fileName);
            fileName.value = item;
            dllFileField.Add(fileName);
        }

        //监听文件数量变化
        filescount.RegisterCallback<ChangeEvent<int>>((evt) =>
        {
            //若资源数量增加
            if (evt.newValue > evt.previousValue)
            {
                int count = evt.newValue - evt.previousValue;
                for (int i = 0; i < count; i++)
                {
                    TextField fileName = new TextField("dll文件名称(带后缀)");
                    scrollView.Add(fileName);

                    dllFileField.Add(fileName);
                }
            }
            else
            {
                int count = evt.previousValue - evt.newValue;
                int index = evt.previousValue - 1;
                //若减少,曾从后往前删除
                for (int i = 0; i < count; i++)
                {
                    scrollView.RemoveAt(index);
                    dllFileField.RemoveAt(index);
                    index--;
                }
            }
        });

        //复制dll文件按钮
        Button copyBtn = new Button(() =>
        {
            BuildTarget v = (BuildTarget)System.Enum.Parse(typeof(BuildTarget), toType.value.ToString());

            string yuanshiPath = GetHotFixDllsOutputDirByTarget(v);
            dllData.Files.Clear();
            foreach (var item in dllFileField)
            {
                //去除未输入的和重复的
                if(!string.IsNullOrEmpty(item.value) && !dllData.Files.Contains(item.value))
                {
                    //去除文件不存在的
                    string filePath = $"{yuanshiPath}/{item.value}";
                    if(File.Exists(filePath))
                        dllData.Files.Add(item.value);
                }
            }

            //保存当前设置结果到json文件中,用于下次打开初始化
            string fileValue = JsonUtility.ToJson(dllData);
            File.WriteAllText(DllFileJsonPath, fileValue);

            
            //选择平台进行文件复制
            switch(v)
            {
                case BuildTarget.StandaloneWindows:
                    CopeByStandaloneWindows32();
                    break;
                case BuildTarget.StandaloneWindows64:
                    CopeByStandaloneWindows64();
                    break;
                case BuildTarget.Android:
                    CopeByAndroid();
                    break;
                case BuildTarget.iOS:
                    CopeByIOS();
                    break;
            }
        });
        copyBtn.text = "复制dll文件";
        root.Add(copyBtn);
    }

    private void Init()
    {
        string value = File.ReadAllText(DllFileJsonPath);
        dllData = JsonUtility.FromJson<CopyDllData>(value);
    }

    private void CopeByStandaloneWindows32()
    {
        Copy(BuildTarget.StandaloneWindows);
    }

    private void CopeByStandaloneWindows64()
    {
        Copy(BuildTarget.StandaloneWindows64);
    }

    private void CopeByAndroid()
    {
        Copy(BuildTarget.Android);
    }

    private void CopeByIOS()
    {
        Copy(BuildTarget.iOS);
    }

    private void Copy(BuildTarget target)
    {
        //复制的dll文件列表
        List<string> copyDlls = dllData.Files;
        //dll原始路径
        string outDir = GetHotFixDllsOutputDirByTarget(target);
        //目标路径
        string exportDir = GetDllToPath(target);

        if (!Directory.Exists(exportDir))
        {
            Directory.CreateDirectory(exportDir);
        }
        //复制
        foreach (var copyDll in copyDlls)
        {
            File.Copy($"{outDir}/{copyDll}", $"{exportDir}/{copyDll}.bytes", true);
        }

        //复制固定需要的依赖dll文件,路径固定
        string AssembliesPostIl2CppStripDir = Application.dataPath.Remove(Application.dataPath.Length - 6, 6) + "HybridCLRData/AssembliesPostIl2CppStrip";
        string aotDllDir = $"{AssembliesPostIl2CppStripDir}/{target}";
        foreach (var dll in aotDlls)
        {
            string dllPath = $"{aotDllDir}/{dll}";
            if (!File.Exists(dllPath))
            {
                Debug.LogError($"ab中添加AOT补充元数据dll:{dllPath} 时发生错误,文件不存在。需要构建一次主包后才能生成裁剪后的AOT dll");
                continue;
            }
            string dllBytesPath = $"{exportDir}/{dll}.bytes";
            File.Copy(dllPath, dllBytesPath, true);
        }
        AssetDatabase.Refresh();
        Debug.Log("热更Dll复制成功!");
    }

    /// <summary>
    /// 获取热更新时输出dll文件的路径
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public string GetHotFixDllsOutputDirByTarget(BuildTarget target)
    {
        string path = dllData.FromPath;
        switch (target)
        {
            case BuildTarget.StandaloneWindows:
                path += "StandaloneWindows";
                break;
            case BuildTarget.StandaloneWindows64:
                path += "StandaloneWindows64";
                break;
            case BuildTarget.Android:
                path += "Android";
                break;
            case BuildTarget.iOS:
                path += "iOS";
                break;
        }

        return path;
    }

    /// <summary>
    /// 获取复制文件目标路径
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public string GetDllToPath(BuildTarget target)
    {
        string path = dllData.ToPath;
        switch (target)
        {
            case BuildTarget.StandaloneWindows:
                path += "StandaloneWindows";
                break;
            case BuildTarget.StandaloneWindows64:
                path += "StandaloneWindows64";
                break;
            case BuildTarget.Android:
                path += "Android";
                break;
            case BuildTarget.iOS:
                path += "iOS";
                break;
        }

        return path;
    }
}

[SerializeField]
public class CopyDllData
{
    public string FromPath;
    public string ToPath;
    public string PingTaiType;
    public List<string> Files;
}

放到Editor/CopyDll文件夹下即可,打开如下

先选择平台,然后设置原始Dll文件所在路径,再设置输出路径,填入dll文件数量并设置好dll文件名+后缀,最后点击复制即可完成复制。 

5.打AB包

此处同样提供一个我简单实现的打包工具(使用UIToolkit实现),也可使用其他打包的插件。

using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System;
using Object = UnityEngine.Object;

public class AssetBundle : EditorWindow
{
    private Dictionary<string, List<Object>> bundles = new Dictionary<string, List<Object>>();

    /// <summary>
    /// ab包设置部分的滑动界面
    /// </summary>
    ScrollView abScr = null;

    [MenuItem("AssetBundle/Setting")]
    public static void ShowExample()
    {
        AssetBundle wnd = GetWindow<AssetBundle>();
        wnd.titleContent = new GUIContent("AssetBundle");
        wnd.minSize = new Vector2(810, 540);
        wnd.maxSize = new Vector2(1910, 810);

        //wnd.position = new Rect(new Vector2(1920, 540), new Vector2(1600, 540));
    }

    public void CreateGUI()
    {
        VisualElement root = rootVisualElement;

        //创建打包按钮,用于打出AB包
        Button btn_Add = new Button(() =>
        {
            //ab包
            List<AssetBundleBuild> abs = new List<AssetBundleBuild>();
            //记录当前打包的ab包信息,用于下次打开时初始化
            ABSaveJsonData saveData = new ABSaveJsonData();
            saveData.ABSave = new List<ABSaveData>();
            //遍历设置的ab包数据
            foreach (var item in bundles)
            {
                //单个ab包文件名与资源文件数据
                ABSaveData data = new ABSaveData();
                data.ABName = item.Key;
                data.ABFilePath = new List<string>();

                List<string> assets = new List<string>();
                foreach (var v in item.Value)
                {
                    if (v == null)
                        continue;
                    //获取资源路径,文件中存储路径信息
                    string filePath = AssetDatabase.GetAssetPath(v);
                    Debug.LogError(filePath);
                    if (assets.Contains(filePath))
                        continue;
                    assets.Add(filePath);
                    data.ABFilePath.Add(filePath);
                }

                AssetBundleBuild abFile = new AssetBundleBuild
                {
                    //包名
                    assetBundleName = item.Key,
                    //资源
                    assetNames = assets.ToArray(),
                };
                abs.Add(abFile);

                //添加每个ab包信息
                saveData.ABSave.Add(data);
            }

            //ab包保存位置
            string streamingAssetPathDst = $"{Application.streamingAssetsPath}";
            CreateDirIfNotExists(streamingAssetPathDst);
            BuildPipeline.BuildAssetBundles(streamingAssetPathDst, abs.ToArray(), BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
            AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);

            //ab包信息文件
            string bundleFilePath = $"{Application.dataPath}/Editor/AssetBundleEditor/ABFile.json";
            if (!File.Exists(bundleFilePath))
            {
                File.Create(bundleFilePath);
            }
            //序列化ab包信息
            string value = JsonUtility.ToJson(saveData);
            File.WriteAllText(bundleFilePath, value);
        });

        btn_Add.text = "打包";
        root.Add(btn_Add);

        CreatAddABBtn(root);
    }

    /// <summary>
    /// 创建添加ab包名的按钮
    /// </summary>
    /// <param name="root"></param>
    private void CreatAddABBtn(VisualElement root)
    {
        abScr = new ScrollView();
        abScr.style.width = rootVisualElement.style.width;
        abScr.style.height = rootVisualElement.style.height;

        Button btn_Add = new Button(() =>
        {
            VisualElement abVi = CreataABNameField();
            abScr.Add(abVi);
        });
        btn_Add.text = "添加ab包名称";
        root.Add(btn_Add);
        root.Add(abScr);

        OnInitBundles(abScr);
    }

    /// <summary>
    /// 初始化上次设置的资源数据
    /// </summary>
    /// <param name="root"></param>
    private void OnInitBundles(VisualElement root)
    {
        string bundleFilePath = $"{Application.dataPath}/Editor/AssetBundleEditor/ABFile.json";
        //反序列化文件数据
        string value = File.ReadAllText(bundleFilePath);
        ABSaveJsonData data = JsonUtility.FromJson<ABSaveJsonData>(value);

        foreach (var item in data.ABSave)
        {
            //初始化bundles
            if (!bundles.ContainsKey(item.ABName))
            {
                bundles.Add(item.ABName, new List<Object>());

                foreach (var path in item.ABFilePath)
                {
                    //通过资源路径获取到资源文件
                    bundles[item.ABName].Add(AssetDatabase.LoadAssetAtPath(path, typeof(Object)));
                }
            }
        }

        foreach (var item in bundles)
        {
            //初始化编辑器界面
            VisualElement abVi = CreataABNameField(item.Key, item.Value);
            root.Add(abVi);
        }
    }

    /// <summary>
    /// 创建ab包名称的输入框
    /// </summary>
    /// <param name="root"></param>
    /// <param name="defaultValue">初始包名</param>
    /// <param name="objects">初始资源</param>
    private VisualElement CreataABNameField(string defaultValue = "", List<Object> objects = null)
    {
        VisualElement abVi = new VisualElement();
        TextField field = new TextField("输入ab包名称");
        field.style.width = 610;
        abVi.Add(field);
        //监听内容修改
        field.RegisterCallback<ChangeEvent<string>>((evt) =>
        {
            //修改bundles
            if (bundles.ContainsKey(evt.previousValue))
            {
                bundles.Remove(evt.previousValue);
            }

            if(!bundles.ContainsKey(evt.newValue))
                bundles.Add(evt.newValue, new List<Object>());
        });

        //初始化包名
        if (string.IsNullOrEmpty(defaultValue))
            field.value = $"Default_{bundles.Count}";
        else
            field.value = defaultValue;

        CreateABCountField(abVi, field, objects);

        return abVi;
    }

    /// <summary>
    /// 创建ab包资源数量的输入框
    /// </summary>
    /// <param name="abVi"></param>
    /// <param name="field">用于设置bundles的key值</param>
    /// <param name="objects">初始资源对象</param>
    private void CreateABCountField(VisualElement abVi, TextField field, List<Object> objects = null)
    {
        //资源数量输入框
        IntegerField field_Count = new IntegerField("输入ab资源数量");
        field_Count.style.width = 200;
        field.Add(field_Count);

        Button delBtn = new Button(() =>
        {
            if(bundles.ContainsKey(field.value))
            {
                bundles.Remove(field.value);
            }
            abScr.Remove(abVi);
        });
        delBtn.style.width = 60;
        delBtn.text = "删除ab包";
        field.Add(delBtn);

        VisualElement objVisE = new VisualElement();
        objVisE.style.width = rootVisualElement.style.width;
        //objVisE.style.maxHeight = 100;

        //初始化资源对象
        if (objects != null)
        {
            //初始化数量
            field_Count.value = objects.Count;
            for (int i = 0; i < objects.Count; i++)
            {
                VisualElement objField = CreataABFile(field, objects[i]);
                objVisE.Add(objField);
            }
        }

        //监听数量修改
        field_Count.RegisterCallback<ChangeEvent<int>>((evt) =>
        {
            //若资源数量增加
            if(evt.newValue > evt.previousValue)
            {
                int count = evt.newValue - evt.previousValue;
                for (int i = 0; i < count; i++)
                {
                    VisualElement objField = CreataABFile(field);
                    objVisE.Add(objField);
                }
            }
            else
            {
                int count = evt.previousValue - evt.newValue;
                int index = evt.previousValue - 1;
                //若减少,曾从后往前删除
                for (int i = 0; i < count; i++)
                {
                    objVisE.RemoveAt(index);

                    if (bundles.ContainsKey(field.value) && bundles[field.value].Count > index)
                    {
                        bundles[field.value].RemoveAt(index);
                    }

                    index--;
                }
            }
            
        });
        abVi.Add(objVisE);
    }

    /// <summary>
    /// 创建ab包资源的输入框
    /// </summary>
    /// <param name="root"></param>
    /// <param name="field">用于设置bundles的key值</param>
    /// <param name="obj">初始资源对象</param>
    /// <returns></returns>
    private VisualElement CreataABFile(TextField field, Object obj = null)
    {
        //资源设置框
        ObjectField objField = new ObjectField();
        objField.objectType = typeof(Object);

        //初始化对象内容
        if(obj != null)
            objField.value = obj;

        //监听资源对象改变
        objField.RegisterCallback<ChangeEvent<Object>>((evt) =>
        {
            if (bundles.ContainsKey(field.value))
            {
                var objs = bundles[field.value];
                objs.Remove(evt.previousValue);
                objs.Add(evt.newValue);
            }
        });

        return objField;
    }

    //创建文件夹
    private static void CreateDirIfNotExists(string dirName)
    {
        if (!Directory.Exists(dirName))
        {
            Directory.CreateDirectory(dirName);
        }
    }
}

[Serializable]
public class ABSaveData
{
    [SerializeField]
    public string ABName;
    [SerializeField]
    public List<string> ABFilePath;
}

[Serializable]
public class ABSaveJsonData
{
    [SerializeField]
    public List<ABSaveData> ABSave;
}

放到Editor/AssetBundleEditor文件夹下即可,界面如图

点击添加ab包名称即可添加一个ab包设置,输入ab包名称及资源数量,设置资源对象最后点击打包即可,ab包输出在StreamingAssets文件夹下。

至此一个简单的热更新就实现了,最后Build工程(Build时只需要Build Init场景即可,无需勾选Main场景等AB包中的场景,当然在编辑器中运行时,需要勾选上其他场景,否则无法跳转),然后修改UIPart中的部分代码,之后依次执行生成dll,复制dll,打ab包,最后将StreamingAssets下的ab包替换到Build的工程中运行,就会发现修改的代码生效了。

下面为我实现的演示工程,地址为:Huatuo热更新演示工程资源-CSDN文库


http://www.kler.cn/a/553084.html

相关文章:

  • 优先队列(典型算法思想)—— OJ例题算法解析思路
  • 打破限制!自定义 Hooks 如何提升 React 组件的灵活性
  • 用户坐标系(ucs)与系统坐标系(wcs)的转换详解——CAD c#二次开发
  • 【AI工程实践】阅文集团:NLP在网络文学领域的应用
  • Java Spring boot 篇:常用注解
  • 数智驱动:医学编程与建模技术在智慧医院AI建设中的创新与变革
  • floodfill算法系列一>衣橱整理
  • 4.7 模型训练基类Trainer:Hugging Face工业级训练引擎深度剖析
  • 安装mmdet3d报错【fatal error: spconv/maxpool.h: No such file or directory】
  • `fi` 是 Bash 脚本中用来结束 `if` 条件语句块的关键字
  • firefox升级后如何恢复收藏夹和密码的问题
  • SPO(Self-Supervised Prompt Optimization)自我监督Prompt提示优化的全景指南
  • 机器人路径规划 | 基于极光PLO优化算法的机器人三维路径规划Matlab代码
  • 【嵌入式Linux应用开发基础】特殊进程
  • 机试刷题_矩阵的最小路径和【python】
  • 7.【线性代数】——求解Ax=0,主列和自由列
  • Spring Cloud环境搭建
  • 【数据结构】队列(Queue)
  • 前端需要学习 Docker 吗?
  • 【Elasticsearch】近实时搜索与刷新机制