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-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文庫

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/70508.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/70508.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/70508.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Springboot + Ollama + IDEA + DeepSeek 搭建本地deepseek簡單調用示例

1. 版本說明 springboot 版本 3.3.8 Java 版本 17 spring-ai 版本 1.0.0-M5 deepseek 模型 deepseek-r1:7b 需要注意一下Ollama的使用版本&#xff1a; 2. springboot項目搭建 可以集成在自己的項目里&#xff0c;也可以到 spring.io 生成一個項目 生成的話&#xff0c;如下…

如何在 macOS 上配置 MySQL 環境變量

如何在 macOS 上配置 MySQL 環境變量 步驟 1: 查找 MySQL 安裝路徑 打開終端&#xff0c;使用以下命令查找 mysql 的可執行文件路徑&#xff1a; which mysql如果該命令沒有返回結果&#xff0c;可以使用 find 命令&#xff1a; sudo find / -name "mysql" 2>/de…

Unity Excel導表工具轉Lua文件

思路介紹 借助EPPlus讀取Excel文件中的配置數據&#xff0c;根據指定的不同類型的數據配置規則來解析成對應的代碼文本&#xff0c;將解析出的字符串內容寫入到XXX.lua.txt文件中即可 EPPlus常用API //命名空間 using OfficeOpenXml;//Excel文件路徑 var fileExcel new File…

【vue項目中如何實現一段文字跑馬燈效果】

在Vue項目中實現一段文字跑馬燈效果&#xff0c;可以通過多種方式實現&#xff0c;以下是幾種常見的方法&#xff1a; 方法一&#xff1a;使用CSS動畫和Vue數據綁定 這種方法通過CSS動畫實現文字的滾動效果&#xff0c;并結合Vue的數據綁定動態更新文本內容。 步驟&#xff…

AcWing走迷宮-最短路問題-BFS求解

題目描述 給定一個 n * m 的二維整數數組&#xff0c;用來表示一個迷宮&#xff0c;數組中只包含 0 或 1&#xff0c;其中 0 表示可以走的路&#xff0c;1 表示不可通過的墻壁。 最初&#xff0c;有一個人位于左上角 (1, 1) 處&#xff0c;已知該人每次可以向上、下、左、右任…

go 錯誤處理 error

普通錯誤處理 // 包路徑 package mainimport ("errors""fmt" )func sqrt(f1, f2 float64) (float64, error) {if f2 < 0 {return 0, errors.New("error: f2 < 0")}return f1 / f2, nil }func sqrt1(f1, f2 float64) {if re, err : sqrt(f…

MCU Bootloader具備什么條件才能跳轉到APP程序

在MCU系統中&#xff0c;BootLoader&#xff08;Boot&#xff09;跳轉到應用程序&#xff08;APP&#xff09;的條件通常由硬件和軟件協同控制&#xff0c;核心邏輯是確保APP的完整性和合法性。以下是關鍵條件及流程&#xff1a; 1. 硬件啟動模式選擇 BOOT引腳電平&#xff1a…

LeeCode題庫第二十八題

28.找出字符串第一個匹配項的下標 項目場景&#xff1a; 給你兩個字符串 haystack 和 needle &#xff0c;請你在 haystack 字符串中找出 needle 字符串的第一個匹配項的下標&#xff08;下標從 0 開始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;則返回 …

深入解析BFS算法:C++實現無權圖最短路徑的高效解決方案

在無權圖中&#xff0c;廣度優先搜索&#xff08;BFS&#xff09;是解決最短路徑問題的高效算法。接下來博主從專業角度深入探討其實現細節&#xff0c;并給出C代碼示例&#xff1a; 目錄 一、核心原理 二、算法步驟 三、C實現關鍵點 1. 數據結構 2. 邊界檢查 3. 路徑回溯…

Plant Simulation培訓教程-雙深堆垛機立庫仿真模塊

原創 知行 天理智能科技 2025年01月03日 17:02 浙江 又到年終盤點的時候了&#xff0c;在這里我把之前錄制的Plant Simulation培訓教程-雙深堆垛機立庫仿真模塊分享出來&#xff0c;有需要的可以直接聯系我。 雙深堆垛機立庫仿真模塊基于單深模塊開發&#xff0c;適用于雙深堆…

文本和語音互轉

