[Unity教程] 创建unity3D的C/C++插件
发表于2016-09-18
今天我决定分享一些我认为不是常识性的内容,并且在互联网上没有太多相关的资源。在本教程中,我将教你如何在unity3中创建C/C++库。
请注意下面讲述的方法只适用与Mac,Windows上你需要使用Visual Studio。Windows的版本将很快到来!
你可能会问,为什么当支持C#,我们还需要写C或者C++代码?
答案是很简单的。因为速度更快。我决定准备一些基准来展示在Mono框架内C++执行速度胜过C#。
测试案例
参考配置:Macbook Pro 15″ Mid 2014, Intel I7 2.2Ghz
用顺序数填充10000×10000整型数组:
[Unity C#] 1.0258 秒vs 0.2346 秒[C Library]
用顺序数填充500×500整型数组:
[Unity C#] 3.794 毫秒vs 0.35 毫秒[C Library]
用随机数填充10000×10000整型数组:
[Unity C#] 4.26 秒vs 0.995 秒[C Library]
用柏林噪点填充10000×10000整型数组:
[Unity C#] 6.015 秒vs 2.394 秒[C Library]
用柏林噪点填充500×500整型数组:
[Unity C#] 20,47 毫秒vs 5.697 毫秒[C Library]
正如你所看到的,在Mono的一些任务中,C库可以很容易地胜出。我认为这项技术对于程序化生成特别有用,我曾经非常喜爱程序化生成。我明白当在GPU上执行这些操作可能会快100倍,但是在这里我将仅仅专注于CPU层上的C++逻辑。
第一部分 创建C++库

如何创建unity3D C/C++插件
打开Xcode,点击File -> New -> Project.在项目列表中选择OS X -> Framework & Library -> Bundle.
现在,我们必须用C++语言创建源文件和头文件。点击包含你项目的文件夹然后选择“New File…”。
将会出现如下窗口。选择C++ file,命名为LowLevelPlugin并且选择“Also create header file”。

做完这些操作之后,你将会看到下面的项目结构/层次关系。

如果在展开文件夹后不是下面的结构关系的话,那么意味着你可能做错了什么操作,我建议你重新再来一遍。
当你都设置好后,打开LowLevelPlugin.h文件。你将会看到像下面这样的代码:
1 2 3 4 | #ifndef __LowLevelPlugin__LowLevelPlugin__ #define __LowLevelPlugin__LowLevelPlugin__ #include #endif /* defined(__LowLevelPlugin__LowLevelPlugin__) */ |
1 2 3 4 5 6 7 8 9 | #pragma once #if UNITY_METRO #define EXPORT_API __declspec(dllexport) __stdcall #elif UNITY_WIN #define EXPORT_API __declspec(dllexport) #else #define EXPORT_API #endif |
这可能看起来比之前的更难度,但是不要担心,我将解释一下。
整个头文件的意思是展示出你当前编译代码所基于的平台。这意味着你可以在windows上用visual studio轻松地编译代码。
1 | #pragma once |
这是C/C++特殊指令,指示当前源文件在单次编译中仅仅被包含一次。
下一个是:
1 | #if UNITY_METRO |
1 | #define EXPORT_API __declspec(dllexport) __stdcall |
-关键词 “define”起到替换函数的作用,它的语法像#define 这样。在我们的例子中,每次编译器在代码中看到EXPORT_API字符,就用#define语句中的第二部分去替换它。当你在更早的窗口编译你的代码编译器也是同样的行为,它想会用__declspec(dllexport)去替换EXPORT_API,并且移除EXPORT_API。
这就是头文件的内容。现在让我们关注更有趣的东西,实际的源代码。打开C源文件并且粘贴如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include #include #include "LowLevelPlugin.h" extern "C" int ** EXPORT_API fillArray( int size) { int i = 0, j = 0; int ** array = ( int **) calloc(size, sizeof ( int *)); for (i = 0; i < size; i++) { array = ( int *) calloc(size, sizeof ( int )); for (j = 0; j < size; j++) { array[j] = i * size + j; } } return array; } |
一些更高级的用户可能注意到,我写的这些代码更像C语言而不是C++,这是因为较之C++,我更熟悉C语言。如果你不明白下面要干什么,那么如下是一个简单的说明:
我觉得头3行就不需要我解释了。我们简单地增加一些标准库和我们自己定义的头文件。
接下来是:
1 | extern "C" int ** EXPORT_API fillArray( int size) { |
这是函数定义。函数名字是“fillArray”,带有一个int型参数并且返回值是二维数组。(是的,**代表二维)“extern”使得可见范围扩展到整个程序,这个函数可以在程序或者外部库/插件的任何文件的任何地方使用。我们也有 EXPORT_API 关键词,稍后我会描述它的工作原理。
下一个有趣的东西是:
1 | int ** array = ( int **) calloc(size, sizeof ( int *)); |
这里我定义了二维的int型数组并且为它分配了内存。因为我是用C语言写代码,我不能仅仅创建数组和分配值,我必须在内存中为它保留一些空间然后再做其他操作。在这种特殊情况下,我分配了N个int*大小的内存单元并且使之指向二维int数组类型。
同样地:
1 | array = ( int *) calloc(size, sizeof ( int )); |
唯一的不同在于,现在我是为数组的第i个元素分配内存。
最后我返回了一个填充了的数组。我希望你是很清楚地明白数组的循环分配值,这样我就不用在这个教程中解释这部分内容了。
好了,C++程序完成了。我们应该如何生成库并且复制到unity中呢。点击这个大的“Play”按钮,几秒后你会看到bundle文件将会产生。

第二部分 在unity3d中使用库
右击bundle文件并且选择“Show in Finder”。复制该文件并且创建一个新的unity3d项目。在/Assets/文件夹中创建名为Plugins的文件夹,然后把bundle文件粘贴在这个文件夹中。你也要创建一个C#脚本在根目录,现在你的项目结构应该像这样:

现在我们要在unity中挂钩我们的库。要做如下的事。
1、在你的C#脚本中加上:
“using System.Runtime.InteropServices;”
2、加上外部函数定义。在我们的例子中它是:
[DllImport ("LowLevelPlugin")]
private static extern int [,] fillArray(int size);
(如果你的库名字不同,使用你自己定义的库名)
3、创建二维数组变量。
int [,] array;
4、从库中调用函数并且把结果赋值给数组变量。array = fillArray(size);
如果你小心地跟着步骤走,你的unity项目现在应该可以使用C++库了,祝贺你!
第三部分 你自己执行下测试程序(可选)
你可以使用如下代码快速比较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; [/font][font=微软雅黑] using System.Collections; using System.Runtime.InteropServices; public class PerformanceTests : MonoBehaviour { public int size = 512; [DllImport ( "LowLevelPlugin" )] private static extern int [,] fillArray( int size); void Start () { ArrayFillTest(); } private void ArrayFillTest() { var start = Time.realtimeSinceStartup; int [,] tab = fillArray(size); Debug.Log( (Time.realtimeSinceStartup-start).ToString( "f6" ) + " secs" ); start = Time.realtimeSinceStartup; int [,] array = new int [size,size]; for ( int i = 0; i < size; i++) { for ( int j = 0; j < size; j++) { array[i,j] = i * size + j; } } Debug.Log( (Time.realtimeSinceStartup-start).ToString( "f6" ) + " secs" ); } } |
在我的案例中C语言是更快。在一些诸如生成噪点的复杂操作中,结果更为明显。在大规模的数学运算中,我极力推荐这项技术。如果你的游戏重度依赖计算,它将在每一帧中为你节约几毫秒。
我希望你喜欢这篇文章,随意提问,希望得到你们的反馈。
原文链接:https://medium.com/@rafalwilinski/tutorial-create-c-c-plugins-for-unity3d-dbde7f67454#.hqsjain5e