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什麼啊?
如果你對.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));
}
最終程式的執行效果如圖: