Unity3D :Unity 中的 .NET 概述
推荐:将NSDT场景编辑器加入你的3D工具链
3D工具集:NSDT简石数字孪生
Unity 中的 .NET 概述
Unity 使用开源 .NET 平台来确保您使用 Unity 创建的应用程序可以在各种不同的硬件配置上运行。.NET 平台支持多种语言和 API 库。
脚本后端
Unity 有两个脚本后端;单声道和IL2CPP(中间语言到C++),每种都使用不同的编译技术:
- Mono 使用即时 (JIT) 编译,在运行时按需编译代码。
- IL2CPP 使用提前(AOT)编译,并在运行之前编译整个应用程序。
使用基于 JIT 的脚本后端的好处是编译时间通常比 AOT 快得多。
默认情况下,Unity 在支持 Mono 的平台上使用 Mono 后端。为应用程序构建播放器时,可以选择要使用的脚本后端。要通过编辑器执行此操作,请转到>播放器编辑>项目设置,打开其他设置面板,然后单击脚本后端下拉列表并选择所需的后端。有关更多信息,请参阅编写后端脚本。
托管代码剥离
构建应用程序时,Unity 会编译并搜索程序集(.DLL)来检测和删除未使用的代码。此剥离代码的过程会减少生成的最终二进制大小,但会增加生成时间。
使用 Mono 时,默认情况下禁用代码剥离,但无法为 IL2CPP 禁用代码剥离。您可以使用“托管剥离级别”属性控制 Unity 剥离的代码量。
要更改此属性,请转到>播放器>编辑项目设置,打开“其他设置”面板,然后单击“托管剥离级别”下拉列表并选择剥离级别。
随着托管剥离级别的提高,Unity 会删除更多代码。这会增加 Unity 删除应用程序所依赖的代码的风险,尤其是在运行时使用反射或生成代码时。
您可以在代码的某些元素上使用注释,以防止 Unity 剥离代码。有关更多信息,请参见托管代码剥离。
垃圾收集
Unity 将 Boehm 垃圾收集器用于 Mono 和 IL2CPP 后端。默认情况下,Unity 使用增量模式。您可以禁用增量模式以使用停止世界垃圾回收,但 Unity 建议使用增量模式。
要在增量模式和停止世界之间切换,请转到>播放器编辑>项目设置,打开其他设置面板,然后单击使用增量 GC 复选框。在增量模式下,Unity 的垃圾回收器仅在有限的时间段内运行,不一定一次收集所有对象。这样可以将收集对象所需的时间分散到多个帧上,并减少卡顿和 CPU 峰值。有关详细信息,请参阅托管内存。
要检查应用程序中的分配数和可能的 CPU 峰值,请使用 Unity 性能分析器。您还可以使用 垃圾收集器 API 在播放器中完全禁用垃圾回收。禁用收集器时,请注意避免分配多余的内存。
.NET 系统库
Unity 支持许多平台,并且可能会使用不同的脚本后端,具体取决于平台。在某些情况下,.NET 系统库需要特定于平台的实现才能正常工作。虽然 Unity 尽最大努力支持尽可能多的 .NET 生态系统,但 Unity 明确不支持的部分 .NET 系统库也有一些例外。
Unity 不对跨 Unity 版本的 .NET 系统库提供性能或分配保证。通常,Unity 不会修复 .NET 系统库中的任何性能回归。
Unity 不支持 System.Drawing 库,也不能保证在所有平台上都能使用。
Mono 脚本后端使用的 JIT 编译使你能够在应用程序运行时发出动态 C#/.NET 中间语言(IL)代码生成。IL2CPP 脚本后端使用的 AOT 编译不支持动态代码生成。
使用第三方库时,请务必考虑这一点,因为它们可能具有不同的 JIT 和 AOT 代码路径,或者它们可能使用依赖于动态生成的代码的代码路径。有关如何在运行时生成代码的详细信息,请参阅 Microsoft 的 ModuleBuilder 文档。
尽管 Unity 支持多个 .NET API 配置文件,但出于以下原因,您应该对所有新项目使用 .NET 标准 API 兼容级别:
- .NET Standard 是一个较小的 API 图面,因此具有较小的实现。这将减小最终可执行文件的大小。
- .NET Standard 具有更好的跨平台支持,因此您的代码更有可能跨所有平台工作。
- 所有 .NET 运行时都支持 .NET Standard,因此当您使用 .NET Standard 时,您的代码可以在更多的 VM/运行时环境(例如 .NET Framework、.NET Core、Xamarin、Unity)上运行。
- .NET Standard 将更多错误移动到编译时。.NET Framework 中的许多 API 在编译时可用,但在某些平台上具有在运行时引发异常的实现。
例如,如果您需要为较旧的现有应用程序提供支持,则其他配置文件可能很有用。若要更改“API 兼容级别”设置,请转到“>播放器”中编辑>项目设置。在“其他设置”标题下,将“API 兼容级别”设置为所需的设置。
有关详细信息,请参阅 .NET 配置文件支持。
使用第三方 .NET 库
应该只使用在各种 Unity 配置和平台上经过广泛测试的第三方 .NET 库。
第三方库中 JIT 和 AOT 代码路径的性能特征可能大不相同。AOT 通常会减少启动时间,因此适用于较大的应用程序,但会增加二进制文件大小以适应编译的代码。AOT 在开发过程中也需要更长的时间来构建。
JIT 根据运行它的平台在运行时进行调整,这可以提高运行性能,但代价是应用程序启动时间可能更长。因此,您应该在编辑器和目标平台上分析您的应用程序。有关详细信息,请参阅探查器概述。
应分析所有目标平台上 .NET 系统库的使用情况,因为它们的性能特征可能因使用的脚本后端、.NET 版本和配置文件而异。
查看第三方库时,请考虑以下方面:
- 兼容性:第三方库可能与某些 Unity 平台和脚本后端不兼容。
- 性能:与其他 .NET 运行时相比,第三方库在 Unity 中可能具有截然不同的性能特征。
- AOT 二进制文件大小:由于库使用的依赖项数量,第三方库可能会显著增大 AOT 二进制文件大小。
C# 反射开销
Mono 和 IL2CPP 在内部缓存所有 C# 反射(System.Reflection)对象,并且根据设计,Unity 不会对它们进行垃圾回收。此行为的结果是垃圾回收器在应用程序的生存期内持续扫描缓存的 C# 反射对象,这会导致不必要且可能显著的垃圾回收器开销。
要最大程度减少垃圾回收器开销,请在应用程序中避免使用诸如 Assembly.GetTypes 和 Type.GetMethods() 等方法,这些方法会在运行时创建许多 C# 反射对象。 而是应该在编辑器中扫描程序集以获取所需数据,并进行序列化和/或代码生成以在运行时使用。
UnityEngine.Object 特殊行为
UnityEngine.Object 是 Unity 中一种特殊类型的 C# 对象,因为它链接到本机C++对应对象。例如,当您使用相机组件时,Unity 将对象的状态存储在对象的本机C++对应项上,而不是存储在 C# 对象本身上。

