1. 程式人生 > >Unity3D遊戲開發之C++外掛接入

Unity3D遊戲開發之C++外掛接入

  各位朋友大家好,我是秦元培,歡迎大家關注我的部落格,我的部落格地址是http://qinyuanpei.com。雖然Unity3D引擎依靠強大的跨平臺能力睥睨高手林立的遊戲引擎世界,我們在使用Unity3D遊戲引擎的時候基本上不會去接觸底層的東西,可是有時候面對某些奇葩的要求的時候,我們就不得不考慮使用C++這樣的語言來為其編寫相關的外掛。你如果問我是什麼樣的奇葩要求,比如接入藍芽手柄來控制遊戲、接入類似街機的裝置來控制遊戲、接入同一個遊戲到兩個不同的裝置上並響應不同的控制……諸如此類的種種問題,可能目前在Unity3D引擎中找不到解決方案,這個時候寫C++外掛就變成了一種剛性需求,這就是我們今天要來一起探討的問題。

  Unity3D主要使用C#進行開發,所以為Unity3D編寫外掛本質上就是讓C#呼叫C++程式碼。目前主要有C++ CLR和C++ Native兩種實現方法,其中C++ CLR可以理解為執行在.Net CLR即公共語言執行庫上的C++程式碼,這種程式碼是託管的C++程式碼,目前並沒有被C++標準承認,因為它更像是C++和C#兩種語言的混合程式碼,這種程式碼的優勢是可以像普通的.NET庫一樣被C#呼叫,考慮到Unity3D建立在和.Net類似的Mono上,因此這種方式應該是我們的最佳實踐方案;C++ Native則是指傳統的C++ 動態連結庫,通過DllImport在C#中進行包裝後在C#中進行呼叫,相對地這種方式呼叫的是非託管的C++程式碼,這種方式相信接觸過Windows開發的朋友應該不會感到陌生啦,它是一種更為普遍的方法,例如我們要接入蘋果官方SDK的時候,需要對Object C的程式碼進行封裝後交給C#去呼叫,而這裡使用的方法就是DllImport了。

  好了,下面我們來看看兩種方式各自是如何實現的吧!這裡博主使用的開發環境是Windows 8.1 32bit 和 Visual Studio 2012,Unity3D的版本為4.6版本。

C++ CLR

建立一個C++ CLR類庫專案

  首先我們按照下圖中的步驟建立一個C++ CLR專案:

截圖是件討厭的事情,雖然懶惰的人們都喜歡

請注意.Net版本問題,重要的事情說三遍,不認真看這裡的人出現問題就不要到我這裡來評論了,我最討厭連文章都沒有看明白就來和你糾纏不清的人,謝謝。建立好專案後請開啟專案屬性視窗設定【公共語言執行時支援】節點的值為【安全 MSIL 公共語言執行時支援(/clr:safe)】好了,下面我們找到CLR4Unity.h檔案,新增ExampleClass宣告:

/// <summary>
/// 一個簡單的託管C++示例類
/// </summary>
public  ref  class ExampleClass
{
    public:

    /// <summary>
    /// 產生一個介於min和max之間的整型隨機數
    /// <returns>整型隨機數</returns>
    /// <param name="min">最小值</param>
    /// <param name="max">最大值</param>
    /// </summary>
    static int  Random(int min,int max)
    {
        //注意在託管的C++中使用gcnew來代替new
        //我承認C++寫CLR程式碼略顯奇葩像是C++和C#語法的混合
        return (gcnew System::Random)->Next(min,max);
    }

    /// <summary>
    /// 計算一個整數的平方
    /// <returns>整型數值</returns>
    /// <param name="a">需要平方的數值</param>
    /// </summary>
    static int Square(int a)
    {
        return a * a;
    }

    /// <summary>
    /// 返回兩個數中的最大值
    /// <returns>整型數值</returns>
    /// <param name="a">引數1</param>
    /// <param name="b">引數2</param>
    /// </summary>
    static int Max(int a,int b)
    {
        if(a<=b){
            return b;
        }else{
            return a;
        }
    }
};

