ILRuntime实现Unity的热更

本文记录了一名开发者在接触ILRuntime热更新技术时的学习过程,包括从导入插件、解决报错、理解官方Demo到实现自主热更项目。作者详细解释了每个步骤,涉及加载DLL、调用方法、处理错误等关键点,并分享了如何在Unity中使用ILRuntime进行热更新的方法。文章最后展示了如何通过ILRuntime实现UI交互的热更示例。

最近公司项目要用到ILRuntime热更,安排我去弄。看着网上的各种教程,头秃了三天,算是把一些简单的热更给整出来了。也是踩坑无数……在此记录一下。
Tip:由于全程没有人指导,代码虽然可以实现,但写的是否合理、或者说这样写(或者这样用)是否正确,可能会有些错误的地方。所以如果有错误的地方希望还望指正。
首先放一下官方的Demo和官方的手册。
官方DemoGit链接
官方中文手册链接
参考文档链接:王王王渣渣博主写的热更教程
冰封百度博主写的热更教程
感谢两位博主做出的贡献~

一、导入ILRuntime插件

Unity可以支持从PackageManager中导入插件。具体操作:
在这里插入图片描述
在这里插入图片描述
根据官方文档所示,如果是Unity中国版可以直接搜到,如果是国际版需要做一点小的操作在这里插入图片描述
手动导入方法:
在你Unity工程的Packages目录(和你的Asset目录是同一级的)的目录找到manifest.json文件。
在这里插入图片描述
在这里插入图片描述
打开,添加引用。
在这里插入图片描述
保存后回到Unity工程,等待编译,就能在PackageManager中找到ILRuntime插件了。在这里插入图片描述
在这里插入图片描述
在这儿也能导入官方的Demo

导入官方的Demo后,可能会报错误在这里插入图片描述
告诉我们要允许使用非安全代码。即:
在这里插入图片描述
在这里插入图片描述
允许使用非安全代码。等待编译后,错误就消失了。

二、官方教程的使用

在这里插入图片描述
我们导入以后就一个一个层级(剥洋葱wwwww)的去打开我们的HelloWorld场景。点击运行
在这里插入图片描述
我们发现Demo并不能够正常运行,报了个错误,需要根据提示编译一下。
在这里插入图片描述
但。在Unity项目中并没有找到这个文件夹。
在这里插入图片描述
此时,我们右键打开文件夹在这里插入图片描述
在这里插入图片描述
我们看到了那个文件夹。但这个文件夹并不在我们Unity的视图中。
因为我们发现文件夹的后面,有个波浪线~ 加波浪线的文件夹,不参与Unity的编译,所以我们在项目中看不到。
用VS打开这个文件。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
选择生成。当提示生成成功的时候,就表示这一步ok了。
在这里插入图片描述
返回Unity工程,等待编译,会发现多了一个StreamingAssets文件夹,里面会有这些文件在这里插入图片描述
再次启动,会发现没有报错,成功运行。
在这里插入图片描述
当我们双击导入到log位置的时候,却发现跟log没有任何关系。
在这里插入图片描述
我们去导入到这个引用。
在这里插入图片描述
在这里插入图片描述
这个才是我们所要找的。
但目前来说我们并没有感觉这和log有什么关系。
先说一下这个调用的参数代表着什么意思吧。
“HotFix_Project.InstanceClass”,命名空间.类名。
“StaticFunTest”。方法名称。
后面两个null的话。当我们导入Invoke里,就明白了。
在这里插入图片描述
第三个是类型,第四个是参数。
但,我们在Asset中找不到叫InstanceClass的类。要在哪儿找那?
前面,我们打开了一个VS的解决方案,在SteramingAssets生成了些dll。我们就会发现,在那会找到我们要的类。
在这里插入图片描述
然后就找到了我们所引用的方法了。
在这里插入图片描述
但这里明明是0引用,它是怎么调用的我们这个方法的那?
先返回我们的HelloWorld.CS脚本,我们会发现脚本中的注释,说明的很详细。

using UnityEngine;
using System.Collections;
using System.IO;
using ILRuntime.Runtime.Enviorment;
//下面这行为了取消使用WWW的警告,Unity2018以后推荐使用UnityWebRequest,处于兼容性考虑Demo依然使用WWW
#pragma warning disable CS0618
public class HelloWorld : MonoBehaviour
{
    //AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个
    //大家在正式项目中请全局只创建一个AppDomain
    AppDomain appdomain;

