Unity跨平台开发与IL2CPP优化打包详解
Unity跨平台开发与IL2CPP优化打包详解
在游戏开发中,跨平台支持是一个重要的需求。Unity作为一款流行的游戏引擎,提供了多种技术方案来实现这一目标。本文将介绍Unity如何利用.NET技术实现跨平台功能,以及如何使用IL2CPP进行优化打包。
一、Unity如何实现跨平台
1. 什么是 .NET
.NET 是微软推出的一整套技术体系,它不是一个编程语言也不是一个框架,而是用来开发应用程序的技术平台。你可以把它想象成一个大工具箱,里面有许多不同的工具(如编程语言、库和工具),帮助开发者创建各种类型的应用程序。这个平台支持多种编程语言(如 C#、VB.NET 等),并且可以让这些语言之间相互协作。
2. .NET 的跨语言特性
为了让不同语言编写的代码能够一起工作,.NET 定义了一组规则,确保所有语言都能遵循这些规则来编写代码。这就好比是制定一套交通规则,让所有的车辆在路上安全行驶。在 .NET 中,有以下几个关键概念:
- CLS (Common Language Specification):公共语言规范,是一组语言互操作性的标准,保证不同语言的代码可以互相调用。
- CTS (Common Type System):公共类型系统,定义了所有语言必须遵守的数据类型和结构,使得不同语言中的数据可以相互通信。
- CLI (Common Language Infrastructure):公共语言基础结构,包含了 CTS 和其他必要的组件,是一个工业标准,确保 .NET 应用可以在任何实现了 CLI 的平台上运行。
3. .NET 的跨平台特性
早期的 .NET 主要是为了 Windows 操作系统设计的(即 .NET Framework)。后来为了实现跨平台,微软推出了 .NET Core,这是一个完全开源且能够在多个操作系统上运行的新版本。此外,还有一个叫做 Mono 的项目,在 .NET Core 出现之前就已经实现了跨平台的功能。Mono 是由第三方公司 Xamarin 开发的,后来被微软收购了。
- .NET Framework:主要用于 Windows 上的应用开发。
- .NET Core:用于跨平台应用开发,支持 Windows、macOS 和 Linux。
- Mono:提供了一个额外的选择,允许 .NET 应用在更多类型的设备上运行,包括游戏主机等。
4. Unity 和 .NET 的关系
Unity 使用了 .NET 技术栈作为其脚本后端。具体来说,Unity 的底层是由 C++ 编写的引擎核心,而上层逻辑则主要通过 C# 来编写。为了使 C# 代码能够在不同的平台上运行,Unity 使用了 Mono 或者后来引入的 IL2CPP 技术。这两种技术都是基于 .NET 的公共语言基础结构 (CLI) 来工作的。
- Mono:这是 Unity 最初采用的方式,它将 C# 代码编译为中间语言 (IL),然后在目标平台上使用虚拟机 (VM) 将其转换为本地机器码执行。
- IL2CPP:这是一种较新的方法,它会将 C# 代码先编译为 C++ 代码,再由 C++ 编译器生成针对特定平台优化后的二进制文件。这种方法通常能带来更好的性能,并且更容易集成到不同的操作系统中。
5. IL2CPP 的优势与挑战
IL2CPP 提供了一些显著的优点,比如更高的运行效率和更小的应用体积。然而,它也有一些局限性,例如无法像 Mono 那样动态生成代码,这意味着你必须提前确定所有要用到的类型。如果某些类型是在运行时才决定使用的(例如通过反射或泛型),那么你需要采取特别措施来确保它们不会被裁剪掉。
6. IL2CPP和Mono性能对比
IL2CPP的代码执行效率是高于Mono的。主要原因:
- Mono是JIT即时编译,IL2CPP是AOT提前编译。
- AOT的优势是在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗。
- 可以在程序运行初期就达到最高性能,可以显著的加快程序的启动。
- 再加上IL2CPP的原生C++代码加持,整体而言IL2CPP的效率在Unity下是高于Mono的。
7. 总结
对于新手来说,最重要的是理解 Unity 如何利用 .NET 技术来实现跨平台功能。无论是选择 Mono 还是 IL2CPP,都是为了让你的游戏可以在尽可能多的不同设备上顺利运行。随着 Unity 不断改进其构建流程和技术栈,IL2CPP 已经成为推荐的打包方式,因为它提供了更好的性能和更广泛的平台支持。
二、设置IL2CPP
1. 修改打包配置
在Unity编辑器中,进入 ProjectSetting -> Player -> OtherSetting -> Configuration -> Scripting Backend
,将脚本后端设置成IL2CPP。
2. 安装IL2CPP模块
如果还没有下载对应平台的IL2CPP包,BuildSetting会报错。需要先安装对应的IL2CPP模块,比如安装windows的IL2CPP模块,安装完成后重启工程。
三、IL2CPP打包时的类型裁剪问题
IL2CPP在打包时会对你项目中的代码进行“裁剪”,也就是去掉那些在代码中没有被用到的部分。这么做是为了减小游戏包的大小,提高运行效率。但有时候,一些你并没有直接调用的类型或者代码会被错误地删除,导致运行时出现找不到某个类型的错误,特别是在用到反射等动态调用时。
如何解决?
1. 调整剥离级别
在Unity的设置中,有一个叫做“Managed Stripping Level”的选项,可以选择不同的级别,控制IL2CPP的裁剪程度:
- Minimal(最小):这是最安全的选择。好像是unity6新增的选项,本来最低只有Low。默认选择这个。
- Low(低):尽量避免删除重要代码,只有最不常用的代码会被删掉。
- Medium(中):中等程度的裁剪,可能会删掉一些不常用的代码,但不会删掉核心代码。
- High(高):最激进的裁剪,尽量删除所有未用代码,能有效减小包的大小,但需要小心可能会删掉你需要的代码。
2. 使用Link.xml文件
你可以通过在Unity项目中(或其任何子目录中)创建一个Link.xml
文件,告诉Unity哪些类型不能被删掉,确保它们在打包时不会被裁剪掉。
用的最多应该就是下面这种,比如保留MyGame程序集下的A整个类型
<?xml version="1.0" encoding="UTF-8"?>
<assembly fullname="Assembly-CSharp">
<type fullname="MyGame.A" preserve="all"/>
</assembly>
3. 最佳实战
我们可以把代码剥离等级设置为高,打包出去,出现报错时,再在裁剪类link.xml中添加保留对应报错类的代码。但是这可能比较考验测试人员能力,假如没测出来有一定风险。
四、IL2CPP打包时的泛型问题
在IL2CPP中,由于它在编译时必须知道所有需要的类型和代码,如果你没有在打包前明确地使用某些泛型类型,它们可能会被裁剪掉,导致运行时找不到相关类型。例如,假设你有两个泛型列表:List<A>
和List<B>
,其中A和B是你自定义的类。
如果你在代码中没有显式地使用这些泛型(比如没有写出List<A>
和List<B>
),那么在打包时,这些类型可能会被裁剪掉。如果你后续在热更新时想使用List<C>
,但是之前并没有显示使用过它,程序就会出错。主要就是因为JIT和AOT两个编译模式的不同造成的。
1. 解决方案
显示使用泛型类型:在代码中显式地声明并使用泛型类型,确保它们在编译时被处理。例如,可以在代码中声明一个包含List<A>
和List<B>
的类,或者编写一个泛型方法,在其中使用这些类型。这样做的目的是告诉IL2CPP,你会在运行时使用这些泛型类型,避免它们被裁剪掉。
2. 泛型类:
声明一个类,然后在这个类中声明一些public的泛型类变量
public class IL2CPP_Info
{
public List<A> list;
public List<B> list2;
public List<C> list3;
public Dictionary<int, string> dic = new Dictionary<int, string>();
}
3. 泛型方法:
随便写一个静态方法,在将这个泛型方法在其中调用一下。这个静态方法无需被调用,这样做的目的其实就是在预编译之前让IL2CPP知道我们需要使用这个内容
public class IL2CPP_Info
{
public void Test<T>(T info)
{
}
public static void StaticMethod()
{
IL2CPP_Info info = new IL2CPP_Info();
info.Test<int>(1);
info.Test<float>(1);
info.Test<bool>(true);
}
}
总结
- 对于新项目,建议使用IL2CPP打包方式,因为它比Mono打包更高效,生成的包也会更小。
- 如果你遇到类型找不到的问题,可以通过调整剥离级别或者使用Link.xml文件来确保所需的类型不会被裁剪掉。
- 如果你使用了泛型,记得在代码中显式地调用这些泛型类型,以确保它们不会被错误地裁剪。