热更新是指在不需要重新编译打包游戏的情况下,在线更新游戏中的一些非核心代码和资源,比如活动运营和打补丁。热更新分为资源热更新和代码热更新两种,代码热更新实际上也是把代码当成资源的一种热更新,但通常所说的热更新一般是指代码热更新。
- 资源热更新主要通过AssetBundle来实现,在Unity编辑器内为游戏中所用到的资源指定AB包的名称和后缀,然后进行打包并上传服务器,待游戏运行时动态加载服务器上的AB资源包。
- 代码热更新主要包括Lua热更新、C#直接反射热更新和ILRuntime热更新等。
Lua热更新解决方案是通过一个Lua热更新插件(如ULua、ToLua、XLua等)来提供一个Lua的运行环境以及和C#进行交互,这样可以使用C#和Lua共同开发。由于Lua不需要编译,Lua代码可以直接在Lua虚拟机里运行,因此几乎可以在任何操作系统和平台上运行,那么可以将项目底层代码使用C#进行编写,而游戏逻辑等需要热更新的部分使用Lua编写,版本更新时更新迭代Lua代码即可,从而保证游戏正常运营。
而热更新插件中,性能最好的当属XLua对应的热更新解决方案,并且稳定性和可持续性较强。XLua就是为Unity、.Net、Mono等C#环境提供一个Lua虚拟机,使这些环境里也可以运行Lua代码,从而为它们增加Lua脚本编程的能力。借助xLua,这些Lua代码就可以方便的和C#相互调用。 可以形容XLua为轻量化的胶水,打通了Lua和C#的框架。
xLua进行IL注入时会为打上[Hotfix]标签的类的所有函数创建一个变量a,同时添加对应的判断条件。如果Lua脚本中添加了对应的热更新函数,变量a就不为空,并将变量a中的方法指向xlua.hotfix中的具体function。这时由于变量a不为空,所以C#中的函数就会执行Lua脚本中对应的热更新函数逻辑。但如果没有定义对应的热更新函数,或对应的热更新函数为nil,变量a就为空,则C#中的函数依然执行原有的函数逻辑。就是在运行时用Lua函数替换对应的C#函数。使用Lua语言修复c#bug
与xLua热更新相关的标签还包括:[LuaCallCSharp]、[ReflectionUse]和[CSharpCallLua],这三个标签都需要生成适配代码,但不需要IL注入。
比如:[LuaCallCSharp]标签表示如果一个C#类型添加了该标签,xLua会生成这个类型的适配代码,否则将会尝试用性能较低的反射方式来访问。比如,Lua脚本中想调用某个C#函数,就可以在该C#函数上添加[LuaCallCSharp]标签,这时Lua就会去寻找该函数的适配代码,然后进行调用。
编译成的DLL打成资源文件, 通过C#反射加载程序集的方式可以动态的从资源包里加载脚本到工程中。
由于Android支持JIT即时编译(动态编译)的模式,即可以边运行边编译,支持在运行时动态生成代码和类型。比如,C#中的虚函数和反射都是在程序运行时才确定对应的重载方法和类。因此,Android平台可以不借助任何第三方热更新方案,直接使用C#反射执行DLL文件。实际开发时通过System.Reflection.Assembly类加载程序集DLL文件,然后再利用System.Type类获取程序集中某个类的信息,还可以通过Activator类来动态创建实例对象。
而IOS平台采用AOT预先编译(静态编译)的模式,不支持JIT编译模式,即程序运行前就将代码编译成机器码存储在本地,然后运行时直接执行即可,因此AOT不能在运行时动态生成代码和类型。
目前Mono在IOS平台上采用Full AOT模式运行,如果想要使用C#反射执行DLL文件,就会触发Mono的JIT编译器,而Full AOT模式下又不允许JIT,于是Mono就会报错。因此,IOS平台上不允许直接使用C#反射执行DLL文件来实现热更新。
ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现,快速、方便且可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新。
官方解释:ILRuntime借助Mono.Cecil库来读取DLL的PE信息,以及当中类型的所有信息,最终得到方法的IL汇编码,然后通过内置的IL解译执行虚拟机来执行DLL中的代码。
直译解释:就是先用VS把需要热更新的C#代码封装成DLL(动态链接库)文件,然后通过Mono.Cecil库读取DLL信息并得到对应的IL中间代码,最后再用内置的IL解译执行虚拟机来执行DLL文件中的IL代码。
IL是.NET框架中 中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL的代码。 使用中间语言的优点有两点,一是可以实现平台无关性,既与特定CPU无关;二是只要把.NET框架某种语言编译成IL代码,就实现.NET框架中语言之间的交互操作。
像C#这样遵循CLI规范的高级语言,被先被各自的编译器编译成中间语言:IL(CIL),等到需要真正执行的时候,这些IL会被加载到运行时库,也就是VM中,由VM动态的编译成汇编代码(JIT)然后在执行。
在使用ILRuntime项目时,由于ILRuntime项目是使用C#来完成热更新,因此很多时候会用到反射来实现某些功能。但Unity的主工程的数据类型和热更工程的数据类型是不直接互通的。要想实现互通的话,就需要实现一些跨域功能。
本文章使用limfx的vscode插件快速发布