对Unity3D C#热更新方案ILRuntime的学习

Unity实现C#热更新方案探究

转载请标明出处:http://www.cnblogs.com/zblade/
最近研究了一下如何在unity中实现c#的热更新,对于整个DLL热更新的过程和方案有一个初步的了解,这儿就写下来,便于后续的深入调查和方案选择。
一、C# DLL的动态加载和卸载
既然要热更新,那么就是动态的加载c#的DLL,所以第一步就是研究如何实现DLL的动态加载和卸载。
在CLR Via C#中,对于DLL的加载有详细的讲解,这儿就不再长篇幅的讲解整个过程,简单的来说,在C#的工程中,都会生成一个默认的程序域appDomain,就叫做DefaultAppDomain吧,在这个程序域的基础上,我们可以加载多个不同的程序集。在.Net中,程序集不能卸载,但是可以随着程序域的释放而一起释放,所以我们可以利用程序域来实现程序集(DLL)的加载和释放。

上面的理论来自CLR Via C#, 具体的图为:

基于这个理论,我们可以在DefaultAppDomain之外,再多次创建多个AppDomain,基于AppDomain来实现DLL的加载和卸载。基于此,编写相关的工程测试,参考网上的一个工程来进一步的测试,这儿是原文,文末有相关的代码下载:
在原代码的基础上,进一步构建。首先,构建4个Class Library:

默认工程为MainServer,将Module1和Module2的Build路径设置到MainServer的bin中,这样MainServer就可以加载最新的Module1.DLL/Module2.DLL(PS:这儿的设置很重要,忽略会使得不能加载最新的DLL)
Module1和Module2都在References中添加CommonLib的引用,实现ICalculater接口,各自的实现为:
Module1:

Module2:

这样,就是两个不同的Class Library中,分别实现了ICalculater接口,分别为相加和相乘。在MainServer的主程序入口Program中:

 

首先在默认appDomain的基础上,进一步加载2个appDomain,然后分别在这2个程序域的基础上加载DLL。得到的结果为:

整个步骤都详细的解释了整个执行流程,先构建appDomain,在此基础上,加载dll,然后执行里面的方法。再一个新的appDomain中加载前面加载过的dll,再次执行,相互之间并不冲突。所以appDomain可以一对多个DLL,一个DLL可以被多个不同的AppDomain加载。

 

二、Unity中测试DLL的加载
在第一部分的基础上,我们进一步的研究如何在Unity中实现Dll的加载,基本的操作步骤可以参考这篇文章:unity dll实现热更新
当然,文章并不是完全的实现热更新,实现的是windows和android平台下,对于dll文件的热更新。对于IOS为什么不能热更新,我们后续会讨论到,先看看安卓和windows下 dll的热更新步骤。
1、新建一个ClassLibrary(类库)的工程,在其中实现对应的类和方法;
2、将该工程导出为DLL;
3、将DLL改为bytes文件,存入Unity工程中的StreamingAssets文件夹下;
4、在工程运行的时候,读取StreamingAssets下的Dll文件,用Assembly.Load(byte[] bytes )的方法,将DLL文件读取出来,进而执行相关的操作。这一步的代码为:

 

对于DLL文件,是执行www.bytes,对于assetbundle文件,则是执行ab.mainAsset转换为TextAsset,进一步得到bytes。在windows和android平台下,都会得到这样的屏幕输出:

这个方案的本质,和前面的本质相差不大,unity工程在执行的时候,会构建一个默认的appDomain,Assembly.Load,其实就是在这个程序域上加载Dll,所以相关的实质和前面一个部分相差不大,这就是c#热更新在unity中的应用(IOS不包括)。

下文我们会讲解IOS为什么不支持DLL的热更新,以及如何利用ILRuntime来实现Android和IOS的热更新。

对C#热更新方案ILRuntime的探究

