在安装完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-UIPartLoadDll("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_EDITORStartCoroutine(OnLoadDll(dllName, callBack));
#elsevar 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){//加载dllTextAsset 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_EDITORStartCoroutine(OnLoadScene(sceneName, callBack));
#elseStartCoroutine(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_EDITORstring[] paths = prefabPath.Split('/');string prefabName = paths[paths.Length - 1];StartCoroutine(OnLoadPrefab(prefabName, callBack));
#elseprefabPath += ".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){//初始化bundlesif (!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) =>{//修改bundlesif (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}";elsefield.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文库