【unity进阶篇】unity如何实现跨平台及unity最优最小包体打包方式(.NET、Mono和IL2CPP知识介绍)

考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇

  • 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的同学入门。
  • 【unity通用篇】:主要讲解unity的基础通用的知识,包括unity界面、unity脚本、unity资源管理、unity动画、unity摄像机等,适合unity初学者入门。
  • 【unity3D篇】:主要讲解unity3D的知识,unity3D角色、unity3D物理系统、unity3D光照等,适合只想做2D游戏的开发者学习。
  • 【unity2D篇】unity2D篇:主要讲解unity2D的知识,包括unity2D角色、unity2D物理系统、unity2D光照等,适合只想做3D游戏的开发者学习。

这样方便大家按需选择性的去学习,比如有编程基础的大概率可以直接从unity通用篇开始入门,没有编程基础的建议从C#篇开始学习。只想做2D游戏的话,可以直接从unity2D篇开始学习,3D篇大概率就不需要看了,节约学习时间成本。

文章目录

一、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、修改打包配置

ProjectSetting->Player->OtherSetting->Configuration->Scripting Backend 把脚本后端设置成IL2CPP

2、安装IL2CPP模块

假如没有下载对应平台的IL2CPP包 BuildSetting会报错

要先安装对应的IL2CPP模块,比如我们安装windows的IL2CPP模块,安装完成后重启工程

三、IL2CPP打包时的类型裁剪问题

IL2CPP 是Unity用来将C#代码转换为C++代码的技术,在打包时,它会对你项目中的代码进行"裁剪",也就是去掉那些在代码中没有被用到的部分。这么做是为了减小游戏包的大小,提高运行效率。但有时候,一些你并没有直接调用的类型或者代码会被错误地删除,导致运行时出现找不到某个类型的错误,特别是在用到反射等动态调用时。

如何解决?

1、调整剥离级别

在Unity的设置中,有一个叫做"Managed Stripping Level"的选项,可以选择不同的级别,控制IL2CPP的裁剪程度:

  • Minimal(最小):这是最安全的选择。好像是unity6新增的选项,本来最低只有Low。默认选择这个。
  • Low(低):尽量避免删除重要代码,只有最不常用的代码会被删掉。
  • Medium(中):中等程度的裁剪,可能会删掉一些不常用的代码,但不会删掉核心代码。
  • High (高):最激进的裁剪,尽量删除所有未用代码,能有效减小包的大小,但需要小心可能会删掉你需要的代码。

2、使用Link.xml文件

你可以通过在Unity项目中(或其任何子目录中)创建一个Link.xml文件,告诉Unity哪些类型不能被删掉,确保它们在打包时不会被裁剪掉。