    System.IO.MemoryStream fs;
    System.IO.MemoryStream p;
    void Start()
    {
        StartCoroutine(LoadHotFixAssembly());
    }

    IEnumerator LoadHotFixAssembly()
    {
        //首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒
        appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
        //正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取,
        //正式发布的时候需要大家自行从其他地方读取dll

        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
        //工程目录在Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~
        //以下加载写法只为演示,并没有处理在编辑器切换到Android平台的读取,需要自行修改
#if UNITY_ANDROID
        WWW www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
        WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] dll = www.bytes;
        www.Dispose();

        //PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可
#if UNITY_ANDROID
        www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
        www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] pdb = www.bytes;
        fs = new MemoryStream(dll);
        p = new MemoryStream(pdb);
        try
        {
            appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
        }
        catch
        {
            Debug.LogError("加载热更DLL失败,请确保已经通过VS打开Assets/Samples/ILRuntime/1.6/Demo/HotFix_Project/HotFix_Project.sln编译过热更DLL");
        }

        InitializeILRuntime();
        OnHotFixLoaded();
    }

    void InitializeILRuntime()
    {
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
        //由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
        appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
        //这里做一些ILRuntime的注册,HelloWorld示例暂时没有需要注册的
    }

    void OnHotFixLoaded()
    {
        //HelloWorld,第一次方法调用
        appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);

    }

    private void OnDestroy()
    {
        if (fs != null)
            fs.Close();
        if (p != null)
            p.Close();
        fs = null;
        p = null;
    }

    void Update()
    {

    }
}

在名为Invocation.CS的OnHotFixLoaded方法中,有更详细的说明。

  void OnHotFixLoaded()
    {
        Debug.Log("调用无参数静态方法");
        //调用无参数静态方法,appdomain.Invoke("类名", "方法名", 对象引用, 参数列表);
        appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);
        //调用带参数的静态方法
        Debug.Log("调用带参数的静态方法");
        appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123);


        Debug.Log("通过IMethod调用方法");
        //预先获得IMethod,可以减低每次调用查找方法耗用的时间
        IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
        //根据方法名称和参数个数获取方法
        IMethod method = type.GetMethod("StaticFunTest2", 1);

        appdomain.Invoke(method, null, 123);

        Debug.Log("通过无GC Alloc方式调用方法");
        using (var ctx = appdomain.BeginInvoke(method))
        {
            ctx.PushInteger(123);
            ctx.Invoke();
        }

        Debug.Log("指定参数类型来获得IMethod");
        IType intType = appdomain.GetType(typeof(int));
        //参数类型列表
        List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();
        paramList.Add(intType);
        //根据方法名称和参数类型列表获取方法
        method = type.GetMethod("StaticFunTest2", paramList, null);
        appdomain.Invoke(method, null, 456);

        Debug.Log("实例化热更里的类");
        object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 });
        //第二种方式
        object obj2 = ((ILType)type).Instantiate();

        Debug.Log("调用成员方法");
        method = type.GetMethod("get_ID", 0);
        using (var ctx = appdomain.BeginInvoke(method))
        {
            ctx.PushObject(obj);
            ctx.Invoke();
            int id = ctx.ReadInteger();
            Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id);
        }

        using (var ctx = appdomain.BeginInvoke(method))
        {
            ctx.PushObject(obj2);
            ctx.Invoke();
            int id = ctx.ReadInteger();
            Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id);
        }
        
        Debug.Log("调用泛型方法");
        IType stringType = appdomain.GetType(typeof(string));
        IType[] genericArguments = new IType[] { stringType };
        appdomain.InvokeGenericMethod("HotFix_Project.InstanceClass", "GenericMethod", genericArguments, null, "TestString");

        Debug.Log("获取泛型方法的IMethod");
        paramList.Clear();
        paramList.Add(intType);
        genericArguments = new IType[] { intType };
        method = type.GetMethod("GenericMethod", paramList, genericArguments);
        appdomain.Invoke(method, null, 33333);

        Debug.Log("调用带Ref/Out参数的方法");
        method = type.GetMethod("RefOutMethod", 3);
        int initialVal = 500;
        using(var ctx = appdomain.BeginInvoke(method))
        {
            //第一个ref/out参数初始值
            ctx.PushObject(null);
            //第二个ref/out参数初始值
            ctx.PushInteger(initialVal);
            //压入this
            ctx.PushObject(obj);
            //压入参数1:addition
            ctx.PushInteger(100);
            //压入参数2: lst,由于是ref/out,需要压引用,这里是引用0号位,也就是第一个PushObject的位置
            ctx.PushReference(0);
            //压入参数3,val,同ref/out
            ctx.PushReference(1);
            ctx.Invoke();
            //读取0号位的值
            List<int> lst = ctx.ReadObject<List<int>>(0);
            initialVal = ctx.ReadInteger(1);

            Debug.Log(string.Format("lst[0]={0}, initialVal={1}", lst[0], initialVal));
        }

