[Unity教程] 创建unity3D的C/C++插件

发表于2016-09-18
评论0 4.5k浏览
       今天我决定分享一些我认为不是常识性的内容,并且在互联网上没有太多相关的资源。在本教程中,我将教你如何在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
       这个宏告诉编译器包含在这个块中的代码仅仅运行在基于windows的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
  看了上面的文章 热爱游戏创作的你是不是已经开始热血沸腾了呢?是不是迫不及待的想加入游戏团队成为里面的一员呢?
  福利来啦~赶快加入腾讯GAD交流群,人满封群!每天分享游戏开发内部干货、教学视频、福利活动、和有相同梦想的人在一起,更有腾讯游戏专家手把手教你做游戏!

腾讯GAD游戏程序交流群:484290331

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引