Unity 跨平台的发展历程

Unity设计的初衷是建立一个简单易用且跨平台的游戏开发引擎。而当时市面上最好的开发平台就是微软的.NET Framework,由于**.NET只能应用于Windows体系**,所以Unity又借助了MONO来实现了跨平台。Unity的底层是C++,但核心却是.NET Framework,在Unity中是可以使用.NET提供的相关工具的

.NET Framework:.NET是微软开发的**跨语言(注意不是跨平台)**开发平台,支持二三十种开发语言,用于构建和运行应用程序。这些应用程序只能在Windows体系内使用。

C#(CSharp):微软专门为.NET推出的高级编程语言。从语言技术、风格、严谨性等特点上来说,C#可称为诸多编程语言中最优秀的一款,甚至由它引发出计算机语言界中的多种新规范和新特征。C#首先发布于2002年,当时公认为C#是.NET大家庭中前景最好的语言,所以Unity选用了C#作为主要开发语言(实际上Unity还支持Unity Script和Boo语言)。

MONO:Novell公司支持在其他操作系统下开发.NET程序的框架。Unity借助MONO实现跨平台,核心是.NET Framework框架。
在这里插入图片描述
.NET Framework的作用是跨语言开发,MONO的作用的跨平台运行,Unity通过MONO实现了跨平台的发布。实际上Unity只是在早期使用了MONO作为一种工具,而并不是通常所理解的构建于MONO之上。因为基于MONO的层并没有处理很多重要的游戏任务,比如音频、渲染、物理以及时间的跟踪。Unity出于速度的考虑构建了本地C++后端,允许他的用户将MONO作为脚本编写界面,控制该游戏引擎。因此,MONO只是底层Unity引擎的一个组成部分。

具体实现方式

早期的Unity跨平台是基于MONO运行时的,又通过CLS实现了跨语言。MONO是一个开源工程,其中包含C#编译器,CLR运行时,以及一组类库。该工程常见了一系列符合ECMA标准的.NET工具,MONO使得开发者可以使用任何平台开发C#,也可以让C#在不同平台运行。其基本思路是将各个版本的语言统一编译成CIL语言,在不同平台运行时使用CLR进行解释。有点类似于Java的jdk和jvm。
总结起来就是1种通用语法、1套固定语句、1种编译器、1套运行时。

  • 1种通用语法 ==>> 通用语言规范(CLS,Common Language System):CLS是一种规范,类似于schema,当文件被指定遵守CLS规范后,在某语言中写出不符合通用规范的代码后将被警告或报错。
  • 1套固定语句 ==>> 通用中间语言(CIL,Common Intermediate Language) :CIL是一种代码指令集,可在任何支持CLI的环境运行。CIL是基于堆栈的,又是面向对象的。
  • 1种编译器 ==>> C#编译器 :通过编译器将C#代码编译为CIL代码,以供CLR使用。
  • 1套运行时 ==>> MONO版通用语言运行库(CLR,Common Language Runtime) :由于.NET只能在Windows体系内运行,MONO就提供了一套能够运行在各个平台下的CLR运行时。MONO版本的运行时也就是MONO VM(MONO虚拟机),其提供了即时编译器(JIT)、静态编译器、库加载器、垃圾回收器等功能。

编译过程

Unity C#编译细节过程如下:
在这里插入图片描述
整个过程一共两次编译:

  1. 第一次把多种语言编译为中间语言;
  2. 第二次把中间语言结合运行环境生成原生机器码,而在CLR的编译过程中又分为即时编译、静态编译及完全静态编译。

CLR编译方式

在CLR的编译过程中,存在三种编译方式:

  1. 即时编译JIT,Just-in-time):程序运行时将IL(中间语言)转为对应平台原生代码,并将原生代码映射到虚拟内存执行。
  2. 静态编译AOT,Ahead-of-time):程序运行之前将.exe或.dll文件中部分转译为目标平台原生码存储。
  3. 完全静态编译Full-AOT,Full-ahead-of-time):程序运行之前,所有代码完全编译为目标平台原生码。

JIT是运行时编译的,解释一条执行一条,解释过的语句会缓存,不重复解释,效率较低。IOS不允许JIT动态编译,所以MONO只能以Full-AOT模式运行(即将程序集所有CIL代码进行AOT编译生成本地机器码),Full-AOT模式下,泛型、反射空类型等功能可能会有问题,不允许动态生成代码,不允许动态创建类型,无法使用DLR(Dynamic Language Runtime动态语言运行时)。
AOT与JIT并非对立,AOT同样采用JIT编译,只不过是运行前就编译好了。

CLR涉及概念

  • CLI(Common Language Infrastructure)通用语言基础结构:CLI是CLR的子集,主要包括类加载器、实时编译器以及GC垃圾回收,CLI是CLR的灵魂,是CIL的摇篮。
  • CTS(Common Type System)通用类型系统,CTS是CLI的一部分,描述了各种类型应该具有怎样的行为以及可以拥有什么样的成员,CTS是一种规范,是CLR实现的依据。CLS是CTS的一部分,描述了一个框架语言需要拥有的最小集功能。
  • VES(虚拟执行系统)负责为某种语言在执行时提供一切必须的上下文和内存、线程管理、字符串驻留、垃圾回收、即时编译等服务,CLR是VES的一个实现,可以用运行时(Runtime)代称,一个VES可以提供上述所有服务,也可以只提供部分服务。

IL2CPP(跨平台过渡方案)

