在安裝完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文庫