实际上,ILRuntime热更和DLL热更很相似。大体流程就是先加载dll文件,然后存为字节流,最后再调取我们所需要的方法。每次修改完成后就要重新生成一下,这样我们改的就会生效。
我们所隐藏的解决方案,一般就是我们的热更方案;Unity工程就是我们的主工程。主工程去调用热更方案的方法,就实现了热更。我们更改只需要更改热更方案里的东西,然后再让客户端去下载,就ok了。
现在,让我们来修改一些东西。
打开官方Demo热更方案的InstanceClass脚本,还是找到StaticFunTest这个方法,修改一下debug的内容。修改什么都无所谓。
在这里插入图片描述
然后右键解决方案——重新生成在这里插入图片描述
在这里插入图片描述
等待编译好后返回Unity工程中,启动HelloWorld。
在这里插入图片描述这就是用官方Demo完成了一个热更方案。

三、自主实现一个热更项目

新建一个Unity工程,或者说在官方demo那个工程也可。
先打开VS,新建一个解决方案,
在这里插入图片描述
选择这个。路径的话就保存到Unity工程Asset目录下。注意要在文件夹的后面打上波浪线,这样不会被Unity编译。
在这里插入图片描述
先打开我们刚刚创建的解决方案,添加一些引用。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击浏览,可以找到我们所需要的DLL。
如果你找不到位置的话,举个例子,以UnityEngine.UI为例子。
随便写一个UI组件(button,text都可),按F12进入。在上面的注释有引用的DLL地址。复制那份地址粘贴在刚才打开的添加里,就能看到了。
在这里插入图片描述
在这里插入图片描述
最好去掉dll的名字,要不然会提示正在尝试打开此dll。
在这里插入图片描述
在这里插入图片描述
我们先导入这四个DLL。

返回Unity工程,创建一个名为ReadDll的脚本。在这里我自己写了一个加载DLL以及调用热更工程的方法。

using System.Collections;
using UnityEngine;
using System.IO;
using ILRuntime.CLR.TypeSystem;
using ILRuntime.CLR.Method;
using UnityEngine.Networking;
using System.Threading;
using System;
using UnityEngine.Events;
using System.Collections.Generic;
using System.Linq;

public class ReadDll
{
    public static ILRuntime.Runtime.Enviorment.AppDomain appDomain;

    static MemoryStream dllStream;