Unity 目前不支持将 C# WeakReference 类与 UnityEngine.Object 实例一起使用。因此,不应使用弱引用来引用加载的资产。有关 WeakReference 类的更多信息,请参阅 Microsoft 的 WeakReference 文档。
Unity C# 和 Unity C++ 共享 UnityEngine 对象
当您使用 Object.Destroy 或 Object.DestroyImmediate 等方法销毁 UnityEngine.Object 派生对象时,Unity 会销毁(卸载)本机计数器对象。不能使用显式调用销毁 C# 对象,因为垃圾回收器管理内存。一旦不再有任何对托管对象的引用,垃圾回收器就会收集并销毁它。
如果您的应用程序尝试再次访问已销毁的 UnityEngine.Object,Unity 会为大多数类型重新创建本机对应对象。这种重新创建行为的两个例外是 MonoBehavior 和 ScriptableObject:Unity 在它们被销毁后永远不会重新加载它们。
MonoBehavior 和 ScriptableObject 覆盖相等(==)和不相等 (!=)运算符。如果将销毁的 MonoBehavior 或 ScriptableObject 与 进行比较,则当托管对象仍然存在且尚未进行垃圾回收时,运算符将返回 true。null
因为你不能超载??和?运算符,它们与派生自 UnityEngine.Object 的对象不兼容。当您在托管对象仍然存在的情况下在已销毁的 MonoBehavior 或 ScriptableObject 上使用它们时,运算符不会返回与相等和不相等运算符相同的结果。
异步和等待任务的限制
Unity API 不是线程安全的,因此,您应该只使用 UnitySynchronizationContext 中的异步和等待任务。异步任务通常在调用时分配对象,如果过度使用它们,可能会导致性能问题。
Unity 使用自定义 UnitySynchronizationContext 覆盖默认的同步上下文,并在默认情况下以编辑和播放模式运行主线程上的所有任务。要使用异步任务,您必须使用 Task.Run API 手动创建和处理自己的线程,并使用默认的同步上下文而不是 Unity 版本。
退出运行模式时,Unity 不会自动停止在托管线程上运行的异步任务。若要侦听进入和退出播放模式事件以手动停止任务,请使用 EditorApplication.playModeStateChanged。如果采用此方法,则大多数 Unity 脚本 API 都不可用,除非您将上下文迁移回 UnitySynchronizationContext。
在开发版本中,如果您尝试在多线程代码中使用 Unity API,Unity 会显示以下错误消息:
UnityException: Internal_CreateGameObject can only be called from the main thread. \
Constructors and field initializers will be executed from the loading thread when loading a scene. \
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
出于性能原因,Unity 不会在非开发版本中执行多线程行为检查,也不会在实时版本中显示此错误。这意味着,虽然 Unity 不会阻止在实时构建中执行多线程代码,但如果确实使用多个线程,则可能会出现随机崩溃和其他不可预测的错误。因此,Unity 建议您不要使用多线程。
若要安全地利用多线程处理的好处,请使用 C# 作业系统。作业系统安全地使用多个线程并行执行作业,并实现多线程的性能优势。更多信息请参阅 [什么是多线程处理?”。
由3D建模学习工作室整理翻译,转载请注明出处!