转载请标明原文地址:https://segmentfault.com/a/11...
一、ILRuntime介绍
问:什么是热更新?
答:软件在使用时就能实现更新的方式就叫做热更新。热更新无需用户重新下载安装或重启,在使用时即可更新,方便快捷体验良好。
问:什么是ILRuntime?
答:ILRuntime是一个C#热更新方案。ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现,快速、方便且可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新
问:lua 和 ILRuntime哪个热更新方案更好?
答:如果你的团队更熟悉lua,就用lua。如果你的团队更熟悉C#就用ILRuntime。如果你是主程,你可以选择自己喜欢的方案,但是要肩负起填坑的责任。
我个人的感觉是:lua在Unity中用起来很难受,不是lua这门语言不好,而是因为在Unity中官方的开发语言是C#。用lua就意味着开发者要会两种语言,学习和开发成本都高,而且因为C#是强类型、面向对象的语言。lua是弱类型,非面向对象的语言。
lua从编程思想和代码写法都和C#有较大差距,这一点在面对越大的项目时感受越明显,项目小的时候觉得lua还好,项目做大了以后会发现lua带给你的麻烦会大于便利。
而ILRuntime方案是基于C#的,开发语言统一,编码更容易。不过他的缺点是实际经过验证的项目还是太少了,不太成熟,可能有很多坑需要填,不像是经过很多项目验证的lua,有比较成熟的方案。
二、下载ILRuntime
GitHub:https://github.com/Ourpalm/IL...
Unity Demo:https://github.com/Ourpalm/IL...
国内码云:https://gitee.com/zhangyu800/...
中文手册:http://ourpalm.github.io/ILRu...
先去GitHub上点个赞,支持一下该项目,再去Unity Demo上把项目下载下来,顺便也点个赞。
如果国外地址下载慢,可以在国内码云上下载Demo。
三、导入ILRuntime
1.解压缩Unity Demo
打开Unity工程目录下的ProjectSettings/ProjectVersion.txt 查看工程版本。
为了避免因为不同版本导致的兼容问题,工程版本和Unity版本尽量保持一致,我下载的Demo工程版本是2019.3.6f1, 我尽量用2019.3.6 或稍微高一点儿的版本导入。
2.目录结构
工程导入完毕,看下目录结构。
Demo目录:
在Samples/ILRuntime/1.6.2/Demo/_Scenes/Examples文件夹下
热更新加载的代码目录:
热更新代码会从StreamingAssets目录下加载编译后的dll
其中mdb和pdb文件都是调试时用的,发布时只需要dll。
运行Hello World Demo:
打开并运行 01_Hello World 场景。可以看到控制台输出了如下结果。
ILR是如何工作的呢?看下HelloWorld脚本。
using UnityEngine;
using System.Collections;
using System.IO;
using ILRuntime.Runtime.Enviorment;
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()
{
}
}
惊喜!注释是纯中文的,作者一定是中国人!注释很详细,看注释就行了。
其中比较重要的代码是:
//这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
//工程目录在Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~
//以下加载写法只为演示,并没有处理在编辑器切换到Android平台的读取,需要自行修改
WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
//HelloWorld,第一次方法调用
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);
打开HotFix_Project:
HotFix_Project 是Demo自带的C#热更新代码工程。
工程目录在
Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~文件夹下,用Visual Studio打开该工程。
注意:HotFix_Project~ 后面带了个波浪号,Unity会自动忽略该目录,资源不会被导入,代码不会被编译,文件不会带进发布包。
具体规则可以看官方文档:
https://docs.unity3d.com/Manu...
修改热更新代码:
打开HotFix项目下的InstanceClass类 修改StaticFunTest方法中输出的内容:
public static void StaticFunTest()
{
UnityEngine.Debug.Log("调用了热更新类的静态方法!");
}
生成热更新代码dll:
右键项目 > 生成
重新运行Hello World Demo:
查看输出结果,可以看到 我们修改的内容被输出了。
通过以上步骤,了解了IRuntime的基本运行流程。
1.热更代码生成dll
2.Unity加载dll
3.Unity调用dll里的方法
三、创建自己的Hotfix工程
从零开始创建自己的热更工程,体验完整配置流程。
1.打开Visual Studio,新建项目。
2.修改项目配置
选择类库(.NET Framework)。
修改名称为 Hotfix
修改路径为 Unity项目根目录
3.添加UnityEngine的引用
在Unity中用Visual Studio打开Unity Demo的任意一个脚本,VS会自动关联需要的Unity类库引用,在项目的引用中可以看到引用的dll路径。
在我们自己的Hotfix工程引用中添加该文件。
有两个主要的路径
(1)在Unity安装目录下的Editor\Data
我的电脑路径是:D:\SDK\Unity\2019.4.1f1\Editor\Data\Managed\UnityEngine\
把该文件夹内所有dll文件都引入到Hotfix工程里。
(2)在Unity工程根目录下的Library\ScriptAssemblies\
我的电脑路径是:
D:\Projects\Unity3D\Unity 2019.3 Projects\ILRuntimeU3D\ILRuntimeDemo\Library\ScriptAssemblies
添加自己需要的dll,我引用了以下两个dll。
Assembly-CSharp.dll
UnityEngine.UI.dll
注:Unity 2019中已经把类库拆的很零散了,应该是为了解耦,为了以后的规划。他把类库拆成两个部分,内置的类库放在Unity安装目录下了,PackageManager包管理器下载的插件都放在Unity工程目录下了,UGUI的库从内置位置移动到PackageManager里了,可能以后要淘汰掉。
4.编写 Hello World
添加完引用,可以开始写代码了。
在我们自己的Hotfix工程中添加一个类 HelloWorld.cs 并输入以下代码:
namespace Hotfix {
using UnityEngine;
// 冰封百度的Blog:https://segmentfault.com/a/1190000023183723
public class HelloWorld {
public void Test() {
Debug.Log("Hello World");
}
}
}
5.配置Hotfix.dll输出路径
右键Hotfix工程, 选择生成选项卡,在下方的输出路径里浏览到StreamingAssets文件夹或填写相对路径:..\..\Assets\StreamingAssets\
5.生成Hotfix.dll
右键Hotfix工程,选择生成。
生成成功后可以看到如下提示,如果失败则根据错误提示进行处理。
6.修改Unity Demo中的HelloWorld脚本
主要修改脚本中加载dll的名称,HotFix_Project改为Hotfix。
并且添加调用热更方法的代码。
修改后如下:
using UnityEngine;
using System.Collections;
using System.IO;
using ILRuntime.Runtime.Enviorment;
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.dll");
#else
WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/Hotfix.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.pdb");
#else
www = new WWW("file:///" + Application.streamingAssetsPath + "/Hotfix.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);
// 实例化Hotfix中的类
object obj = appdomain.Instantiate("Hotfix.HelloWorld");
appdomain.Invoke("Hotfix.HelloWorld", "Test", obj);
}
private void OnDestroy()
{
if (fs != null)
fs.Close();
if (p != null)
p.Close();
fs = null;
p = null;
}
}
运行Unity,可以看到输出了Hello World
至此,ILRuntime Hello World部分已经全部完成了。
总结:
ILRuntime使用时,除了Hotfix工程的配置稍微麻烦一些外,其他部分都不难,Demo做的很完善,基本是开箱即用。具体使用细节建议仔细阅读官方文档:
http://ourpalm.github.io/ILRu...