Unity 插件(Plug-ins)使用说明
插件
在 Unity 中,通常使用脚本来创建功能,但你也可以以插件的形式包含在 Unity 之外创建的代码。你可以在 Unity 中使用两种不同类型的插件:
- 托管插件(Managed plug-ins):你可以使用 Visual Studio 等工具创建的托管 .NET 程序集。它们只包含 .NET 代码,这意味着它们不能访问 .NET 库不支持的任何功能。有关详细信息,请参阅微软的托管代码文档。
- 本机插件(Native plug-ins):平台特定的本地代码库。它们可以访问操作系统调用和第三方代码库等功能,这些功能在 Unity 中通常无法访问。
托管代码可以通过 Unity 用来编译脚本的标准 .NET 工具访问。托管插件代码和 Unity 脚本代码之间的唯一区别是插件是在 Unity 之外编译的,因此 Unity 可能无法访问其源代码。使用本机插件时,Unity 的工具无法像访问托管库那样访问第三方代码库。例如,如果忘记将托管插件文件添加到项目中,会收到标准的编译器错误消息。而如果忘记将本机插件文件添加到项目中,则只会在尝试运行项目时看到错误报告。
导入和配置插件(Import and configure plug-ins)
如果你有托管插件或本机插件,你可以将其导入 Unity,然后进行配置。在编辑器中,插件被视为一个资产,类似于脚本,你可以在 Inspector 窗口中配置它。
你可以使用插件配置来指定插件的运行位置、平台和平台配置,以及在何种条件下运行。
导入插件
导入插件到项目的最简单方法是将插件拖动到 Assets 文件夹或其子文件夹中。Unity 会识别特定的文件和文件夹类型为插件,并应用与插件目标平台匹配的默认设置。
支持的插件文件和文件夹类型(Supported plug-in file and folder types)
在 Unity 中,以下文件扩展名被识别为插件,每种扩展名表示一种特定类型的文件或库:
- .a: 静态库文件,通常用于 iOS 和 macOS 平台。
- .aar: Android 库包,包含 Android 库和资源文件。
- .bc: LLVM 字节码文件,用于低级编译任务。
- .c: C 语言源代码文件。
- .cc: C++ 语言源代码文件。
- .cpp: C++ 语言源代码文件。
- .dll: 动态链接库文件,常用于 Windows 平台。
- .def: 定义文件,描述导出函数和数据。
- .dylib: 动态库文件,常用于 macOS 平台。
- .h: 头文件,包含 C/C++ 的函数和变量声明。
- .jar: Java ARchive 文件,包含 Java 类文件和元数据。
- .jslib: JavaScript 库文件,用于 WebGL 插件。
- .jspre: JavaScript 预处理文件。
- .m: Objective-C 语言源代码文件,通常用于 iOS 和 macOS 平台。
- .mm: Objective-C++ 语言源代码文件,通常用于 iOS 和 macOS 平台。
- .prx: 索尼 PlayStation 专用插件文件。
- .rpl: 任天堂平台的插件文件。
- .so: 共享对象文件,常用于 Linux 和 Android 平台。
- .sprx: 索尼 PlayStation 专用插件文件(扩展的 prx 文件)。
- .suprx: 索尼 PlayStation 专用超级插件文件。
- .swift: Swift 语言源代码文件,通常用于 iOS 和 macOS 平台。
- .winmd: Windows 元数据文件,用于 Windows 运行时组件。
- .xex: Xbox 执行文件。
- .xib: XML 接口构建器文件,定义 iOS 应用的用户界面。
Unity 还将某些文件夹视为捆绑插件。Unity 不会在这些文件夹中寻找额外的插件文件,因此文件夹中的所有内容都被视为一个单独的插件。Unity 将具有以下扩展名的文件夹视为捆绑插件(bundled plug-in):
- .androidlib:Android 库文件夹。包含一个完整的 Android 库,包括资源文件、AndroidManifest.xml 文件和库代码。通常用于将复杂的 Android 库集成到 Unity 项目中。
- .bundle:macOS 和 iOS 捆绑包。包含一组资源文件和可执行代码,通常用于 macOS 应用和 iOS 应用的插件开发。捆绑包可以包含动态库、图像、音频文件和其他资源。
- .framework:macOS 和 iOS 框架。一个框架文件夹包含一个共享库以及相关资源。用于将功能模块化为独立的、可重用的组件。macOS 和 iOS 都使用框架来分发系统库和第三方库。
- .plugin:通用插件文件夹。包含不同平台的插件文件,可以用于各种平台的动态库。通常包含 Windows 的 .dll 文件、macOS 的 .dylib 文件或 Linux 的 .so 文件。
插件默认设置
如果插件在 Assets 文件夹中的路径匹配平台特定模式,Unity 会自动应用平台特定的默认设置。如果路径不匹配任何模式,Unity 会对插件应用编辑器平台的默认设置。
下表显示了 Unity 识别的路径模式。路径中出现的方括号部分为可选项。当路径包含双点时,可以包含更多文件夹。
文件夹路径模式 | 默认设置 |
---|---|
Assets/…/Editor/(x86 或 x86_64 或 x64) | 平台:仅编辑器 CPU(可选):根据子文件夹,如果存在。 |
Assets/…/Plugins/(x86_64 或 x86 或 x64) | 平台:Windows、Linux 和 macOSCPU(可选):根据子文件夹,如果存在。 |
Assets/Plugins/iOS | 平台:iOS |
Assets/Plugins/WSA/(SDK80 或 SDK81 或 PhoneSDK81)/(x86 或 ARM) | 平台:通用 Windows 平台 SDK(可选):根据子文件夹,如果存在。出于兼容性原因,SDK81 是 Win81,PhoneSDK81 是 WindowsPhone81。CPU(可选):根据子文件夹,如果存在。 |
注意:你可以使用关键字 Metro 代替 WSA。 |
更改插件设置
在 Unity 中,插件分为托管和本机两种类型。下表显示了每种类型插件的相关设置:
设置 | 托管 | 本机 |
---|---|---|
选择插件平台 | x | x |
平台设置(Platform settings) | x | x |
资产标签(Asset Labels) | x | x |
资产包(Asset Bundles) | x | x |
通用设置 | x | |
定义约束(Define Constraints) | x | |
插件加载设置(Plugin load settings) | x | |
要在 Inspector 中查看和更改插件设置,请在项目窗口中选择插件文件。 |
通用插件设置
选择插件平台和平台设置指定 Unity 在构建中包含插件的位置。
下表描述了常见设置:
设置 | 选项 | 说明 |
---|---|---|
选择插件平台(Select platforms for plugin) | 编辑器(Editor):用于 play 模式和在编辑时运行的任何脚本。独立( Standalone):Windows、Linux 和 macOS。你安装的 Unity 包含的任何平台,例如 Android、iOS 和 WebGL。 | 要使插件与尚未包含在 Unity 中的平台一起工作,请选中“Any Platform”。如果不支持某些平台,可以排除这些平台。导入插件时,Unity 会将其加载到内存中。本机插件不能卸载;即使更改设置后,它仍会在 Unity 会话中保持加载状态。要卸载插件,必须重新启动 Unity。 |
平台设置(Platform settings) | 对于选择的每个平台,可以指定附加条件,例如 CPU 架构和依赖项。Unity 仅显示适用于所选平台的设置,如果可能,还会显示适用于该平台上特定插件类型的设置。例如,带有 .dll 扩展名的本机插件文件只能在 Windows 上运行,因此 Unity 仅显示 Windows 设置。 | |
编辑器(Editor) | CPU 架构 操作系统 | 大多数托管插件与任何 CPU 和操作系统兼容。大多数本机插件仅与单一操作系统兼容,并且根据其编译方式,可能仅与单一 CPU 架构兼容。 |
Windows、Linux 和 macOS | CPU 架构 操作系统 | **托管库通常与任何操作系统和 CPU 架构兼容,除非它们访问特定的系统 API。**本机库仅与单一操作系统兼容,但可以与 32 位、64 位或两者都有的 CPU 架构兼容。 |
Universal Windows Platform | 参见通用 Windows 平台:IL2CPP 脚本后端上的插件。 | |
Android | CPU 架构 | CPU 架构必须与库编译的架构匹配。Unity 不验证你的设置。详见:AAR 插件和 Android 库。 |
iOS 和 tvOS | 框架依赖项添加到嵌入的二进制文件编译标志 | 当你选择“添加到嵌入的二进制文件(Add to Embedded Binaries)”选项时,Unity 会将 Xcode 项目选项设置为将插件文件复制到最终的应用程序包中。这样做用于:* 动态加载库。 |
- 包含动态加载库的捆绑包和框架。
- 需要在运行时加载的任何资产和资源。
在“编译标志”字段中,为 Unity 作为构建的一部分必须编译的插件源代码文件设置编译标志。 |
提示:有关其他通用设置的信息,请参见资产包和编辑器搜索。
托管插件设置
托管插件可以是第三方库或你想要包含在项目中的用户编译程序集(user-compiled assemblies)。
通用 - 自动引用(Auto Reference)
自动引用设置控制项目中的程序集定义如何引用插件文件。当启用自动引用时,所有预定义程序集和程序集定义都会自动引用插件文件。
自动引用默认启用。
要限制插件的引用范围,请禁用自动引用。然后需要显式声明对该插件的所有引用。这样做的原因可能包括:
- 想防止脚本错误地使用插件。
- 在插件迭代时减少编译时间。如果显式声明插件,Unity 仅重新编译依赖程序集,而不是整个项目。
- 防止 Asset Store package 中的插件与导入项目中的其他代码冲突。
当禁用自动引用时,Unity 无法从它为项目创建的预定义程序集引用插件。这些预定义程序集包含项目中未使用程序集定义文件分配给其他程序集的所有脚本。要引用禁用自动引用属性的插件中的类、函数或其他资源,引用代码必须在使用程序集定义文件创建的程序集内。例如,如果项目中的一组脚本使用插件,则必须为这些脚本创建一个程序集定义文件,并在定义文件中添加对插件的显式引用。
可以有多个程序集使用插件,但所有程序集必须显式声明依赖项。有关 Unity 中程序集定义的更多信息,请参见程序集定义。
注意:自动引用选项不影响文件是否包含在 build 中。要控制插件的构建设置,请使用 Platform settings 。
通用 - 验证引用
Unity 可以检查你的插件引用是否在项目中可用。如果不进行此验证,用户在应用程序尝试使用丢失的引用时可能会遇到运行时错误。
启用验证引用选项可以检查:
- 插件引用是否存在。例如,如果插件引用了插件
Newtonsoft.Json.dll
,但 Unity 找不到Newtonsoft.Json.dll
,Unity 会显示错误。 - 强名称引用是否能够加载。这很重要,因为强名称引用需要匹配版本。例如,如果编译插件时引用了
b.dll
版本 2.0.0,该版本必须在项目中。
如果不想检查强名称引用,但仍想检查引用是否存在:
- 在插件 Inspector 中,启用 Validate References。
- 在项目设置 > Player > 其他设置 > 禁用 Assembly Version Validation。
定义约束(Define Constraints)
可以指定 Unity 加载和引用插件到内存中的条件。这些条件是必须满足的符号,可以是定义或未定义。
约束的工作方式类似于 C# 中的 #if
预处理指令,但在程序集级别而不是脚本级别。可以在 Assembly Definition properties 中了解更多关于约束的信息。
可以使用 Unity 的任何内置定义符号,或在项目设置 > Player > 其他设置 > 脚本编译 > 脚本定义符号(Scripting Define Symbols)中添加符号。添加的符号是平台特定的,因此需要为每个相关平台定义它们。详见平台依赖编译,包括内置符号列表。
提示:要指定符号必须未定义,在前面加一个否定符号 !
(感叹号)。
在以下示例中,我们希望 Unity 仅在非 IL2CPP 脚本运行时和 Unity 2018.3 或更新版本中加载和引用插件。我们定义了两个约束,必须同时满足:
ENABLE_IL2CPP
未定义UNITY_2018_3_OR_NEWER
已定义
插件加载设置 - 启动时加载
可以在图形初始化、脚本、资产加载、场景等之前开始执行独立于它们的本机代码(native code)。这与 player 加载native 插件的默认方式不同,默认方式是等待脚本首次调用插件的函数。
要在应用程序执行任何脚本之前加载插件:
- 在插件中实现
UnityPluginLoad
。详见低级本机插件接口。 - 在编辑器中选择 Plugin load settings > Load on startup 。
提示:有关 C# 脚本调用插件函数的示例,请参见 Manual: Native plug-ins 。
托管插件(Managed plug-ins)
托管插件是你在 Unity 之外创建和编译的 .NET 程序集(DLL),可以使用 Visual Studio 等工具创建。
这与标准 C# 脚本的过程不同,Unity 将标准 C# 脚本作为源文件存储在你的 Unity 项目的 Assets 文件夹中。Unity 每当脚本发生变化时都会编译标准 C# 脚本,而 DLL 是预编译的,不会更改。你可以将已编译的 .dll 文件添加到项目中,并以与标准脚本相同的方式将其包含的类附加到 GameObject 上。
有关 C# 托管代码的更多信息,请参阅微软的托管代码是什么文档。
托管插件仅包含 .NET 代码,这意味着它们不能访问 .NET 库不支持的任何功能。然而,托管代码可以通过 Unity 用于编译脚本的标准 .NET 工具访问。
使用 DLL 时,需要完成比使用脚本更多的步骤。然而,在某些情况下,创建并将 .dll 文件添加到 Unity 项目中会更有帮助,例如:
- 你想在代码中使用 Unity 不支持的编译器。
- 你想在 .dll 文件中添加第三方 .NET 代码。
- 你想在不提供源代码的情况下向 Unity 提供代码。
本页面解释了一般方法,你可以用它来创建托管插件,以及如何使用 Visual Studio 创建托管插件并设置调试会话。
创建托管插件
要创建托管插件,你需要创建一个 DLL。为此,你需要一个合适的编译器,例如:
并非所有生成 .NET 代码的编译器都与 Unity 兼容,因此在进行大量工作之前,你应该使用一些可用代码测试编译器。创建 DLL 的方法取决于 DLL 是否包含 Unity API 代码:
- 如果 DLL 包含 Unity API 代码,则在编译之前需要将 Unity 的 DLL 提供给编译器:
- 查找 Unity DLL:
- 在 Windows 上,
- 路径为:
C:\Program Files\Unity\Hub\Editor\<version-number>\Editor\Data\Managed\UnityEngine
- 路径为:
- 在 macOS 上,
- 路径为:
/Applications/Unity/Hub/Editor/<version-number>/Unity.app/Contents/Managed/UnityEngine
- 找到电脑上的 Unity.app 文件。
- 右键点击 Unity.app,选择“显示包内容”。
- 路径为:
- 在 Windows 上,
- UnityEngine 文件夹包含多个模块的 .dll 文件。引用这些文件以使它们可用于你的脚本。有些命名空间还需要引用 Unity project 中 compiled library(例如,UnityEngine.UI)。可以在项目文件夹的目录中找到这些库:
~\Library\ScriptAssemblies
- 查找 Unity DLL:
- 如果 DLL 不包含 Unity API 代码,或你已经提供了 Unity 的 DLL,请按照编译器的文档编译 .dll 文件。编译 DLL 的具体选项取决于所用的编译器。例如,使用 Roslyn 编译器 csc 在 macOS 上的命令行可能如下所示:
csc /r:/Applications/Unity/Hub/Editor/<version-number>/Unity.app/Contents/Managed/UnityEngine.dll /target:library /out:MyManagedAssembly.dll /recurse:*.cs
在此示例中:
- 使用
/r
选项指定要包含在构建中的库的路径,此处为UnityEngine
library。 - 使用
/target
选项指定所需的构建类型,library
表示 DLL 构建。 - 使用
/out
指定库的名称,此处为MyManagedAssembly.dll
。 - 添加要包含的源文件名。使用
/recurse
方法添加当前工作目录及其子文件夹中以.cs
结尾的所有文件。生成的.dll
文件出现在与源文件相同的文件夹中。
使用托管插件
编译 DLL 后,你可以像其他资产一样将 .dll 文件拖动到 Unity 项目中。然后你可以:
- 展开(expand)托管插件以查看库中的各个类。
- 将从
MonoBehaviour
派生的类拖动到GameObjects
上。 - 从其他脚本中直接使用非 MonoBehaviour 类。
使用 Visual Studio 创建 DLL
本节解释:
- 如何使用 Visual Studio 构建和集成一个简单的 DLL 示例。
- 如何为 DLL 在 Unity 中准备调试会话(debugging session)。
设置项目
- 打开 Visual Studio 并创建一个新项目。
- 选择 文件 > 新建 > 项目 > Visual C# > .Net Standard > Class Libray(.NET Standard)。
- 为新库添加以下信息:
- 名称(Name):命名空间(此示例中使用
DLLTest
作为名称)。 - 位置(Location):项目的父文件夹。
- 解决方案名称(Solution Name):项目的文件夹。
- 名称(Name):命名空间(此示例中使用
- 使 Unity 的 DLL 可用于你的脚本。在 Visual Studio 中,打开解决方案资源管理器中的引用上下文菜单,选择“添加引用” > 浏览 > 选择文件。
- 选择位于 UnityEngine 文件夹中的所需 .dll 文件。
编写 DLL 代码
- 在解决方案浏览器中将默认类重命名为 MyUtilities。
- 将其代码替换为以下内容:
using System;
using UnityEngine;
namespace DLLTest {
public class MyUtilities {
public int c;
public void AddValues(int a, int b) {
c = a + b; }
public static int GenerateRandom(int min, int max) {
System.Random rand = new System.Random();
return rand.Next(min, max);
}
}
}
- 构建项目以生成 DLL 文件及其调试符号。
在 Unity 中调试 DLL
- 创建一个新的 Unity 项目,并将生成的 .dll 文件(例如,
<project folder>/bin/Debug/DLLTest.dll
)复制到 Assets 文件夹中。 - 在 Assets 文件夹中创建一个名为 Test 的 C# 脚本。
- 将其内容替换为一个创建 DLL 中类的新实例、使用其函数并在控制台窗口中显示输出的脚本。例如,要为上面部分的 DLL 创建测试脚本,可以复制以下代码:
using UnityEngine;
using System.Collections;
using DLLTest;
public class Test : MonoBehaviour {
void Start () {
MyUtilities utils = new MyUtilities();
utils.AddValues(2, 3);
print("2 + 3 = " + utils.c);
}
void Update () {
print(MyUtilities.GenerateRandom(0, 100));
}
}
- 将此脚本附加到场景中的一个 GameObject,然后点击 Play。
- Unity 在控制台窗口中显示来自 DLL 的代码的输出。
编译 Unsafe C# 代码
不安全的 C# 代码能够直接访问内存。默认情况下未启用不安全代码,因为编译器无法验证其不会引入安全风险。
你可能希望使用不安全代码来:
- 使用指针访问内存。
- 分配原始内存。
- 使用指针调用方法。
要启用对编译不安全 C# 代码的支持,请转到 编辑 > 项目设置 > Player > 其他设置 并启用允许不安全代码(Allow Unsafe Code)。
有关更多信息,请参阅微软的不安全代码文档。
本机插件 (Native Plug-ins)
Unity 支持本机插件 (native plug-ins),这些插件是你可以用 C、C++ 和 Objective-C 等语言编写的本地代码库。插件允许你编写的 C# 代码调用这些库中的函数。这个功能使 Unity 能够与中间件库或现有的 C/C++ 代码进行集成。
本机插件提供了一个简单的 C 接口,C# 脚本然后将其暴露给其他脚本。当某些低级渲染事件发生时(例如,当你创建图形设备时),Unity 还可以调用本机插件导出的函数。有关更多信息,请参见低级本机插件接口。
有关本机插件的示例,请参见本机渲染器插件。
使用本机插件
要使用 native 插件:
- 用 C 系语言编写函数以访问所需功能。
- 将它们编译成一个库。
- 在 Unity 中创建一个 C# 脚本,调用本地库中的函数。
你需要使用目标平台的本地代码编译器(native code compilers)来构建本机插件。由于插件函数使用基于 C 的调用接口,因此必须用 C 链接(linkage)声明函数以避免名称重整问题。
示例
一个包含单个函数的简单本机库的代码可能如下所示:
float ExamplePluginFunction () {
return 5.0F;
}
要从 Unity 内部访问此代码,请使用以下 C# 脚本:
using UnityEngine;
using System.Runtime.InteropServices;
class ExampleScript : MonoBehaviour {
#if UNITY_IPHONE
// 在 iOS 上,插件被静态链接到可执行文件中,
// 所以我们必须使用 __Internal 作为库名。
[DllImport ("__Internal")]
#else
// 其他平台动态加载插件,因此传递插件的动态库名称。
[DllImport ("PluginName")]
#endif
private static extern float ExamplePluginFunction ();
void Awake () {
// 调用插件内的 ExamplePluginFunction 函数
// 并将 5 打印到控制台
print (ExamplePluginFunction ());
}
}
为桌面平台构建插件(Building plug-ins for desktop platforms)
桌面平台的插件是你可以用 C、C++ 和 Objective-C 等语言编写的本机代码库 (native libraries)。本页面描述了 macOS、Windows 和 Linux 的插件。有关更多信息,请参见本机插件 (native plug-ins)。
macOS 插件
你可以将 macOS 插件作为捆绑包 (bundles) 部署,或者如果你使用 IL2CPP 脚本后端 (IL2CPP scripting backend),可以使用 loose C++ 文件,通过 [DllImport("__Internal")]
语法调用。有关 loose C++ 插件的更多信息,请参见C++ 源代码插件 for IL2CPP。
要使用 Xcode 创建捆绑项目:
- 打开 Xcode。
- 选择 文件 > 新建 > 项目 > macOS > 框架与库 > 捆绑包。
有关使用 Xcode 的更多信息,请参见 Apple 的Xcode 文档.
要求:
- 你可以将插件构建为兼容 64 位架构的通用二进制文件。或者,你可以提供单独的 dylib 文件。
- 如果你使用 C++ (.cpp) 或 Objective-C (.mm) 实现插件,使用 C 链接声明函数以避免名称重整问题:
extern "C" {
float ExamplePluginFunction ();
}
Windows 插件
Windows 上的插件可以是导出函数的 .dll 文件,或者如果使用 IL2CPP,则可以是 loose C++ 文件。你可以使用大多数可以创建 .dll 文件的语言和开发环境来创建插件。必须使用 C 链接声明任何 C++ 函数以避免名称重整问题。
Linux 插件
Linux 上的插件是导出函数的 .so
文件。虽然这些库通常使用 C 或 C++ 编写,但你可以使用任何语言。必须使用 C linkage 声明任何 C++ 函数以避免名称重整问题。
当你为 Linux 构建本机插件时,如果构建的库依赖于另一个本机插件,必须在编译时指定该库的 rpath
。
添加链接器标志 -Wl,-rpath=$ORIGIN
来指定运行时搜索路径。链接器标志指示加载器在当前目录中查找其依赖项以及系统搜索路径。你可以将其他链接器标志与 -Wl,-rpath=$ORIGIN
一起添加,但 Unity 不控制它们。例如:
/usr/bin/g++ -o binary.c.o -Wl,-rpath=$ORIGIN
或者,你可以在环境中设置 LD_LIBRARY_PATH=dependency_path
来指示加载器在该路径中查找依赖项。Linux 不会自动在当前目录中搜索依赖项。确保设置正确的依赖项搜索路径,因为不正确的路径会导致 Unity 编辑器中缺少库错误。
在 Unity 中管理插件
在 Unity 中,插件管理器 (Plugin Inspector) 管理你的插件。要访问插件管理器,请在项目窗口中选择一个插件文件。对于独立平台(Standalone platforms,只能在一个平台用),你可以选择库兼容的 CPU 架构。对于跨平台插件,你必须包含 .bundle 文件(适用于 macOS)、.dll 文件(适用于 Windows)和 .so 文件(适用于 Linux)。Unity 会自动为目标平台选择合适的插件并将其包含在 player 中。有关更多信息,请参见导入和配置插件。
从 C# 脚本调用插件
将编译好的插件放在 Unity 项目的 Assets 文件夹或适当的架构特定子目录中。然后,Unity 会在你从 C# 脚本中调用它时按名称找到它。例如:
[DllImport ("PluginName")]
private static extern float ExamplePluginFunction ();
注意:不要在 PluginName
值中包含库前缀或文件扩展名。例如,如果插件文件的实际名称在 Windows 上是 PluginName.dll 或在 Linux 上是 libPluginName,值在两种情况下都应该是 PluginName
。
插件示例
你可以下载并使用这些项目来学习如何在 Unity 中实现插件。
- Simplest Plugin Example:该项目实现了基本操作(例如,打印数字、打印字符串、添加两个浮点数和添加两个整数)。该项目包括 Windows、macOS 和 Linux 项目文件。
- Native Renderer Plugin:这是一个低级渲染插件,使用 C++ 代码在所有常规渲染完成后渲染一个旋转三角形,并使用
Texture.GetNativeTexturePtr
从 C++ 代码填充程序纹理。该项目包括 Windows、UWP、macOS、WebGL 和 Android 文件。
低级本机插件接口 (Low-level Native Plug-in Interface)
在 Unity 中,当某些事件发生时,本机插件 (native plug-ins) 可以接收回调。你可以利用这一功能在插件中实现低级渲染(low-level rendering),从而与 Unity 的多线程渲染(Unity’s multithreaded rendering)协同工作。
接口注册 (Interface Registry)
为了处理主要的 Unity 事件,插件必须导出 UnityPluginLoad
和 UnityPluginUnload
函数。IUnityInterfaces
使插件能够访问这些函数,你可以在插件 API 的 IUnityInterface.h
中找到这些函数:
#include "IUnityInterface.h"
#include "IUnityGraphics.h"
// Unity 插件加载事件
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces)
{
IUnityGraphics* graphics = unityInterfaces->Get<IUnityGraphics>();
}
访问图形设备 (Access to the Graphics Device)
使用 IUnityGraphics
接口(你可以在 IUnityGraphics.h
中找到它),让插件访问通用图形设备功能。以下示例展示了如何使用 IUnityGraphics
接口注册回调:
#include "IUnityInterface.h"
#include "IUnityGraphics.h"
static IUnityInterfaces* s_UnityInterfaces = NULL;
static IUnityGraphics* s_Graphics = NULL;
static UnityGfxRenderer s_RendererType = kUnityGfxRendererNull;
// Unity 插件加载事件
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces)
{
s_UnityInterfaces = unityInterfaces;
s_Graphics = unityInterfaces->Get<IUnityGraphics>();
s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent);
// 在插件加载时手动运行 OnGraphicsDeviceEvent(initialize),
// 以免错过图形设备已经初始化的事件
OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize);
}
// Unity 插件卸载事件
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload()
{
s_Graphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent);
}
static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType)
{
switch (eventType)
{
case kUnityGfxDeviceEventInitialize:
{
s_RendererType = s_Graphics->GetRenderer();
// 用户初始化代码
break;
}
case kUnityGfxDeviceEventShutdown:
{
s_RendererType = kUnityGfxRendererNull;
// 用户关闭代码
break;
}
case kUnityGfxDeviceEventBeforeReset:
{
// 用户 Direct3D 9 代码
break;
}
case kUnityGfxDeviceEventAfterReset:
{
// 用户 Direct3D 9 代码
break;
}
};
}
渲染线程上的插件回调 (Plug-in Callbacks on the Rendering Thread)
如果平台和可用的 CPU 数量允许,你可以使用多线程在 Unity 中进行渲染。
注意:当使用多线程渲染时,渲染 API 命令会在一个完全独立于运行 MonoBehaviour 脚本的线程上执行。主线程和渲染线程之间的通信意味着你的插件可能不会立即开始渲染,取决于主线程向渲染线程推送了多少工作。
要从插件进行渲染,请从托管插件脚本调用 GL.IssuePluginEvent
。这会导致 Unity 的渲染管线在渲染线程上调用本机函数,如下例所示。例如,如果你从 Camera 的 OnPostRender
函数调用 GL.IssuePluginEvent
,该函数将在相机完成渲染后立即调用插件回调。
本机插件代码:
// 处理特定渲染事件的插件函数
static void UNITY_INTERFACE_API OnRenderEvent(int eventID)
{
// 用户渲染代码
}
// 传递回调到插件特定脚本的自定义函数
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API GetRenderEventFunc()
{
return OnRenderEvent;
}
托管插件代码:
#if UNITY_IPHONE && !UNITY_EDITOR
[DllImport ("__Internal")]
#else
[DllImport("RenderingPlugin")]
#endif
private static extern IntPtr GetRenderEventFunc();
// 在渲染线程上队列特定回调
GL.IssuePluginEvent(GetRenderEventFunc(), 1);
UnityRenderingEvent 回调的签名在 Native Rendering Plugin 示例中的 IUnityGraphics.h
中提供。
使用 OpenGL 图形 API 的插件 (Plug-in Using the OpenGL Graphics API)
有两种 OpenGL 对象:
- 在 OpenGL 上下文间共享的对象,如纹理、缓冲区、渲染缓冲区、采样器、查询、着色器和程序对象。
- 每个 OpenGL 上下文对象,如顶点数组、帧缓冲、程序管道、变换反馈和同步对象。
Unity 使用多个 OpenGL 上下文。当初始化和关闭编辑器和播放器时,Unity 依赖主上下文,但在渲染时使用专用上下文。这意味着你不能在 kUnityGfxDeviceEventInitialize
和 kUnityGfxDeviceEventShutdown
事件期间创建每个上下文的对象。
Unity Plug-in 和 Package 的对比
对比总结
- 管理方式: Unity Package 使用 Unity Package Manager 进行管理,而 Plug-in 需要手动管理。
- 安装位置: Unity Package 安装在
Packages
文件夹中,而 Plug-in 安装在Assets/Plugins
文件夹中。 - 内容类型: Unity Package 可以包含多种内容,包括脚本、资产和插件;而 Plug-in 主要是指外部编写的代码库。
- 依赖管理: Unity Package 可以定义依赖项和版本控制,而 Plug-in 需要手动处理依赖关系。
- 程序集管理:
- Unity Package 使用 .asmdef 文件明确管理和组织脚本和程序集的依赖关系,提高编译效率。
- Plug-in 依赖手动管理程序集和库文件的引用,容易出错且缺乏自动化支持。
- Build 过程:
- Unity Package 通过 Package Manager 处理依赖项和版本控制,使得包管理和项目打包更加自动化和简化。
- Plug-in 需要手动确保所有文件和依赖项的正确路径和配置,增加了出错的风险和维护成本。
- 适用场景:
- Unity Package 适用于需要频繁更新和维护的功能模块,尤其是涉及多个脚本和资源的复杂项目。
- Plug-in 适用于需要集成外部功能或第三方库的场景,尤其是涉及平台特定功能调用的情况。
Reference
- https://learn.microsoft.com/en-us/dotnet/standard/managed-code
- MsBuild
- .NET SDK
- Mono Interop with native libraries
- P-invoke documentation on MSDN
- https://github.com/Unity-Technologies/DesktopSamples