顯然我們這裡定義了三個簡單的方法,注意到第一個方法Random依賴於System.Rnadom類,而在託管的C++中是使用gcnew來代替new這個關鍵字的,所以請盡情感受C#和C++的混搭語法風格吧!這樣我們就可以編譯得到CLR4Unity.dll這個類庫,將這個檔案複製到Unity3D專案中的Plugins目錄下下,然後將其加入專案引用列表。如果你以為引用就是:

using CLR4Unity;

呵呵,我嚴重懷疑你對.Net的熟悉程度。你沒有新增對CLR4Unity.dll的引用,你到底在using什麼啊?

先新增引用然後using

如果你對.NET熟悉到足以無視這裡的一切,請閉上眼接著往下看,哈哈!

在C#中新增引用及方法呼叫

  接下來我們在Unity3D中建立一個指令碼PluginTest.cs,然後在OnGUI方法增加下列程式碼。可是你要以為這些程式碼就應該寫在OnGUI方法中,抱歉請你先去了解MonoBehaviour這個類。什麼?添加了這些程式碼報錯?沒有using的請自行面壁:

//呼叫C++ CLR中的方法
if(GUILayout.Button("呼叫C++ CLR中的方法", GUILayout.Height (30))) 
{
    Debug.Log("呼叫C++ CLR中的方法Random(0,10):" + ExampleClass.Random(0,10));
    Debug.Log("呼叫C++ CLR中的方法Max(5,10):" + ExampleClass.Max(5,10));
    Debug.Log("呼叫C++ CLR中的方法Square(5):" + ExampleClass.Square(5));
}

C++ Native

建立一個C++動態連結庫專案

  首先我們按照下圖中的步驟來建立一個C++ Win32專案:

不要問我從哪裡來

我的故鄉在遠方

好了,接下來我們找到Native4Unity.cpp寫入下列程式碼:

// Native4Unity.cpp : 定義 DLL 應用程式的匯出函式。
//

#include "stdafx.h"
//為了使用rand()函式引入C++標準庫
#include "stdlib.h"

/// <summary>
/// 產生一個介於min和max之間的整型隨機數
/// <returns>整型隨機數</returns>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
/// </summary>
extern "C" __declspec(dllexport) int Random(int min,int max)
{
    return rand() % (max - min + 1) + min;
}

/// <summary>
/// 返回兩個數中的最大值
/// <returns>整型數值</returns>
/// <param name="a">引數1</param>
/// <param name="b">引數2</param>
/// </summary>
extern "C" __declspec(dllexport) int Max(int a ,int b)
{
    if(a<=b){
       return b;
    }else{
       return a;
    }
}

/// <summary>
/// 計算一個整數的平方
/// <returns>整型數值</returns>
/// <param name="a">需要平方的數值</param>
/// </summary>
extern "C" __declspec(dllexport) int Square(int a)
{
    return a * a;
}

和C++ CLR類似,我們使用標準的C++語言來實現同樣的功能。注意到rand()這個函式是C++標準庫裡的內容,所以我們在檔案開頭增加了對stdlib.h這個標頭檔案的引用。這裡需要注意的一點是:所有希望使用DllImport引入C#的C++方法都應該在方法宣告中增加__declspec(dllexport)關鍵字,除非它在.def檔案中對這些方法進行顯示宣告。關於.def檔案的相關定義大家可以到MSDN上檢索,這些都是屬於C++編譯器的內容,這裡不再詳細說了。

在C#中使用DllImport封裝方法

  將編譯好的Native4Unity.dll複製到Plugins目錄中後,下面我們要做的事情就是在C#裡對這些方法進行封裝或者說是宣告:

 [DllImport("Native4Unity")]
 private extern static int Random(int min, int max);

 [DllImport("Native4Unity")]
 private extern static int Max(int a, int b);

 [DllImport("Native4Unity")]
 private extern static int Square(int a);

然後就是簡單地呼叫啦:

//呼叫C++ Native中的方法
if(GUILayout.Button("呼叫C++ Native中的方法", GUILayout.Height (30))) 
{
    Debug.Log("呼叫C++ Native中的方法Random(0,10):" + Random(0, 10));
    Debug.Log("呼叫C++ Native的方法Max(5,10):" + Max(5, 10));
    Debug.Log("呼叫C++ Native中的方法Square(5):" + Square(5));
}

最終程式的執行效果如圖:

這個結果來之不易請大家珍惜