转载请标明出处:http://www.cnblogs.com/zblade/
对于游戏中的热更,目前主流的解决方案,分为Lua(ulua/slua/xlua/tolua)系和ILRuntime代表的c#系。今天就来探究一下ILRuntime是如何实现热更的流程的,新手入门,个中有错误理解,欢迎指正。
ILRuntime的原理
首先说一下lua的热更新基本过程,unity提供了AssetBundle的资源打包方式,这样可以通过资源的对比来更新最新的资源。而lua文件不需要编译,可以被打包进游戏的资源中,在游戏启动的过程中加载对应的脚本资源,今儿解释执行转换为字节码,在lua的虚拟机中执行,这种天然的设计,可以规避静态语言需要编译的环节。
C#代码在编写后,是需要执行编译的,才能起效,这样如果在手机端,没有对应的编译环境,那么对应的c#代码就无法实现热更。ILRuntime实现的基础,也是基于AssetBundle的资源热更新方式,将需要热更新的c#代码打包成DLL,放在工程的StreamingAssets下,在每次完成资源打包后,对应的DLL会被作为资源热更新出去。这样就规避了编译相关的环节,实现了热更。
文字讲述较为枯燥,看看示例代码是如何执行这个流程的:
1、ILRuntime的相关资料链接
可以在github上查找: ILRuntime
对应的unity3d的工程的例子:ILRuntime U3D例子
 
2、探究ILRuntime的例子
整个例子分为两个部分:
分别是热更新的工程和U3D主工程,那么先看看U3D工程:
较为重要的是两个部分,一个是ILRuntime需要用到的几个环境,重点是Mono.Ceil.20, Mono.Ceil.Pdb,ILRuntime三个文件夹,ILRuntime后续的版本将LitJson也加入进来了。
另一个关键点就是讲HotFix_Project.dll作为资源加入到StreamingAssets文件夹下,这样就可以被ab打包作为资源热更新出去。
接下来我们打开HotFix_Project:
 
得到的工程为:
 

重点分析一下引用,可以看到,该工程是引用了UnityEngine/UnityEngine.UI/Assembly-CSharp等几个U3D游戏主工程中的dll的,这样,可以在工程中直接调用对应的u3d相关的dll下的类和方法,示例代码:

 

只要using UnityEngine申明后,下面的代码就可以继承MonoBehaviour类,自然可以调用其中的相关类和方法。那么到这一步,就可以理解,在HotFix_Project中,我们是可以调用U3D游戏主工程的类和方法的,只要添加其相关的dll到工程的引用中。这样我们就完成了HotFix对U3D游戏主工程的调用的一条线路。

3、U3D游戏主工程调用HotFix_Project中的类型和相关方法
现在的调用都是交互式的,在完成了hotfix对u3d的调用后,我们接下来看看u3d是如何调用hotfix中的类和方法的。
取我们最常见和关注的反射作为一个示例,这样可以了解整个调用过程。
首先,需要加载hotfix的dll,来看看相关的代码:
 
整个竖屏截出来的图比较大,跟随箭头来看看流程:
1) 首先MonoBehaviour下的Start执行一个协程;
2) 实例化唯一的appdomain;
3) 加载hotfix的dll和pdb,可以看到,如果我们热更新相关的dll和pdb,是需要在这一步之前执行的,其实可以放在Awake中执行,当然这儿没有热更,我们就直接执行dll和pdb的加载;
4) 在完成dll和pdb的加载后,执行assembly的加载,这样我们可以获取到hotfix中的相关类和方法,属性等相关信息,相关可以查看源码即可了解。
5) 测试如何调用反射:
        Debug.Log("热更DLL中的类型我们均需要通过AppDomain取得");
        var it = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
        Debug.Log("LoadedTypes返回的是IType类型,但是我们需要获得对应的System.Type才能继续使用反射接口");
        var type = it.ReflectionType;
        Debug.Log("取得Type之后就可以按照我们熟悉的方式来反射调用了");
        var ctor = type.GetConstructor(new System.Type[0]);
        var obj = ctor.Invoke(null);
        Debug.Log("打印一下结果");
        Debug.Log(obj);
        Debug.Log("我们试一下用反射给字段赋值");
        var fi = type.GetField("id", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        fi.SetValue(obj, 111111);
        Debug.Log("我们用反射调用属性检查刚刚的赋值");
        var pi = type.GetProperty("ID");
        Debug.Log("ID = " + pi.GetValue(obj, null));
让我们看看整个流程:
基于appdomain来获取类,然后获取type,对应的invoke, 也可以利用反射来修改对应的field的值。
 
总结:到这儿,我们就基本理清楚了基于ILRuntime如何实现hotfix和u3d主工程之间的相互调用,可以讲这样一套热更新方式嵌套到游戏的框架中,实现不用lua来实现热更。当然ILRuntime要转换到IL2CPP,还是有很多地方需要注意的,详情可以参考github上的相关手册。
  • 1
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值