    /// <summary>
    /// 加载热更DLL并且调用方法
    /// 目前是读取StreamingAssets里的dll
    /// </summary>
    /// <param name="dllName">需要读取的dll名字,不需要加后缀</param>
    /// <param name="num">调用热更的类型。1为静态方法,2为非静态方法</param>
    /// <param name="className"></param>
    /// <param name="funName"></param>
    /// <param name="paramCount"></param>
    /// <param name="p"></param>
    /// <returns></returns>
    public static IEnumerator LoadDll(Action action)
    {
        appDomain = new ILRuntime.Runtime.Enviorment.AppDomain();
        UnityWebRequest tmpDLL = UnityWebRequest.Get(Application.streamingAssetsPath + "/ILRuntimeTest.dll");
        yield return tmpDLL.SendWebRequest();
        if (string.IsNullOrEmpty(tmpDLL.error))
        {
            byte[] dll = tmpDLL.downloadHandler.data;
            dllStream = new MemoryStream(dll);
        }
        else
        {
            Debug.LogError("报错信息" + tmpDLL.error);
        }
        try
        {
            appDomain.LoadAssembly(dllStream, null, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }

        InitILRunTime();
        action?.Invoke();
        //   HotFixType(num, className, funName, paramCount, p);
        //   InitILRuntime();
    }

    //初始化热更
    //这里也写一些注册
    static void InitILRunTime()
    {
        appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
        //注册类/接口继承器
        appDomain.RegisterCrossBindingAdaptor(new InterfaceAdapter());
        //注册委托方法
        appDomain.DelegateManager.RegisterMethodDelegate<bool>();

        appDomain.DelegateManager.RegisterMethodDelegate<float>();
        //Slidr的委托注册
        appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction<float>>((action) =>
        {
            return new UnityAction<float>((a) =>
            {
                ((Action<float>)action)(a);
            });
        });
        //toggle委托注册
        appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction<bool>>((action) =>
        {
            return new UnityAction<bool>((a) =>
            {
                ((Action<bool>)action)(a);
            });
        });
        //button委托注册
        appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction>((action) =>
        {
            return new UnityAction(() =>
            {
                ((Action)action)();
            });
        });
    }

    //static void InitILRuntime()
    //{
    //    List<Action> actions = new List<Action>();
    //    List<Type> types = new List<Type>();
    //    var values = appDomain.LoadedTypes.Values.ToList();
    //    foreach (var item in values)
    //    {
    //        types.Add(item.ReflectionType);
    //    }
    //    types = types.Distinct().ToList();
    //    actions.Clear();
    //    foreach (var item in types)
    //    {
    //        if (item.IsClass && item.GetInterface("Adaptor") != null)
    //        {
    //            var tmpInter = appDomain.Instantiate<IUI>(item.FullName);
    //            tmpInter.Start();
    //        }
    //    }
    //}

    /// <summary>
    /// 调用热更的类是静态还是非静态
    /// </summary>
    /// <param name="num">1为静态,2为非静态方法</param>
    /// <param name="className">热更类名</param>
    /// <param name="funName">调用方法</param>
    /// <param name="paramCount">参数个数</param>
    /// <param name="p">参数。没有参数可以传null</param>
    public static void HotFixType(int num, string className, string funName, int paramCount, params object[] p)
    {
        switch (num)
        {
            case 1:
                StaticHotFix(className, funName, paramCount, p);
                break;
            case 2:
                InstanceHotFix(className, funName, paramCount, p);
                break;
        }
    }

    /// <summary>
    /// 调用静态方法
    /// </summary>
    /// <param name="className">热更类名</param>
    /// <param name="funName">调用方法</param>
    /// <param name="paramCount">参数个数</param>
    /// <param name="p">参数。没有参数可以传null</param>
    static void StaticHotFix(string className, string funName, int paramCount, params object[] p)
    {
        IType type = appDomain.LoadedTypes[className];
        //获取方法以及参数
        IMethod method = type.GetMethod(funName, paramCount);
        object tmpObj = appDomain.Invoke(method, null, p);
        Debug.Log("静态log:" + tmpObj);
    }

    /// <summary>
    /// 调用非静态方法
    /// </summary>
    /// <param name="className">热更类名</param>
    /// <param name="funName">调用方法</param>
    /// <param name="paramCount">参数个数</param>
    /// <param name="p">参数。没有参数可以传null</param>
    static void InstanceHotFix(string className, string funName, int paramCount, params object[] p)
    {
        IType type = appDomain.LoadedTypes[className];
        object instance = (type as ILType).Instantiate();
        IMethod method = type.GetMethod(funName, paramCount);
        object tmpObj = appDomain.Invoke(method, instance, p);
        Debug.Log("非静态log:" + tmpObj);
    }

    //关闭文件
    public static void DestroyStream()
    {
        if (dllStream != null)
        {
            dllStream.Close();
        }
        dllStream = null;
    }
}

很多教程都是让这个类继承于mono,然后挂载在物体上去执行。这里的话我没有去继承。
在热更方案里,写一些简单方法,去尝试调用。

using UnityEngine;
namespace ILRuntimeTest
{
    public class Class1
    {
        static void ILTest()
        {
            Debug.Log("这是一个不带参数的静态方法");
        }

        static void ILTest2(int a)
        {
            Debug.Log($"这是一个带参数的静态方法.参数为{a}");
        }

        static string GetName1()
        {
            return "这是一个有返回值不带参数的静态方法";
        }

        static float Sum1(int a, int b)
        {
            Debug.Log("这是一个有返回值带参数的静态方法");
            return a + b;
        }

        void LITest3()
        {
            Debug.Log("这是一个不带参数的非静态方法");
        }

        void ILTest4(int b)
        {
            Debug.Log($"这是一个带参数的非静态方法.参数为{b}");
        }
        string GetName()
        {
            return "这是一个有返回值不带参数的非静态方法";
        }

