目录
前言
编辑
Mono
Unity和Mono的关系
Unity跨平台必备概念
Mono利用
Mono主要构成部分
基于Mono跨平台的优缺点
IL2CPP
Mono和IL2CPP的区别
Mono
IL2CPP
Mono和IL2CPP的使用建议
安装IL2CPP
IL2CPP打包存在的问题
类型裁剪
泛型问题
前言
Unity跨平台的基本原理主要基于其底层架构和Mono的运行机制。以下是对Unity跨平台基本原理的详细阐述。
Mono
Unity和Mono的关系
Unity公司于2004年成立。Unity的底层是通过C/C++来完成的,但是为了更方便的让开发者使用,
Mono在当时成为了不二之选,它同时具备跨平台和跨语言的两个特性。
Unity希望能有更多的开发者使用其进行游戏开发,并且一次开发一劳永逸,所以跨语言和跨平台对于他们来说是很重要的。虽然C++本身跨平台,但是如果使用C++作为上层逻辑开发语言,那么作为开发者来说选择性相对较少,并且对于初学者来说学习难度也较大,所以当时的Mono是非常满足Unity需求的不仅支持跨语言还支持跨平台。
Unity跨平台必备概念
Unity主要包括两个部分
Unity Engine(引擎)
包含平台相关代码提供UnityEngine.dll动态库,各平台不同,C/C++编写,图形AP1、物理引擎、灯光等等所有游戏引擎底层内容
Unity Editor(编辑器)
提供UnityEditor.dll动态库,大部分由C#编写,用户脚本最初可以使用C#JavaScript、Boo语言编写,项目代码最后由Mono编译
Mono利用
.Net平台制定的CLI公共语言基础结构规则,利用它我们可以把很多种语言编译成通用规范的CIL公共中间语言,再利用CLR公共语言运行时,将这些CIL公共中间语言转换为对应操作系统的原生代码。
这样用各种不同语言编写的逻辑就能够在指定操作系统上运行了,它的这一套规则是在.Net Framework规则上进行的修改和添加。
Mono主要构成部分
1.C#编译器(mcs)
2.Mono Runtime 类似CLR公共语言运行时(虚拟机)
包括JIT(Just in time)即时编译器、AOT(Ahead of time)提前编译器、GC、类库加载器 等等
3.BCL基础类库
4.Mono类库
提供很多超出.Net的一些额外功能,主要用于构建各种操作系统上的应用
在Unity下使用各种语言进行逻辑实现
这些语言在发布时会被编译成IL中间代码
最终这些中间代码在对应操作系统上
通过Mono VM(虚拟机)真正翻译成机器码运行起来
基于Mono跨平台的优缺点
优点:
只要在不同操作系统上实现MonoVM(虚拟机)
那我们能够支持的平台就会“无限”多
缺点:
维护工作耗时耗力,当Unity版本更新时,MonoVM也需要维护和更新
那多对于N多个平台来说,工作量是非常大的。(Unity的工作量)
低版本Mono无法支持新版本C#的强大新特性
IL2CPP
IL2CPP是在Unity4.6.1 p5之后的版本中加入的脚本后处理方式
你可以把它简单理解为是继Mono之后的一种跨平台解决方案
顾名思义就是把IL中间代码转译为CPP代码(C++)
通过IL2CPP我们可以将编译好的LL中间代码转译成C++代码
再利用各平台优化过的编译器编译为对应平台的目标代码
IL2CPP和Mono的区别就在于当生成了IL中间代码后
Mono是直接通过虚拟机转译运行
而IL2CPP的步骤多了一些
会将儿中间代码转译为C++代码
再通过各平台的C++编译器直接编译为可执行的原生汇编代码
需要注意的是
虽然中间代码变为了C++
但是内存管理还是遵循C#中GC的方式
这也是为什么有一个IL2CPP VM(虚拟机)存在的原因,它主要是用来完成GC管理,线程创建等服务工作的
Mono和IL2CPP的区别
Mono
1.构建(最终打包时)速度快
Oher I DLL
2.Mono编译机制是川T即时编译,所以支持更多类库
3.必须将代码发布为托管程序集(.d文件)
4.Mono VM虚拟机平台维护麻烦,且部分平台不支持(WebGL)
5.由于Mono版本授权原因,C#很多新特性无法使用
6.IOS支持Mono,但不在允许32位的Mono应用提交到应用商店
IL2CPP
1.相对Mono构建(最终打包时)速度慢
2.只支持AOT提前编译
3.可以启用引擎代码剥离来减少代码的大小
4.程序的运行效率比Mono高,运行速度快
5多平台移植更加方便
Mono和IL2CPP的最大区别就是IL2CPP不能在运行时动态生成代码和类型,所以必须在编译时就完全确定需要用到的类型。举例:List<A>和List<B>中A和B是我们自定义的类,我能必须在代码中显示的调用过,IL2CPP才能保留List<A>和List<B>两个类型。如果在热更新时我们调用List<C>,但是它之前并没有在代码中显示调用过,那么这时就会出现报错等问题。主要就是因为川T和AOT两个编译模式的不同造成的。
Mono和IL2CPP的使用建议
由于IL2CPP的运行效率有很大优势,所以建议大家在实际开发中直接使用IL2CPP模式进行打包
安装IL2CPP
编辑器中添加模块选择目标平台带有IL2CPP支持项的。
IL2CPP打包存在的问题
类型裁剪
IL2CPP在打包时会自动对Unity工程的DLL进行裁剪,将代码中没有引用到的类型裁剪掉,以达到减小发布后包的尺寸的目的。然而在实际使用过程中,很多类型有可能会被意外剪裁掉,造成运行时抛出找不到某个类型的异常。特别是通过反射等方式在编译时无法得知的函数调用,在运行时都很有可能遇到问题
解决方案:
1.IL2CPP处理模式时,将PlayerSetting->Other Setting->Managed Stripping Level(代码剥离)设置为Low
Disable:Mono模式下才能设置为不删除任何代码
Low:默认低级别,保守的删除代码,删除大多数无法访问的代码,同时也最大程度减少剥离实际使用的代码的可能性
Medium:中等级别,不如低级别剥离谨慎,也不会达到高级别的极端
Hight:高级别,尽可能多的删除无法访问的代码,有限优化尺寸减小。如果选择该模式一般需要配合link.xml使用
2.通过Unity提供的link.xml方式来告诉Unity引擎,哪些类型是不能够被剪裁掉的
在Unity工程的Assets目录中(或其任何子目录中)建立一个叫link.xml的XML文件
泛型问题
IL2CPP和Mono最大的区别,是不能在运行时动态生成代码和类型。就是说 泛型相关的内容,如果你在打包生成前没有把之后想要使用的泛型类型显示使用一次,那么之后如果使用没有被编译的类型,就会出现找不到类型的报错。
举例:List<A>和List<B>中A和B是我们自定义的类,我们必须在代码中显示的调用过,IL2CPP才能保留List<A>和List<B>两个类型。如果在热更新时我们调用List<C>,但是它之前并没有在代码中显示调用过,那么这时就会出现报错等问题。主要就是因为JIT和AOT两个编译模式的不同造成的。
List<A> list = new List<A>();
List<B> list2 = new List<B>();
解决方案:
泛型类:声明一个类,然后在这个类中声明一些public的泛型类变量
泛型方法:随便写一个静态方法,在将这个泛型方法在其中调用一下。这个静态方法无需被调用,
这样做的目的其实就是在预言编译之前让IL2CPP知道我们需要使用这个内容。