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