        float Sum(int a, int b)
        {
            Debug.Log("这是一个有返回值带参数的非静态方法");
            return a + b;
        }
    }
}

返回Unity工程,再新建一个测试脚本,写一些调用方法,挂载在物体上,进行测试。

using UnityEngine;
using UnityEngine.UI;

public class ThisTest : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(ReadDll.LoadDll(Test));

    }

    void Test()
    {
        //   ReadDll.HotFixType(2, "ILRuntimeTest.HotUI", "Start", 0,null);
        ReadDll.HotFixType(1, "ILRuntimeTest.Class1", "ILTest", 0, null);
        ReadDll.HotFixType(1, "ILRuntimeTest.Class1", "ILTest2", 1, 5);
        ReadDll.HotFixType(1, "ILRuntimeTest.Class1", "GetName1", 0, null);
        ReadDll.HotFixType(1, "ILRuntimeTest.Class1", "Sum1", 2, 3, 4);

        ReadDll.HotFixType(2, "ILRuntimeTest.Class1", "LITest3", 0, null);
        ReadDll.HotFixType(2, "ILRuntimeTest.Class1", "ILTest4", 1, 8);
        ReadDll.HotFixType(2, "ILRuntimeTest.Class1", "GetName", 0, null);
        ReadDll.HotFixType(2, "ILRuntimeTest.Class1", "Sum", 2, 6, 9);
    }

    // Update is called once per frame
    void Update()
    {
      
    }
    private void OnDestroy()
    {
        ReadDll.DestroyStream();
    }
}

在这里插入图片描述
可以看到我们写的方法已经被成功调用了。
同样,改变热更工程里的一些东西后,再重新生成新的dll文件,覆盖原来的,无需改变主程序的代码,即可轻松实现热更。

然后,我们再来写一个难度稍微有点大的Demo。这个Demo就需要用到官方文档中说到的跨域继承以及跨域委托。

跨域委托的话比较好整一些,跨域继承的话就会显得麻烦些。
这些说明在官方的文档中是有说明的。这里就不细说了。

首先我们在主工程中拼接几个简单的UI,有text,button,toggle,slider,这些随便的去布局。
在这里插入图片描述

创建一个IUI接口,用来实现Unity的生命周期。

public interface IUI
{
    void Start();
}

返回热更工程,去在这里写逻辑。并且去继承IUI接口。
如果热更工程无法调用Unity工程接口,看一下我的注释

using UnityEngine;
using UnityEngine.UI;
namespace ILRuntimeTest
{
    //热更方案调用Unity接口,要去Unity的Library下去找。
    //Library\ScriptAssemblies\Assembly-CSharp.dll
    public  class HotUI : IUI
    {
        Button button;
        Text text;
        Slider slider;
        Toggle toggle;
        public  void Start()
        {
            Debug.Log("热更的方法进来啦~");
            button = GameObject.Find("Panel/Button").GetComponent<Button>();
            text = GameObject.Find("Panel/Text").GetComponent<Text>();
            toggle = GameObject.Find("Panel/Toggle").GetComponent<Toggle>();
            slider = GameObject.Find("Panel/Slider").GetComponent<Slider>();
            slider.onValueChanged.AddListener(ThisSliOnValue);
            button.onClick.AddListener(ThisBtnOnClick);
            toggle.onValueChanged.AddListener(ThisTogOnValue);
        }

        void ThisBtnOnClick()
        {
            text.text = "这是一个热更按钮";
        }

        void ThisSliOnValue(float a)
        {
            slider.value = a;
            Debug.Log("Slider:" + a);
        }

        void ThisTogOnValue(bool isClick)
        {
            toggle.isOn = isClick;
            Debug.Log("Toggle" + isClick);
        }
    }
}

既然热更工程去继承了主工程的接口/类(其实都一样),那么我们就需要去注册它。注册的话…也是比这葫芦画瓢,也暂时没来及去弄透它。

using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
using System;

public class InterfaceAdapter : CrossBindingAdaptor
{
    public override Type BaseCLRType
    {
        get
        {
            return typeof(IUI);//这是你想继承的那个类
        }
    }

    public override Type AdaptorType
    {
        get
        {
            return typeof(Adaptor);//这是实际的适配器类
        }
    }
    public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
    {
        return new Adaptor(appdomain, instance);//创建一个新的实例
    }

    //继承实现接口/类以及实现CrossBindingAdaptorType接口
    public class Adaptor : IUI, CrossBindingAdaptorType
    {
        ILTypeInstance instance;
        ILRuntime.Runtime.Enviorment.AppDomain appdomain;