早先的跨平台方案是通过将各类语言编译为IL通用语言,再将通用语言放到Mono运行时中编译运行。而自从4.6.1 P5版本后,Unity就推出了IL2CPP的替代方案,其原理与MONO几乎相同,但却能规避掉MONO的众多问题,具体会在下文中进行解释。

MONO时代编译过程大致过程如下:
在这里插入图片描述
后期由于种种限制,Unity又提供了一套过渡方案,以提高程序的可用性,大致情况如下:
在这里插入图片描述

为什么要用IL2CPP过渡呢?
根据Unity官方博客给出的解释,原因有以下几个:

  1. Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成
    Mono的跨平台是通过Mono VM实现的,有几个平台,就要实现几个VM,像Unity这样支持多平台的引擎,Mono官方的VM肯定是不能满足需求的。所以针对不同的新平 台,Unity的项目组就要把VM给移植一遍,同时解决VM里面发现的bug。这非常耗时耗力。这些能移植的平台还好说,还有比如WebGL这样基于浏览 器的平台。要让WebGL支持Mono的VM几乎是不可能的。

  2. Mono版本授权受限
    虽然Mono本身是开源的,但是商业使用还是收到一定的版权限制,而低版本的MONO就无法使用C#的强大特性,大家有没有意识到Mono的版本已经更新到3.X了,但是在Unity中,C#的运行时版本一直停留在2.8。这也是Unity社区开发者抱怨的最多一 条:很多C#的新特性无法使用。这是因为Mono 授权受限,导致Unity无法升级Mono。如果换做是IL2CPP,IL2CPP VM这套完全自己开发的组件,就解决了这个问题。

  3. 提高运行效率
    因为MONO需要运行在虚拟机内,相比于编译成原生的CPP代码而言,效率非常低。根据官方的实验数据,换成IL2CPP以后,程序的运行效率有了1.5-2.0倍的提升。

几点注意:

  1. 将IL变回CPP的目的除了CPP的执行效率快以外,另一个很重要的原因是可以利用现成的在各个平台的C++编译器对代码执行编译期优化,这样可以进一步减小最终游戏的尺寸并提高游戏运行速度。
  2. 由于动态语言的特性,他们多半无需程序员太多关心内存管理,所有的内存分配和回收都由一个叫做GC(Garbage Collector)的组件完成。虽然通过IL2CPP以后代码变成了静态的C++,但是内存管理这块还是遵循CIL的方式,这也是为什么最后还要有一个IL2CPP VM的原因:它负责提供诸如GC管理,线程创建这类的服务性工作。但是由于去除了IL加载和动态解析的工作,使得IL2CPP VM可以做的很小,并且使得游戏载入时间缩短。
  3. 由于C++是一门静态语言,这就意味着我们不能使用动态语言的那些酷炫特性。运行时生成代码并执行肯定是不可能了。这就是Unity里面提到的所谓AOT(Ahead Of Time)编译而非JIT(Just In Time)编译。其实很多平台出于安全的考虑是不允许JIT的,大家最熟悉的有iOS平台,在Console游戏机上,不管是微软的Xbox360, XboxOne,还是Sony的PS3,PS4,PSV,没有一个是允许JIT的。使用了IL2CPP,就完全是AOT方式了,如果原来使用了动态特性的代码肯定会编译失败。这些代码在编译iOS平台的时候天生也会失败,所以如果你是为iOS开发的游戏代码,就不用担心了。因此就这点而言,我们开发上几乎不会感到什么问题。
  4. 有了IL2CPP,程序尺寸可以相对缩小,运行速度可以提高!
  5. 可以简单的认为,il2cpp 只是替换掉了 mono 的虚拟机实现,所以该分配堆内存的地方还是会一样的分配(可能会有某些细节的地方不一样)。使用了IL2CPP在堆内存分配方面和Mono 相比,Reserved Total 是可以下降的,而 Mono的 Reserved Total 只会上升不会下降。

动态语言和静态语言

我们常说的动、静态语言,通常是指:

  • 动态类型语言 Dynamically Typed Language:在运行期间检查数据的类型的语言。例如:Ruby\Python。这类语言编程,不会给变量指定类型,而是在附值时得到数据类型。Python是动态语言,变量只是对象的引用,变量a和b本身都没有类型,而它们的值有类型。
  • 静态类型语言 Statically Typed Language:数据类型是在编译其间检查。例如:C/C++、Java/C#。Static typing when possible, dynamic typing when needed。java是静态语言,但是有许多动态特性,比如Reflection、Annotation、Dynamic proxy。

跟Java跨平台的区别

  • Java用同一套代码,唯一的语言生成统一的jar包(class文件),通过JVM现场解释现场执行。
  • Unity可以使用不同的语言开发,运行环境也无需安装虚拟机(其实是需要MONO CLR的),不同平台要分别编译成平台所需的运行文件。
  • 两者都需要运行时进行解释操作,效率是有所损耗的。

其他

本文中所说的跨平台,只是Unity真正实现跨平台的一部分(很重要的一部分),Unity的跨平台还有针对渲染方面的跨平台,也是非常重要的,具体内容可参考【Unity】Unity Shader学习笔记(一)Unity Shader基础 等Shader相关的文章。


更多内容请查看总目录【Unity】Unity学习笔记目录整理

Logo

智屏生态联盟致力于大屏生态发展,利用大屏快应用技术降低开发者开发、发布大屏应用门槛

更多推荐