官方示例下载地址1: https://github.com/Ourpalm/ILRuntime
官方示例下载地址2 : https://github.com/Ourpalm/ILRuntimeU3D
官方文档地址 : https://ourpalm.github.io/ILRuntime/public/v1/guide/tutorial.html
此案例向我们展示了在unity主工程中如何调用热更dll中的协程,类似于ILRuntime热更案例学习(二) ------ Invocation/Delegate/Inheritance/错误提醒文中Inheritance的使用
1.创建协程的继承适配器
public class CoroutineAdapter : CrossBindingAdaptor
{
public override Type BaseCLRType
{
get
{
return null;
}
}
public override Type[] BaseCLRTypes
{
get
{
//跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,对于coroutine来说是IEnumerator
2.注册适配器
appdomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
3.调用
unsafe void OnHotFixLoaded()
{
appdomain.Invoke("HotFix_Project.TestCoroutine", "RunTest", null, null);
}
public void DoCoroutine(IEnumerator coroutine)
{
StartCoroutine(coroutine);
}
热更dll :
public static void RunTest()
{
CoroutineDemo.Instance.DoCoroutine(Coroutine());
}
static System.Collections.IEnumerator Coroutine()
{
Debug.Log("开始协程,t=" + Time.time);
yield return new WaitForSeconds(3);
Debug.Log("等待了3秒,t=" + Time.time);
}
MonoBehaviour官方并不推荐我们跨域使用,因为即便能做到使用,要完全支持MonoBehaviour的所有特性,会需要很多额外的工作量,而且通过MonoBehaviour做游戏逻辑当项目规模大到一定程度之后会是个噩梦,因此应该尽量避免,官方提供的案例里面只实现了AddComponent及GetComponent
1.创建MonoBehaviour适配器,实现基本流程以及tostring()方法
public class MonoBehaviourAdapter : CrossBindingAdaptor
{
public override Type BaseCLRType
{
get
{
return typeof(MonoBehaviour);
}
}
public override Type AdaptorType
{
get
{
return typeof(Adaptor);
}
}
public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
return new Adaptor(appdomain, instance);
}
//为了完整实现MonoBehaviour的所有特性,这个Adapter还得扩展,这里只抛砖引玉,只实现了最常用的Awake, Start和Update
public class Adaptor : MonoBehaviour, CrossBindingAdaptorType
{
ILTypeInstance instance;
ILRuntime.Runtime.Enviorment.AppDomain appdomain;
public Adaptor()
{
}
public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
this.appdomain = appdomain;
this.instance = instance;
}
public ILTypeInstance ILInstance { get { return instance; } set { instance = value; } }
public ILRuntime.Runtime.Enviorment.AppDomain AppDomain { get { return appdomain; } set { appdomain = value; } }
IMethod mAwakeMethod;
bool mAwakeMethodGot;
public void Awake()
{
//Unity会在ILRuntime准备好这个实例前调用Awake,所以这里暂时先不掉用
if (instance != null)
{
if (!mAwakeMethodGot)
{
mAwakeMethod = instance.Type.GetMethod("Awake", 0);
mAwakeMethodGot = true;
}
if (mAwakeMethod != null)
{
appdomain.Invoke(mAwakeMethod, instance, null);
}
}
}
IMethod mStartMethod;
bool mStartMethodGot;
void Start()
{
if (!mStartMethodGot)
{
mStartMethod = instance.Type.GetMethod("Start", 0);
mStartMethodGot = true;
}
if (mStartMethod != null)
{
appdomain.Invoke(mStartMethod, instance, null);
}
}
IMethod mUpdateMethod;
bool mUpdateMethodGot;
void Update()
{
if (!mUpdateMethodGot)
{
mUpdateMethod = instance.Type.GetMethod("Update", 0);
mUpdateMethodGot = true;
}
if (mStartMethod != null)
{
appdomain.Invoke(mUpdateMethod, instance, null);
}
}
public override string ToString()
{
IMethod m = appdomain.ObjectType.GetMethod("ToString", 0);
m = instance.Type.GetVirtualMethod(m);
if (m == null || m is ILMethod)
{
return instance.ToString();
}
else
return instance.Type.FullName;
}
}
}
2.注册并重定向方法
unsafe void SetupCLRRedirection()
{
//这里面的通常应该写在InitializeILRuntime,这里为了演示写这里
var arr = typeof(GameObject).GetMethods();
foreach (var i in arr)
{
if (i.Name == "AddComponent" && i.GetGenericArguments().Length == 1)
{
appdomain.RegisterCLRMethodRedirection(i, AddComponent);
}
}
}
unsafe void SetupCLRRedirection2()
{
//这里面的通常应该写在InitializeILRuntime,这里为了演示写这里
var arr = typeof(GameObject).GetMethods();
foreach (var i in arr)
{
if (i.Name == "GetComponent" && i.GetGenericArguments().Length == 1)
{
appdomain.RegisterCLRMethodRedirection(i, GetComponent);
}
}
}
MonoBehaviourAdapter.Adaptor GetComponent(ILType type)
{
var arr = GetComponents();
for(int i = 0; i < arr.Length; i++)
{
var instance = arr[i];
if(instance.ILInstance != null && instance.ILInstance.Type == type)
{
return instance;
}
}
return null;
}
unsafe static StackObject* AddComponent(ILIntepreter __intp, StackObject* __esp, IList __mStack, CLRMethod __method, bool isNewObj)
{
//CLR重定向的说明请看相关文档和教程,这里不多做解释
ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
var ptr = __esp - 1;
//成员方法的第一个参数为this
GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;
if (instance == null)
throw new System.NullReferenceException();
__intp.Free(ptr);
var genericArgument = __method.GenericArguments;
//AddComponent应该有且只有1个泛型参数
if (genericArgument != null && genericArgument.Length == 1)
{
var type = genericArgument[0];
object res;
if(type is CLRType)
{
//Unity主工程的类不需要任何特殊处理,直接调用Unity接口
res = instance.AddComponent(type.TypeForCLR);
}
else
{
//热更DLL内的类型比较麻烦。首先我们得自己手动创建实例
var ilInstance = new ILTypeInstance(type as ILType, false);//手动创建实例是因为默认方式会new MonoBehaviour,这在Unity里不允许
//接下来创建Adapter实例
var clrInstance = instance.AddComponent();
//unity创建的实例并没有热更DLL里面的实例,所以需要手动赋值
clrInstance.ILInstance = ilInstance;
clrInstance.AppDomain = __domain;
//这个实例默认创建的CLRInstance不是通过AddComponent出来的有效实例,所以得手动替换
ilInstance.CLRInstance = clrInstance;
res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance
clrInstance.Awake();//因为Unity调用这个方法时还没准备好所以这里补调一次
}
return ILIntepreter.PushObject(ptr, __mStack, res);
}
return __esp;
}
unsafe static StackObject* GetComponent(ILIntepreter __intp, StackObject* __esp, IList __mStack, CLRMethod __method, bool isNewObj)
{
//CLR重定向的说明请看相关文档和教程,这里不多做解释
ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
var ptr = __esp - 1;
//成员方法的第一个参数为this
GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;
if (instance == null)
throw new System.NullReferenceException();
__intp.Free(ptr);
var genericArgument = __method.GenericArguments;
//AddComponent应该有且只有1个泛型参数
if (genericArgument != null && genericArgument.Length == 1)
{
var type = genericArgument[0];
object res = null;
if (type is CLRType)
{
//Unity主工程的类不需要任何特殊处理,直接调用Unity接口
res = instance.GetComponent(type.TypeForCLR);
}
else
{
//因为所有DLL里面的MonoBehaviour实际都是这个Component,所以我们只能全取出来遍历查找
var clrInstances = instance.GetComponents();
for(int i = 0; i < clrInstances.Length; i++)
{
var clrInstance = clrInstances[i];
if (clrInstance.ILInstance != null)//ILInstance为null, 表示是无效的MonoBehaviour,要略过
{
if (clrInstance.ILInstance.Type == type)
{
res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance
break;
}
}
}
}
return ILIntepreter.PushObject(ptr, __mStack, res);
}
return __esp;
}
3.为了显示dll脚本中的公共属性实现方法
[CustomEditor(typeof(MonoBehaviourAdapter.Adaptor), true)]
public class MonoBehaviourAdapterEditor : UnityEditor.UI.GraphicEditor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
MonoBehaviourAdapter.Adaptor clr = target as MonoBehaviourAdapter.Adaptor;
var instance = clr.ILInstance;
if (instance != null)
{
EditorGUILayout.LabelField("Script", clr.ILInstance.Type.FullName);
foreach (var i in instance.Type.FieldMapping)
{
//这里是取的所有字段,没有处理不是public的
var name = i.Key;
var type = instance.Type.FieldTypes[i.Value];
var cType = type.TypeForCLR;
if (cType.IsPrimitive)//如果是基础类型
{
if (cType == typeof(float))
{
instance[i.Value] = EditorGUILayout.FloatField(name, (float)instance[i.Value]);
}
else
throw new System.NotImplementedException();//剩下的大家自己补吧
}
else
{
object obj = instance[i.Value];
if (typeof(UnityEngine.Object).IsAssignableFrom(cType))
{
//处理Unity类型
var res = EditorGUILayout.ObjectField(name, obj as UnityEngine.Object, cType, true);
instance[i.Value] = res;
}
else
{
//其他类型现在没法处理
if (obj != null)
EditorGUILayout.LabelField(name, obj.ToString());
else
EditorGUILayout.LabelField(name, "(null)");
}
}
}
}
}
}
4.调用
unsafe void OnHotFixLoaded()
{
Debug.Log("直接调用GameObject.AddComponent会报错,这是因为这个方法是Unity实现的,他并不可能取到热更DLL内部的类型");
Debug.Log("因此我们需要挟持AddComponent方法,然后自己实现");
Debug.Log("我们先销毁掉之前创建的不合法的MonoBehaviour");
SetupCLRRedirection();
appdomain.Invoke("HotFix_Project.TestMonoBehaviour", "RunTest", null, gameObject);
SetupCLRRedirection2();
appdomain.Invoke("HotFix_Project.TestMonoBehaviour", "RunTest2", null, gameObject);
Debug.Log("成功了");
Debug.Log("那我们怎么从Unity主工程获取热更DLL的MonoBehaviour呢?");
Debug.Log("这需要我们自己实现一个GetComponent方法");
var type = appdomain.LoadedTypes["HotFix_Project.SomeMonoBehaviour2"] as ILType;
var smb = GetComponent(type);
var m = type.GetMethod("Test2");
Debug.Log("现在来试试调用");
appdomain.Invoke(m, smb, null);
Debug.Log("调用成功!");
Debug.Log("我们点一下左边列表里的GameObject,查看一下我们刚刚挂的脚本");
Debug.Log("默认情况下是无法显示DLL里面定义的public变量的值的");
Debug.Log("这个Demo我们写了一个自定义Inspector来查看变量,同样只是抛砖引玉");
Debug.Log("要完整实现MonoBehaviour所有功能得大家自己花功夫了,最好还是避免脚本里使用MonoBehaviour");
Debug.Log("具体实现请看MonoBehaviourAdapterEditor");
Debug.Log("特别注意,现在仅仅是运行时可以看到和编辑,由于没有处理序列化的问题,所以并不可能保存到Prefab当中,要想实现就得靠大家自己了");
}
可以看到,光两个方法就这么多代码,太繁琐了,能不用就不用吧
如果想在热更dll中使用litjson,需要将案例demo中的LitJson文件夹拷贝至你的项目中
1.对LitJson进行注册(在注册CLR绑定之前,执行下面这行代码)
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appdomain);
2.使用
LitJson的使用非常简单,将一个对象转换成json字符串,只需要下面这行代码即可
string json = JsonMapper.ToJson(obj);
将json字符串反序列化成对象也同样只需要一行代码
JsonTestClass obj = JsonMapper.ToObject(json);
其他具体使用方法请参考LitJson库的文档即可