最近公司项目要用到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热更新技术时的学习过程,包括从导入插件、解决报错、理解官方Demo到实现自主热更项目。作者详细解释了每个步骤,涉及加载DLL、调用方法、处理错误等关键点,并分享了如何在Unity中使用ILRuntime进行热更新的方法。文章最后展示了如何通过ILRuntime实现UI交互的热更示例。
1万+

被折叠的 条评论
为什么被折叠?