        IMethod m_Start;
        bool m_StartGot;

        public Adaptor()
        {

        }

        public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
        {
            this.appdomain = appdomain;
            this.instance = instance;
        }

        public ILTypeInstance ILInstance { get { return instance; } }

        //实现start接口
        public void Start()
        {
            if (!m_StartGot)
            {
                m_Start = instance.Type.GetMethod("Start", 0);
                m_StartGot = true;
            }
            if (m_Start != null)
            {
                appdomain.Invoke(m_Start, instance, null);//没有参数建议显式传递null为参数列表,否则会自动new object[0]导致GC Alloc
            }
        }
    }
}

实际上在上面的ReadDll脚本,我已经是在初始化appDomain那写了些注册的方法了。

        //注册类/接口继承器
        appDomain.RegisterCrossBindingAdaptor(new InterfaceAdapter());
        //注册委托方法
        appDomain.DelegateManager.RegisterMethodDelegate<bool>();

        appDomain.DelegateManager.RegisterMethodDelegate<float>();
        //Slidr的委托注册
        appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction<float>>((action) =>
        {
            return new UnityAction<float>((a) =>
            {
                ((Action<float>)action)(a);
            });
        });
        //toggle委托注册
        appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction<bool>>((action) =>
        {
            return new UnityAction<bool>((a) =>
            {
                ((Action<bool>)action)(a);
            });
        });
        //button委托注册
        appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction>((action) =>
        {
            return new UnityAction(() =>
            {
                ((Action)action)();
            });
        });

最后返回我们的热更工程,保存后重新生成Dll。
然后再返回到主工程中,把ThisTest脚本挂载到Panel下,更改下以前写的东西。
在这里插入图片描述
保存,然后运行项目。
在这里插入图片描述
可以看到项目已经成功的运行了。
点一下按钮、滑动下Slidr,点点toggle,这些都是可以实现的。
在这里插入图片描述
至此,热更的第一步算是走出去了。

最后一点碎碎念


从周二开始搞,然后一直到周四晚上,才把这个Demo去实现。这比半年前自己去搞xLua时间短多了。毕竟当初搞就是个人兴趣,这次是有项目在身。两次都是没有人去帮助的情况下搞出来的,虽然可能会有些不太合理的地方,还望各位指正。
实际上还有个JEngine热更,但那个热更不适合我们项目,所以我也没去了解。那个热更我稍微看了下,也是可以去学习一下的。
总之……热更这块的话,不管是lua热更还是c#的,都算是稍微知道怎么回事了,剩下的就看能不能有时间或者有机会或者有精力去往深处理解了。
对于个人来说的话,个人还是比较倾向于xLua热更。
最后,也许我写的一些方法并不适合所有情况,我也会根据我们项目的一些需求去更改。
官方手册也还有一些关于CLR的东西。那些东西我还没来及去学。有兴趣的可以自己去琢磨琢磨。

本课程主要是针对ILRuntime设计一个独立的脚本新框,框架的优势:1.将代码脱离Assetbundle资源,独立的部分适用于各种不同的框架。2.加快项目的逻辑新,bug修复.(后期修bug,多数情况下并不用动到资源,只需要新脚本代码,无需重走资源打包发布流程,大大提升效率)3.提供模式和正常开发模式的快速切换接口,可以让队友像平常一样去开发.4.不依赖市面上的任何AB框架,完全兼容市面上各种不同的AB框架.5.重点:希望通过它,帮助你学习、了解ILRuntime真正在项目中的应用.框架的将提供以下这些接口,这些接口将从0开始,在Unity里将C#脚本编译成dll,然后将dll放到服务器上,再将dll下载下来,进行加载,到最后从Unity主工程调用新的代码逻辑.1.Create hotfixdll接口将部分的代码 编译成dll生成dll版本配置(MD5)2.新对比接口本地跟服务器的dll进行版本对比3.下载dll下载dll本身的文件下载版本记录文件4.加载dll加载dll实例化:AppDomain初始化:注册跨域继承适配器注册委托适配器LitJson重定向调用性能优化(CLR绑定功能)调用接口Hotfix.HotfixApplication.Main 进入逻辑5.ILMonoBehaviour用于监听组件的生命周期,实际是桥接(调用)的逻辑AwakeStartEnableUpdateLateUpdate.......6.添加其他常用的库DOTweenLitJsonSpineGoogle.ProtobufTextAnimation可以根据上面的方式,自行添加依赖的库... 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值