目錄 1. 下載依賴ddl 2. 引入Pom依賴 3. java代碼 二. 語音轉文本 1. 下載中文語音轉文本的模型 2. 引入pom依賴 3. java代碼 4. 運行效果 1. 下載依賴ddl 文字轉語音文件需要使用jacob的dll文件放在jdk安裝目錄下的bin文件夾下 點擊官網下載錄或者通過csdn下載 2. …

DeepSeek破局啟示錄:一場算法優化對算力霸權的降維打擊

導言 2024年,中國AI大模型賽道殺出一匹黑馬——深度求索(DeepSeek)。從數學推理能力超越GPT-4,到API價格僅為Claude 3.5的1/53,再到開源生態的快速擴張,DeepSeek的崛起不僅打破了“算力霸權”的固有認知,更揭示了AI行業底層邏輯的深刻變革。這場技術革命背后,隱藏著技術…

Python大數據可視化:基于python大數據的電腦硬件推薦系統_flask+Hadoop+spider

開發語言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7數據庫&#xff1a;mysql 5.7數據庫工具&#xff1a;Navicat11開發軟件&#xff1a;PyCharm 系統展示 管理員登錄 管理員功能界面 價格區間界面 用戶信息界面 品牌管理 筆記本管理 電腦主機…

阿里云虛機的遠程桌面登錄提示帳戶被鎖定了

提示由于安全原因&#xff0c;帳戶被鎖定。 阿里云虛機ECS的遠程桌面登錄提示帳戶被鎖定了&#xff0c;只能登錄阿里云處理 阿里云-計算&#xff0c;為了無法計算的價值 需選擇通過VNC連接 然后計算機管理&#xff0c;解除帳戶鎖定即可。

Grok 使用指南

文章來源&#xff1a;Grok 漫游指南 | xAI Docs 歡迎&#xff01;在本指南中&#xff0c;我們將引導您了解使用 xAI API 的基礎知識。 #第 1 步&#xff1a;創建 xAI 帳戶 您需要一個 xAI 帳戶才能訪問 xAI API。在此處注冊帳戶。 創建賬戶后&#xff0c;您需要為其加載積分…

Node.js高頻面試題精選及參考答案

目錄 什么是 Node.js?它的主要特點有哪些? Node.js 的事件驅動和非阻塞 I/O 模型是如何工作的? 為什么 Node.js 適合處理高并發場景? Node.js 與傳統后端語言(如 Java、Python)相比,有哪些優勢和劣勢? 簡述 Node.js 的運行原理,包括 V8 引擎的作用。 什么是 Nod…

Servlet概述(Ⅰ)

目錄 一、Servlet概述 演示 創建JavaWeb項目&#xff08;2017版本為例&#xff09; 1. 打開 IntelliJ IDEA 2. 選擇項目類型 3. 配置框架 二、Servlet初識(熟練) 1.servlet說明 2.Servlet 接口方法 3.創建Servlet 4.JavaWeb請求響應流程 ?編輯 ?編輯 5.servlet…

Windows 小記 18 —— 子窗口繼承父窗口的樣式

子窗口會繼承父窗口或者所有者窗口的一些樣式。 當我們使用 CreateWindowExW 創建窗口后&#xff0c;指定其 HwndParent 參數時&#xff0c;或者通過 SetWindowLongPtr(vd->Hwnd, GWLP_HWNDPARENT, (LONG_PTR)vd->HwndParent); 指定所有者窗口時&#xff0c;子窗口將從父…

19、《Springboot+MongoDB整合:玩轉文檔型數據庫》

SpringbootMongoDB整合&#xff1a;玩轉文檔型數據庫 摘要&#xff1a;本文全面講解Spring Boot與MongoDB的整合實踐&#xff0c;涵蓋環境搭建、CRUD操作、聚合查詢、事務管理、性能優化等核心內容。通過15個典型代碼示例&#xff0c;演示如何高效操作文檔數據庫&#xff0c;深…

跳躍游戲II(力扣45)

這道題在跳躍游戲(力扣55)-CSDN博客 的基礎上需要找到最小的跳躍次數。那么我們需要用一個變量來統計跳躍次數&#xff0c;而難點就在于何時讓該變量的值增加。這一點我寫在注釋中&#xff0c;大家結合我的代碼會更好理解。其他部分跟跳躍游戲(力扣55)-CSDN博客 幾乎相同&#…