注意
尽量不要在热更工程中使用继承MonoBehavior的类,MonoBehaviour需要适配大量的方法,而有些方法可能需要其他关于Unity底层的适配,非常复杂。
本节只展示基本的和可能会常用到的MonoBehaviour方法适配,如Awake、Update等
MonoBehaviour适配器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 using ILRuntime.CLR.Method;using ILRuntime.Runtime.Enviorment;using ILRuntime.Runtime.Intepreter;using System.Collections;using System.Collections.Generic;using UnityEngine;public class MonoBehaviourAdaptor : CrossBindingAdaptor { public override System.Type BaseCLRType => typeof (MonoBehaviour); public override System.Type AdaptorType => typeof (Adaptor); public override object CreateCLRInstance (AppDomain appdomain, ILTypeInstance instance ) { return new Adaptor(appdomain, instance); } public class Adaptor : MonoBehaviour , CrossBindingAdaptorType { AppDomain m_AppDomain; ILTypeInstance m_Instance; IMethod m_AwakeMethod; IMethod m_StartMethod; IMethod m_UpdateMethod; IMethod m_ToStringMethod; public Adaptor () { } public Adaptor (AppDomain appdomain, ILTypeInstance instance ) { m_AppDomain = appdomain; m_Instance = instance; } public ILTypeInstance ILInstance { get { return m_Instance; } set { m_Instance = value ; m_AwakeMethod = null ; m_StartMethod = null ; m_UpdateMethod = null ; m_ToStringMethod = null ; } } public AppDomain AppDomain { get { return m_AppDomain; } set { m_AppDomain = value ; } } public void Awake () { if (m_Instance != null ) { if (m_AwakeMethod == null ) { m_AwakeMethod = m_Instance.Type.GetMethod("Awake" ,0 ); } if (m_AwakeMethod != null ) { m_AppDomain.Invoke(m_AwakeMethod,m_Instance,null ); } } } public void Start () { if (m_StartMethod == null ) { m_StartMethod = m_Instance.Type.GetMethod("Start" , 0 ); } if (m_StartMethod != null ) { m_AppDomain.Invoke(m_StartMethod, m_Instance, null ); } } public void Update () { if (m_UpdateMethod == null ) { m_UpdateMethod = m_Instance.Type.GetMethod("Update" , 0 ); } if (m_UpdateMethod != null ) { m_AppDomain.Invoke(m_UpdateMethod, m_Instance, null ); } } public override string ToString () { if (m_ToStringMethod == null ) { m_ToStringMethod = m_AppDomain.ObjectType.GetMethod("ToString" , 0 ); } IMethod m = m_Instance.Type.GetVirtualMethod(m_ToStringMethod); if (m != null || m is ILMethod) { return m_Instance.ToString(); } else { return m_Instance.Type.FullName; } } } }
在ILRuntimeCLRBinding
和ILRuntimeManager
中注册适配器
1 AppDomain.RegisterCrossBindingAdaptor(new MonoBehaviourAdaptor());
注册完后生成一下CLR绑定的代码
AddComponent重定向 热更代码中写的Mono脚本想要发挥作用,就需要在运行时将自己挂载上去。此时就需要调用AddComponent
方法,但是热更工程里面的类主工程并没有,这时就需要CLR重定向,重定向AddComponent
方法,在添加热更工程的类时,添加我们上面的适配器。
在ILRuntimeManager
中,添加AddComponent
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 private unsafe StackObject* AddComponent(ILIntepreter __intp, StackObject* __esp, IList<object > __mStack, CLRMethod __method, bool isNewObj) { ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain; StackObject* ptr = ILIntepreter.Minus(__esp, 1 ); GameObject instance = StackObject.ToObject(ptr,__domain, __mStack) as GameObject; if (instance == null ) { throw new System.NullReferenceException(); } __intp.Free(ptr); var genericArguments = __method.GenericArguments; if (genericArguments != null && genericArguments.Length == 1 ) { var type = genericArguments[0 ]; object res; if (type is CLRType) { res = instance.AddComponent(type.TypeForCLR); } else { var ilInstance = new ILTypeInstance(type as ILType,false ); var clrInstance = instance.AddComponent<MonoBehaviourAdaptor.Adaptor>(); clrInstance.ILInstance = ilInstance; clrInstance.AppDomain = __domain; ilInstance.CLRInstance = clrInstance; res = clrInstance.ILInstance; clrInstance.Awake(); } return ILIntepreter.PushObject(ptr, __mStack, res); } return __esp; }
GetComponent重定向 基本上和AddComponent
同理,但是GetComponent
在调用时不创建新的ILTypeInstance
,而是需要遍历GameObject,找到符合要求的MonoBehaviourAdaptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private unsafe StackObject* GetComponent(ILIntepreter __intp, StackObject* __esp, IList<object > __mStack, CLRMethod __method, bool isNewObj) { ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain; StackObject* ptr = ILIntepreter.Minus(__esp, 1 ); GameObject instance = StackObject.ToObject(ptr,__domain,__mStack) as GameObject; if (instance == null ) { throw new System.NullReferenceException(); } __intp.Free(ptr); var genericArguments = __method.GenericArguments; if (genericArguments != null && genericArguments.Length == 1 ) { var type = genericArguments[0 ]; object res = null ; if (type is CLRType) { res = instance.GetComponent(type.TypeForCLR); } else { var clrInstances = instance.GetComponents<MonoBehaviourAdaptor.Adaptor>(); foreach (var clrInstance in clrInstances) { if (clrInstance.ILInstance != null ) { if (clrInstance.ILInstance.Type == type) { res = clrInstance.ILInstance; break ; } } } } return ILIntepreter.PushObject(ptr, __mStack, res); } return __esp; }
重定向准备 在游戏开始前,需要在ILRuntime初始化时进行方法的重定向。
在ILRuntimeManager
里面添加SetUpCLRRedirection
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 unsafe void SetUpCLRRedirection () { var arr = typeof (GameObject).GetMethods(); foreach (var method in arr) { if (method.Name == "AddComponent" && method.GetGenericArguments().Length == 1 ) { m_AppDomain.RegisterCLRMethodRedirection(method, AddComponent); } if (method.Name == "GetComponent" && method.GetGenericArguments().Length == 1 ) { m_AppDomain.RegisterCLRMethodRedirection(method, GetComponent); } } }
然后再在ILRuntimeManager
的InitializeILRuntime
方法内调用
1 2 3 4 5 6 7 8 void InitializeILRuntime () { SetUpCLRRedirection(); ILRuntime.Runtime.Generated.CLRBindings.Initialize(m_AppDomain); }
测试 在HotFix工程新建TestMono
文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 using UnityEngine;namespace HotFix { public class TestMono { public static void RunTest (GameObject go ) { go.AddComponent<MonoTest>(); MonoTest monoTest = go.GetComponent<MonoTest>(); monoTest.Test(); } public class MonoTest : MonoBehaviour { private float m_CurTime = 0 ; void Awake () { Debug.Log("MonoTest Awake!" ); } void Start () { Debug.Log("MonoTest Start" ); } void Update () { if (m_CurTime < 0.2f ) { m_CurTime += Time.deltaTime; Debug.Log("MonoTest Update" ); } } public void Test () { Debug.Log("MonoTest TestMethod" ); } } } }
生成dll库,并在ILRuntimeManager
中更新热更文件。最后运行便可查看