unity热更方案ILRuntime和Puerts对比?
今天刚看到腾讯的Puerts方案,查了些资料,看起来是XLua得升级版。那么Puerts和ILRuntime对比有哪些优劣呢? 前言上一节我们详细的讲解了ILRuntime游戏项目框架的启动过程,以及进入到热更项目中的入口,为我们做框架打下了扎实的基础,逻辑热更项目是用C#来完成的,所以我们在逻辑热更项目这里最大限度的保证开发与普通的Unity C#没有太大的差别,所以今天我们来设计基于逻辑热更项目的组件化开发机制,我们叫它ILRBehaviour,类似与MonoBehaviour,尽量保证所有的开发习惯与MonoBehaviour很像,由于MonoBehaviour是Unity C# 域的数据对象类型,所以我们在逻辑热更项目中无法直接使用,所以我们要自己设计一套类式的机制,主要完成3件事情:
设计ILRBehaviour接口
设计ILRBehaviour接口主要是和MonoBehaviour保持一致,MonoBehaviour常用的开发习惯主要是接口+gameObject+transform对象,后面我们在做好物理引擎的碰撞检测接口等。关于MonoBehaviour的定时器,我们后续单独设计一个定时器模块来做定时器,不去模拟MonoBehaviour的定时器机制。有了这样的考虑后,我们的ILRBehaviour的数据成员与接口就可以设计出来了,
如下:
这里和MonoBehaviour不一样的是,直接把基类的接口做成虚函数,而不是像MonoBehaviour一样通过反射查找看子类是否有这个方法。所以子类重载接口函数的时,加上override关键字。
添加,查找,删除ILRBehaviour组件实例机制
其它的查找,删除接口也类似,我这里就不一一分析了,大家可以对着源码查看。
让ILRBehaviour的特定接口在特定时机被调用
设计完添加,查找,删除ILRBehaviour组件的接口后,接下来就是要让ILRBehaviour组件机制中特定的接口在特定的时期被调用。目前热更新的特定时期,都是从Unity C#中来的,并且入口在main.cs 中,所以我们要让ILRBehaviour组件中的特定接口能调用到,就可以从main.cs 的特定接口开始,让他们调用ILRBehaviourMgr中的特定接口,然后在IlRBehaviourMgr中,我们遍历behaviourMap中的每个gameObject, 找到属于它的ILRBehaviour的List数组列表,遍历里面的每个ILRBehaviour组件实例,调用特定的接口就可以了,接下来我们以update为例,来分析:
整个过程,我整理在如图1.4-2中。
实现完这些机制以后,我们就完整的设计了一套基于ILRBehaviour的组件化开发机制,它尽可能的复合之前Unity c#的Mono的开发习惯,这样其它小伙伴就可以无缝的来在热更项目中做开发。最后上一个架构图,加深一下印象。
今天的分享就到这里了, 关注公众号,可以获取我们的ILRuntime热更教程源码, 对着源码看更酸爽。
两小时带你搞懂ILRuntime热更新 前言
ILRuntime 热更框架如何启动
上一节我们分析了ILRuntime基本开发原则与如何做到接口绑定,能使得逻辑热更项目直接调用Unity的引擎API,以及实现这个的技术原理,这一节我们来分析一个ILRuntime热更的游戏项目我们的启动流程是怎么样的?搞懂游戏项目的启动流程,对我们整个项目非常的重要。整个ILRuntime游戏项目的启动过程我们大致分成几个阶段:
对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀
启动与初始化Unity C#框架, 并实例化 ILRuntime 虚拟机
图1.3-1 启动节点与启动脚本
打开GameLanch.cs, Awake函数主要做两件事情,初始化Unity C#的框架模块, 启动检查更新最新的逻辑代码。如图 1.3-2:
图1.3-2 Awake函数做的主要事情
在图1.3-2中,ILRuntimeWrapper作为全局唯一单例的ILRuntime解释器的核心管理对象也是在这里被初始化的,接下来看下ILRuntimeWrapper.cs的启动代码,当实例化一个组件实例到GameApp节点上时,调用ILRuntimeWrapper的Awake函数,这个函数的主要内容就是new 了一个ILRuntime的解释器对象实例appdomain, 如图1.3-3
图1.3-3 实例化AppDomain解释器
这样第一阶段 Unity C#框架+ILRuntiem解释器初始化与启动就完成了。
检查更新最新逻辑热更.dll, 并装载字节码进虚拟机
下载以后,读取逻辑热更新.dll, 获得dll的二进制字节码的文件内容,如图1.3-4:
图1.3-4 加载.dll的二进制数据
然后调用AppDomain解释器接口,装载二进制字节码,进入到ILRuntime解释器。
ILRuntimeWrapper.Instance.LoadHotFixAssembly(dll, pdb);
如图1.3-5,为:
图1.3-5 LoadHotFixAssembly具体实现
自此,第二步就顺利完成了,然后调用ILRuntimeWrapper.EnterGame进入游戏。
引入Unity的生命周期入口到逻辑热更项目入口
完成第二步以后,ILRuntime虚拟机已经做好了解释执行的准备,而且现在装载的是最新的游戏逻辑代码与下载了最新的游戏资源,接下来就是要把代码跑入到热更项目.dll里面,让ILRuntime解释器,开始解释执行游戏逻辑代码,同时游戏逻辑代码调用Unity接口,来完成对应的逻辑功能。这个如何做呢?做游戏其实就是Init+update+FixedUpdate等常用的入口,我们的思路就是在ILRuntimerWrapper.cs里,当C#调用它的相应接口的时候,让它再调用逻辑热更.dll中的接口。这样逻辑热更项目就有了执行入口。我们在逻辑热更项目中,我们编写一个main.cs, 编写一个main类作为入口类,同时入口类里面有入口方法,如Init, Update, LateUpdate, FilxedUpdate等。如图1.3-6:
图1.3-6 逻辑热更项目main.cs中几个入口函数
我们在EnterGame这里调用热更逻辑项目main的Init函数,在Update, LateUpdate, FixedUpdate也是做类似的操作,调用逻辑热更项目main中对应的入口,如图所示1.3-7:
图1.3-7 ILRuntime解释器掉热更项目入口
这样,我们的代码就由Unity C# 跑入了逻辑热更项目的代码中了,同时逻辑热更项目又调用Unity引擎API完成游戏逻辑,这样我们就可以在逻辑热更项目中很愉快的开发游戏了。逻辑热更项目中的其它框架比如:事件管理,定时器管理,UI管理等,都是基于这个机制来实现。
今天的分享就到这里了, 关注公众号,可以获取我们的ILRuntime热更教程源码, 对着源码看更酸爽
附:视频教程
Unity / 热更新+AI专题 两小时带你搞懂ILRuntime热更新www.bycwedu.com/promotion_channels/1188508056www.bycwedu.com/promotion_channels/1188508056 前言
上一节我们讲解了ILRuntime热更新的技术原理,根据技术原理来搭建ILRuntime的开发环境。对ILRuntime有初步的了解,并且安装了ILRuntime的运行环境与实例化了ILRuntime解释器,创建了生成.net字节码的.dll逻辑热更项目, 并把.dll生成到Unity项目的StreammingAssets下方便运行时加载,而热更新能够热更的就是逻辑热更项目生成的.dll。本节分析ILRuntime基本开发原则与如何做到接口绑定,能使得逻辑热更项目直接调用Unity的引擎API。
ILRuntime开发的基本原则
我们上几个截图,来看下一个ILRuntime的热更的案例Demo:
图1.2-1:使用Unity做好的地图资源,不挂任何逻辑脚本
图1.2-2:使用Unity做好的UI资源,不挂任何逻辑脚本
图1.2-3:加载最新的.dll中的二进制数据
图1.2-4:ILRuntime解释器装载二进制代码
图1.2-5:进入逻辑热更入口函数
图1.2-6逻辑热入口函数
图1.2-7调用Unity 的引擎接口来生成游戏内容
图1.2-8运行的游戏效果
如何做到热更项目工程解释使用C#工程的接口
很多读者很好奇,明明的热更项目与Unity C#是两个不同的”域”,为什么热更项目可以直接调用Unity C#提供的接口呢?其实这个的技术原理主要依赖于.net的.dll。我们在做.dll开发的时候,如果调用了别的.dll库,我们生成.dll的时候,会给调用别的库的接口做一个”符号”,当最终运行的时候,先根据符号,加载我们依赖的别的库的.dll,这样就运行时定位到”这些接口符号所在的地址”,在加载我们开发的.dll, 然后对我们使用别的库的”符号”进行重定向,这样当运行的时候,在自己开发的这个.dll,调用别人开发的.dll库中函数的时候,由于启动做了重定向地址,就能在运行中跳到别人开发的.dll库中的函数去。
当我们使用逻辑热更项目进行逻辑开发的时候,我们要把使用的Unity引擎相关的.dll的引用用,要拉入到我们逻辑热更项目中,这样当我们在逻辑热更项目中调用Unity引擎相关的组件函数的时候,会生成”依赖符号”到生成的 逻辑热更.dll中。这样也就解释了为什么逻辑热更项目中要添加Unity .dll库的引用,其实就是为逻辑热更.dll生成Unity引擎的引用符号。
图1.2-9热更项目引用系统库与Unity引擎库
当ILRuntime加载逻辑热更.dll的时候,会把逻辑热更.dll的符号表重新定向到运行中的对应的位置,这样在用户毫无察觉的情况下完成了逻辑热更项目可以调用Unity 引擎接口的绑定。 1.介绍
ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现,快速、方便且可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新。学习交流聚集地
介绍 — ILRuntime (http://ourpalm.github.io)
https://ourpalm.github.io/ILRuntime/public/v1/guide/index.html
ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp websocket协议,支持服务端3D recast寻路等等 。
GitHub - egametang/ET: Unity3D Client And C# Server Framework
https://github.com/egametang/ET.git
2.接入ILRuntime
1.BuildAssemblieEditor.cs
构建codes.dll和codes.pdb到unity工程中并打上ab标签
Unitywww.bycwedu.com/promotion_channels/2146264125
public static class BuildAssemblieEditor
{
//dll复制到unity工程的路径
private const string CodeDir = "Assets/Bundles/Code/";
public static void BuildCode()
{
//将codes目录下的所有cs文件打成code.dll
BuildAssemblieEditor.BuildMuteAssembly("Code", new []
{
"Codes/Model/",
"Codes/ModelView/",
"Codes/Hotfix/",
"Codes/HotfixView/"
}, Array.Empty<string>());
//将code.dll复制到unity工程路径下并打上ab标签
AfterCompiling();
//刷新资源
AssetDatabase.Refresh();
}
private static void BuildMuteAssembly(string assemblyName, string[] CodeDirectorys, string[] additionalReferences)
{
//获取CodeDirectorys路径下的所有cs文件
List<string> scripts = new List<string>();
for (int i = 0; i < CodeDirectorys.Length; i++)
{
DirectoryInfo dti = new DirectoryInfo(CodeDirectorys);
FileInfo[] fileInfos = dti.GetFiles(&#34;*.cs&#34;, System.IO.SearchOption.AllDirectories);
for (int j = 0; j < fileInfos.Length; j++)
{
scripts.Add(fileInfos.FullName);
}
}
//编译dll的路径
string dllPath = Path.Combine(Define.BuildOutputDir, $&#34;{assemblyName}.dll&#34;);
string pdbPath = Path.Combine(Define.BuildOutputDir, $&#34;{assemblyName}.pdb&#34;);
File.Delete(dllPath);
File.Delete(pdbPath);
Directory.CreateDirectory(Define.BuildOutputDir);
AssemblyBuilder assemblyBuilder = new AssemblyBuilder(dllPath, scripts.ToArray());
//启用UnSafe
//assemblyBuilder.compilerOptions.AllowUnsafeCode = true;
BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
assemblyBuilder.compilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup);
// assemblyBuilder.compilerOptions.ApiCompatibilityLevel = ApiCompatibilityLevel.NET_4_6;
//传递给程序集编译的其他程序集引用。
assemblyBuilder.additionalReferences = additionalReferences;
assemblyBuilder.flags = AssemblyBuilderFlags.DevelopmentBuild;
//AssemblyBuilderFlags.None 正常发布
//AssemblyBuilderFlags.DevelopmentBuild 开发模式打包
//AssemblyBuilderFlags.EditorAssembly 编辑器状态
assemblyBuilder.referencesOptions = ReferencesOptions.UseEngineModules;
assemblyBuilder.buildTarget = EditorUserBuildSettings.activeBuildTarget;
assemblyBuilder.buildTargetGroup = buildTargetGroup;
//编译开始回调
assemblyBuilder.buildStarted += delegate(string assemblyPath) { Debug.LogFormat(&#34;build start:&#34; + assemblyPath); };
//编译结束回调
assemblyBuilder.buildFinished += delegate(string assemblyPath, CompilerMessage[] compilerMessages)
{
int errorCount = compilerMessages.Count(m => m.type == CompilerMessageType.Error);
int warningCount = compilerMessages.Count(m => m.type == CompilerMessageType.Warning);
Debug.LogFormat(&#34;Warnings: {0} - Errors: {1}&#34;, warningCount, errorCount);
if (warningCount > 0)
{
Debug.LogFormat(&#34;有{0}个Warning!!!&#34;, warningCount);
}
if (errorCount > 0)
{
for (int i = 0; i < compilerMessages.Length; i++)
{
if (compilerMessages.type == CompilerMessageType.Error)
{
Debug.LogError(compilerMessages.message);
}
}
}
};
//开始构建
if (!assemblyBuilder.Build())
{
Debug.LogErrorFormat(&#34;build fail:&#34; + assemblyBuilder.assemblyPath);
return;
}
}
private static void AfterCompiling()
{
//编译中
while (EditorApplication.isCompiling)
{
Debug.Log(&#34;Compiling wait1&#34;);
// 主线程sleep并不影响编译线程
Thread.Sleep(1000);
Debug.Log(&#34;Compiling wait2&#34;);
}
Debug.Log(&#34;Compiling finish&#34;);
//将dll和pdb拷贝到unity工程中
Directory.CreateDirectory(CodeDir);
File.Copy(Path.Combine(Define.BuildOutputDir, &#34;Code.dll&#34;), Path.Combine(CodeDir, &#34;Code.dll.bytes&#34;), true);
File.Copy(Path.Combine(Define.BuildOutputDir, &#34;Code.pdb&#34;), Path.Combine(CodeDir, &#34;Code.pdb.bytes&#34;), true);
AssetDatabase.Refresh();
Debug.Log(&#34;copy Code.dll to Bundles/Code success!&#34;);
// 设置ab包
AssetImporter assetImporter1 = AssetImporter.GetAtPath(&#34;Assets/Bundles/Code/Code.dll.bytes&#34;);
assetImporter1.assetBundleName = &#34;Code.unity3d&#34;;
AssetImporter assetImporter2 = AssetImporter.GetAtPath(&#34;Assets/Bundles/Code/Code.pdb.bytes&#34;);
assetImporter2.assetBundleName = &#34;Code.unity3d&#34;;
AssetDatabase.Refresh();
Debug.Log(&#34;set assetbundle success!&#34;);
Debug.Log(&#34;build success!&#34;);
}
}2.CodeLoader.cs
初始化ILRuntime并启动热更层开始函数
case Define.CodeMode_ILRuntime:
{
//从ab包中加载dll和pdb
Dictionary<string, UnityEngine.Object> dictionary = AssetsBundleHelper.LoadBundle(&#34;code.unity3d&#34;);
byte[] assBytes = ((TextAsset)dictionary[&#34;Code.dll&#34;]).bytes;
byte[] pdbBytes = ((TextAsset)dictionary[&#34;Code.pdb&#34;]).bytes;
AppDomain appDomain = new AppDomain();
MemoryStream assStream = new MemoryStream(assBytes);
MemoryStream pdbStream = new MemoryStream(pdbBytes);
//ILRuntime加载程序集
appDomain.LoadAssembly(assStream, pdbStream, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
//注册委托适配器等
ILHelper.InitILRuntime(appDomain);
//缓存所有热更反射类型
this.allTypes = appDomain.LoadedTypes.Values.Select(x => x.ReflectionType).ToArray();
//调用到热更层的entry类的start方法
IStaticMethod start = new ILStaticMethod(appDomain, &#34;ET.Entry&#34;, &#34;Start&#34;, 0);
start.Run();
break;
}3.ILHelper.cs
注册重定向函数,委托,适配器,clr绑定
public static class ILHelper
{
public static List<Type> list = new List<Type>();
public static void InitILRuntime(ILRuntime.Runtime.Enviorment.AppDomain appdomain)
{
// 注册重定向函数
list.Add(typeof(Dictionary<int, ILTypeInstance>));
list.Add(typeof(Dictionary<int, int>));
list.Add(typeof(Dictionary<object, object>));
list.Add(typeof(Dictionary<int, object>));
list.Add(typeof(Dictionary<long, object>));
list.Add(typeof(Dictionary<long, int>));
list.Add(typeof(Dictionary<int, long>));
list.Add(typeof(Dictionary<string, long>));
list.Add(typeof(Dictionary<string, int>));
list.Add(typeof(Dictionary<string, object>));
list.Add(typeof(List<ILTypeInstance>));
list.Add(typeof(List<int>));
list.Add(typeof(List<long>));
list.Add(typeof(List<string>));
list.Add(typeof(List<object>));
list.Add(typeof(ListComponent<ILTypeInstance>));
list.Add(typeof(ETTask<int>));
list.Add(typeof(ETTask<long>));
list.Add(typeof(ETTask<string>));
list.Add(typeof(ETTask<object>));
list.Add(typeof(ETTask<AssetBundle>));
list.Add(typeof(ETTask<UnityEngine.Object[]>));
list.Add(typeof(ListComponent<ETTask>));
list.Add(typeof(ListComponent<Vector3>));
// 注册委托
appdomain.DelegateManager.RegisterMethodDelegate<List<object>>();
appdomain.DelegateManager.RegisterMethodDelegate<object>();
appdomain.DelegateManager.RegisterMethodDelegate<bool>();
appdomain.DelegateManager.RegisterMethodDelegate<string>();
appdomain.DelegateManager.RegisterMethodDelegate<float>();
appdomain.DelegateManager.RegisterMethodDelegate<long, int>();
appdomain.DelegateManager.RegisterMethodDelegate<long, MemoryStream>();
appdomain.DelegateManager.RegisterMethodDelegate<long, IPEndPoint>();
appdomain.DelegateManager.RegisterMethodDelegate<ILTypeInstance>();
appdomain.DelegateManager.RegisterMethodDelegate<AsyncOperation>();
appdomain.DelegateManager.RegisterFunctionDelegate<UnityEngine.Events.UnityAction>();
appdomain.DelegateManager.RegisterFunctionDelegate<System.Object, ET.ETTask>();
appdomain.DelegateManager.RegisterFunctionDelegate<ILTypeInstance, bool>();
appdomain.DelegateManager.RegisterFunctionDelegate<System.Collections.Generic.KeyValuePair<System.String, System.Int32>, System.String>();
appdomain.DelegateManager.RegisterFunctionDelegate<System.Collections.Generic.KeyValuePair<System.Int32, System.Int32>, System.Boolean>();
appdomain.DelegateManager.RegisterFunctionDelegate<System.Collections.Generic.KeyValuePair<System.String, System.Int32>, System.Int32>();
appdomain.DelegateManager.RegisterFunctionDelegate<List<int>, int>();
appdomain.DelegateManager.RegisterFunctionDelegate<List<int>, bool>();
appdomain.DelegateManager.RegisterFunctionDelegate<int, bool>();//Linq
appdomain.DelegateManager.RegisterFunctionDelegate<int, int, int>();//Linq
appdomain.DelegateManager.RegisterFunctionDelegate<KeyValuePair<int, List<int>>, bool>();
appdomain.DelegateManager.RegisterFunctionDelegate<KeyValuePair<int, int>, KeyValuePair<int, int>, int>();
appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((act) =>
{
return new UnityEngine.Events.UnityAction(() =>
{
((Action)act)();
});
});
appdomain.DelegateManager.RegisterDelegateConvertor<Comparison<KeyValuePair<int, int>>>((act) =>
{
return new Comparison<KeyValuePair<int, int>>((x, y) =>
{
return ((Func<KeyValuePair<int, int>, KeyValuePair<int, int>, int>)act)(x, y);
});
});
// 注册适配器
RegisterAdaptor(appdomain);
//注册Json的CLR
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appdomain);
//注册ProtoBuf的CLR
PType.RegisterILRuntimeCLRRedirection(appdomain);
//clr绑定初始化
CLRBindings.Initialize(appdomain);
}
public static void RegisterAdaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain)
{
//注册自己写的适配器
appdomain.RegisterCrossBindingAdaptor(new IAsyncStateMachineClassInheritanceAdaptor());
}
}
页:
[1]