Unity C#熱更新方案 ILRuntime學習筆記
一、主工程調用Hotfix代碼
假設Hotfix工程里有一個Test類,該如何調用該類的方法呢?
namespace Hotfix { public class Test { // 實例方法 public string GetName() { return "test"; } // 靜態方法 public static float Sum(float a, float b) { return a + b; } } }
1.調用靜態方法
// 獲取類型 IType type = appdomain.LoadedTypes["Hotfix.Test"]; // 獲取方法 IMethod method = type.GetMethod("Sum", 2); // 調用方法 object returnValue = appdomain.Invoke(method, null, 1, 2); // 輸出返回值 print("靜態方法返回值:" + returnValue);
2.調用實例方法
// 獲取類型 IType type = appdomain.LoadedTypes["Hotfix.Test"]; // 創建實例 object instance = (type as ILType).Instantiate(); // 獲取方法 IMethod method = type.GetMethod("GetName", 0); // 調用方法 object returnValue = appdomain.Invoke(method, instance); // 輸出返回值 print("靜態方法返回值:" + returnValue);
二、Hotfix調用主工程代碼
Hotfix調用主工程代碼直接調用即可,無需特別步驟。
namespace Hotfix { using UnityEngine; public class Test { // 調用主工程方法 private void CallUnity() { Debug.Log(Application.streamingAssetsPath); } } }
三、Hotfix響應MonoBehaviour事件
Hotfix響應MonoBehaviour中的事件,可以用代理方法實現。
1.在Unity中新建一個Mono腳本,實現自己需要的接口,在這些接口被調用時,調用代理事件
namespace GameUtils.Triggers { using System; using UnityEngine; /// <summary> /// <para>MonoBehaviour 基本事件觸發器 觸發以下事件</para> /// OnEnable、Start、OnDisable、OnDestroy /// </summary> public class MonoBehaviourEventTrigger : MonoBehaviour { public Action onEnable; public Action start; public Action update public Action onDisable; public Action onDestroy; private void OnEnable() { if (onEnable != null) onEnable.Invoke(); } private void Start() { if (start != null) start.Invoke(); } private void OnDisable() { if (onDisable != null) onDisable.Invoke(); } private void Update() { if (update != null) update.Invoke(); } private void OnDestroy() { if (onDestroy != null) onDestroy.Invoke(); } } }
2.在Hotfix工程中這樣調用:
namespace Hotfix { using UnityEngine; using GameUtils.Triggers; public class Test { private void MonoTest() { GameObject obj = new GameObject("Test"); MonoBehaviourEventTrigger monoTrigger = obj.AddComponent<MonoBehaviourEventTrigger>(); monoTrigger.start = Start; monoTrigger.update = Update; monoTrigger.onDestroy = OnDestroy; } private void Start() { Debug.Log("Start"); } private void Update() { // 鼠標按下時,做射線碰撞檢測 if (Input.GetMouseButtonDown(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit, 1000)) { Debug.Log(hit.point); } } } private void OnDestroy() { Debug.Log("OnDestroy"); } } }
注:由於Mono接口是主工程中的,不能熱更,所以要提前寫好所有接口以供熱更代碼調用。Mono中的接口眾多,如果全部用一個類實現的話太過冗余,我這里做了一下優化,把不同功能的接口分組到不同的腳本中,由一個統一的Mono根據調用情況動態添加,在set里實現調用時的動態添加腳本功能,使用時還是一樣方便,代碼太長就不貼了。
3.帶參數的Action
注意,帶參數的Action在跨域調用前要在主工程里注冊參數。
否則調用會報錯。
private void RegistDelegate() { DelegateManager manager = appdomain.DelegateManager; manager.RegisterMethodDelegate<bool>(); manager.RegisterMethodDelegate<byte>(); manager.RegisterMethodDelegate<sbyte>(); manager.RegisterMethodDelegate<char>(); manager.RegisterMethodDelegate<short>(); manager.RegisterMethodDelegate<ushort>(); manager.RegisterMethodDelegate<int>(); manager.RegisterMethodDelegate<uint>(); manager.RegisterMethodDelegate<long>(); manager.RegisterMethodDelegate<ulong>(); manager.RegisterMethodDelegate<float>(); manager.RegisterMethodDelegate<double>(); manager.RegisterMethodDelegate<string>(); manager.RegisterMethodDelegate<object>(); manager.RegisterMethodDelegate<Collider>(); manager.RegisterMethodDelegate<Collision>(); manager.RegisterMethodDelegate<BaseEventData>(); manager.RegisterMethodDelegate<PointerEventData>(); manager.RegisterMethodDelegate<Object>(); manager.RegisterMethodDelegate<GameObject>(); appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((action) => { return new UnityEngine.Events.UnityAction(() => { ((System.Action)action)(); }); }); }
四、跨域調用性能優化
跨域調用的性能是很差的。性能優化的方式如下:
1.主工程調用Hotfix時,多用type.GetMethod()去調用。
// 1.用string調用方法: appdomain.Invoke("Hotfix.Test", "Sum", null, null); // 2.用Method調用方法性能更好 IType type = appdomain.LoadedTypes["Hotfix.Test"]; IMethod method = type.GetMethod("Sum", 2); appdomain.Invoke(method, instance, 1, 2);
2.Hotfix調用主工程代碼時,可以用CLRBinding優化。
官方提供了一個工具,可以實現Hotfix調用的優化,該工具會自動分析熱更dll中調用的方法,自動生成CLRBinding類。
使用方式是:在unity頂部菜單中選擇ILRuntime > Generate CLR Binding Code by Analysis
當然,使用前要配置你熱更dll的路徑和生成文件的路徑。
在Unity查找ILRuntimeCLRBinding這個類,修改其中路徑。
#if UNITY_EDITOR using UnityEditor; using UnityEngine; using System; using System.Text; using System.Collections.Generic; using ILRuntimeDemo; [System.Reflection.Obfuscation(Exclude = true)] public class ILRuntimeCLRBinding { [MenuItem("ILRuntime/Generate CLR Binding Code by Analysis")] static void GenerateCLRBindingByAnalysis() { //用新的分析熱更dll調用引用來生成綁定代碼 ILRuntime.Runtime.Enviorment.AppDomain domain = new ILRuntime.Runtime.Enviorment.AppDomain(); using (System.IO.FileStream fs = new System.IO.FileStream("Assets/StreamingAssets/Hotfix.dll", System.IO.FileMode.Open, System.IO.FileAccess.Read)) { domain.LoadAssembly(fs); //Crossbind Adapter is needed to generate the correct binding code InitILRuntime(domain); ILRuntime.Runtime.CLRBinding.BindingCodeGenerator.GenerateBindingCode(domain, "Assets/Game/ILRuntime/Generated"); } AssetDatabase.Refresh(); } static void InitILRuntime(ILRuntime.Runtime.Enviorment.AppDomain domain) { //這里需要注冊所有熱更DLL中用到的跨域繼承Adapter,否則無法正確抓取引用 domain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter()); domain.RegisterCrossBindingAdaptor(new CoroutineAdapter()); domain.RegisterCrossBindingAdaptor(new TestClassBaseAdapter()); domain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder()); } } #endif
總結:
主工程調用Hotfix代碼時比較麻煩,要用類似反射的形式。
Hotfix調用主工程代碼很容易,正常怎么寫就怎么寫。
Hotfix調用MonoBehaviour的接口可以用代理的方式。
調用時要注意性能問題,可以進行優化。
轉發自: https://segmentfault.com/a/1190000023290547 感謝冰封百度大佬