Huatuo热更新--如何使用
- 开源代码
- 2025-08-27 17:00:02

在安装完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文库
Huatuo热更新--如何使用由讯客互联开源代码栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Huatuo热更新--如何使用”