link.xml语法规则

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
  
  <!--保存整个程序集-->
  <assembly fullname="UnityEngine" preserve="all"/>
  <!--没有"preserve"属性,也没有指定类型意味着保留所有-->
  <assembly fullname="UnityEngine"/>

  <!--完全限定程序集名称-->
  <assembly fullname="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
    <type fullname="Assembly-CSharp.Foo" preserve="all"/>
  </assembly>

  <!--在程序集中保留类型和成员-->
  <assembly fullname="Assembly-CSharp">
    <!--保留整个类型-->
    <type fullname="MyGame.A" preserve="all"/>
    <!--没有"保留"属性,也没有指定成员 意味着保留所有成员-->
    <type fullname="MyGame.B"/>
    <!--保留类型上的所有字段-->
    <type fullname="MyGame.C" preserve="fields"/>
    <!--保留类型上的所有方法-->
    <type fullname="MyGame.D" preserve="methods"/>
    <!--只保留类型-->
    <type fullname="MyGame.E" preserve="nothing"/>
    <!--仅保留类型的特定成员-->
    <type fullname="MyGame.F">
      <!--类型和名称保留-->
      <field signature="System.Int32 field1" />
      <!--按名称而不是签名保留字段-->
      <field name="field2" />
      <!--方法-->
      <method signature="System.Void Method1()" />
      <!--保留带有参数的方法-->
      <method signature="System.Void Method2(System.Int32,System.String)" />
      <!--按名称保留方法-->
      <method name="Method3" />

      <!--属性-->
      <!--保留属性-->
      <property signature="System.Int32 Property1" />
      <property signature="System.Int32 Property2" accessors="all" />
      <!--保留属性、其支持字段(如果存在)和getter方法-->
      <property signature="System.Int32 Property3" accessors="get" />
      <!--保留属性、其支持字段(如果存在)和setter方法-->
      <property signature="System.Int32 Property4" accessors="set" />
      <!--按名称保留属性-->
      <property name="Property5" />

      <!--事件-->
      <!--保存事件及其支持字段(如果存在),添加和删除方法-->
      <event signature="System.EventHandler Event1" />
      <!--根据名字保留事件-->
      <event name="Event2" />
    </type>

    <!--泛型相关保留-->
    <type fullname="MyGame.G`1">
      <!--保留带有泛型的字段-->
      <field signature="System.Collections.Generic.List`1&lt;System.Int32&gt; field1" />
      <field signature="System.Collections.Generic.List`1&lt;T&gt; field2" />

      <!--保留带有泛型的方法-->
      <method signature="System.Void Method1(System.Collections.Generic.List`1&lt;System.Int32&gt;)" />
      <!--保留带有泛型的事件-->
      <event signature="System.EventHandler`1&lt;System.EventArgs&gt; Event1" />
    </type>


    <!--如果使用类型,则保留该类型的所有字段。如果类型不是用过的话会被移除-->
    <type fullname="MyGame.I" preserve="fields" required="0"/>

    <!--如果使用某个类型,则保留该类型的所有方法。如果未使用该类型,则会将其删除-->
    <type fullname="MyGame.J" preserve="methods" required="0"/>

    <!--保留命名空间中的所有类型-->
    <type fullname="MyGame.SomeNamespace*" />

    <!--保留名称中带有公共前缀的所有类型-->
    <type fullname="Prefix*" />

  </assembly>
</linker>

用的最多应该就是下面这种,比如保留MyGame程序集下的A整个类型

xml 复制代码
<?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>,其中AB是你自定义的类。
    如果你在代码中没有显式地使用这些泛型(比如没有写出List<A>List<B>),那么在打包时,这些类型可能会被裁剪掉。如果你后续在热更新时想使用List<C>,但是之前并没有显示使用过它,程序就会出错。主要就是因为JIT和AOT两个编译模式的不同造成的。

1、解决方案

显示使用泛型类型 :在代码中显式地声明并使用泛型类型,确保它们在编译时被处理。例如,可以在代码中声明一个包含List<A>List<B>的类,或者编写一个泛型方法,在其中使用这些类型。这样做的目的是告诉IL2CPP,你会在运行时使用这些泛型类型,避免它们被裁剪掉。

2、泛型类:

声明一个类,然后在这个类中声明一些public的泛型类变量

csharp 复制代码
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知道我们需要使用这个内容

csharp 复制代码
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文件来确保所需的类型不会被裁剪掉。
  • 如果你使用了泛型,记得在代码中显式地调用这些泛型类型,以确保它们不会被错误地裁剪。

专栏推荐

地址
【零基础入门unity游戏开发------C#篇】
【零基础入门unity游戏开发------unity通用篇】
【零基础入门unity游戏开发------unity3D篇】
【零基础入门unity游戏开发------unity2D篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

相关推荐
abments2 分钟前
C# OpenCvSharp Yolov8 Face Landmarks 人脸特征检测
开发语言·yolo·c#
Tester_孙大壮6 分钟前
第13章:Python TDD完善货币加法运算(二)
开发语言·python
洛阳纸贵10 分钟前
基于SpringCloud的广告系统设计与实现(一)
java·开发语言
壮Sir不壮12 分钟前
go chan底层分析
开发语言·后端·golang
egekm_sefg13 分钟前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
一只小bit27 分钟前
C/C++内存管理(超详解)
开发语言·c++
蹦蹦跳跳真可爱58936 分钟前
Python----Python高级(面向对象:对象,类,属性,方法)
开发语言·python
JoYER_cc38 分钟前
C 陷阱与缺陷 —— Chapter one 词法陷阱
c语言·开发语言
yqcoder1 小时前
Node.js 与 JavaScript 是什么关系
开发语言·javascript·node.js
mnwl12_01 小时前
python轻量级框架-flask
开发语言·python·flask