原文1:[Unity3D]Unity3D遊戲開發之Lua與遊戲的不解之緣(上)javascript
各位朋友,你們好,我是秦元培,歡迎你們關注個人博客,我地博客地址是blog.csdn.net/qinyuanpei。若是提到遊戲開發,你們必定會想到C/C++、DirectX、OpenGL等這些東西,但是衆所周知,遊戲行業是一個需求變化極快地行業,若是咱們採用編譯型的語言,那麼咱們可能很難跟上這個時代的步伐,由於編譯型的語言每經歷一次重大地更新,整個項目都須要從新編譯,這樣無疑會影響咱們的開發效率。那麼,有沒有一種更爲高效的遊戲開發模式呢?或許答案你們已經看到了。如今在遊戲界廣泛採用的方式是將遊戲的底層邏輯交給C/C++這樣的底層語言,而將遊戲的上層邏輯交給腳本語言。由於底層邏輯更看重效率而上層邏輯更注重靈活、便捷地使用。例如咱們熟知的Unreal引擎是採用UnrealScripts,這是一種相似於Java/C語法地語言;Unity3D引擎是採用的C#/javaScript/Boo這三種腳本語言;cocos2d-x採用地是Lua/javaScript這兩種腳本語言,將來可能會支持更多的語言。你們可能想問一個問題:什麼是腳本語言?所謂腳本語言是一種用來控制軟件應用程序且只在被調用時進行解釋或編譯的編程語言,這種語言一般以文本的形式來存儲腳本代碼。換句話說,腳本語言相似於一種指令,它縮短了傳統應用程序的編寫-編譯-連接-運行(edit-compile-link-run)這個過程,是一種解釋執行的程序。或許人們發明腳本語言的那一刻起,從未想過要將腳本語言和遊戲開發聯繫在一塊兒,不過腳本語言註定會由於遊戲開發而開拓出更爲廣闊的世界。本文將以目前遊戲開發領域較爲流行的Lua語言爲線索,深度解密遊戲開發領域與腳本語言之間千絲萬縷的聯繫。html
1、什麼是Lua?java
Lua 是一個小巧的腳本語言,巴西里約熱內盧天主教大學裏的一個研究小組於1993年開發,其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。Lua由標準C編寫而成,幾乎在全部操做系統和平臺上均可以編譯,運行。一個完整的Lua解釋器不過200k,在目前全部腳本引擎中,Lua的速度是最快的。這一切都決定了Lua是做爲嵌入式腳本的最佳選擇。相比Python和Per的內核,Lua的內核小於120KB,而Python的內核大約860KB,Perl的內核大約1.1MB。Lua語言支持面向對象編程和函數式編程,它提供了一個通用類型的表table,能夠實現數組、哈希表、集合、對象的功能。Lua支持協同進程機制。做爲一門可擴展的語言,Lua提供簡單而穩定的交互接口,如Lua和C程序可經過一個堆棧交換數據,這使得Lua語言能夠快速地和其它語言實現整合。整體來講,Lua語言具有如下優勢:(1)語言優美、輕巧 (2)性能優良、速度快 (3)可擴展性強。正由於Lua語言具有了這樣的特色,使得它能和遊戲開發領域的需求完美地結合起來,由於咱們須要這樣的一門語言,它可以和C/C++進行完美地交互,由於咱們須要它對底層進行封裝。它須要足夠地簡單,由於咱們須要簡單、靈活、快速地編寫代碼。那麼顯然Lua就是咱們一直在尋找地這種語言。ios
2、Lua能夠作什麼?git
儘管博主已經告訴了你們太多的關於Lua語言的優秀特性,相信你們仍然會對Lua語言的能力存在懷疑。你們或許會想,Lua到底能夠作什麼呢?在《Lua遊戲開發》一書中做者已經告訴了咱們答案:github
一、編輯遊戲的用戶界面
二、定義、存儲和管理基礎遊戲數據
三、管理實時遊戲事件
四、建立和維護開發者友好的遊戲存儲和載入系統
五、編寫遊戲的人工智能系統
六、建立功能原型,能夠以後用高性能語言移植編程
這時候咱們彷佛以爲Lua語言在某種程度上就是專門爲遊戲開發而誕生的,由於它將大量的優秀特性所有指向了遊戲開發領域,所以Lua語言走進走進遊戲開發領域變得順利成章,那麼,讓咱們接着往下看吧,Lua在遊戲開發領域有那些成熟的案例吧。c#
3、哪些遊戲使用了Lua?windows
一、魔獸世界api
若是提到Lua在遊戲領域中第一次嶄露頭角,咱們就不能不說《魔獸世界》這款遊戲,因爲《魔獸世界》在其客戶端中使用了Lua,使得Lua在遊戲領域的做用第一次被展現出來,Lua語言所以在遊戲開發領域成名。Lua語言的虛擬機很輕巧,能夠很容易地嵌入到客戶端程序中。若是須要更新客戶端,只須要更新腳本程序便可,無需從新編譯整個客戶端。這樣地優勢使得Lua在遊戲開發領域一戰成名,能夠說是《魔獸世界》爲遊戲開發領域帶來了這樣激動人心的偉大語言,做爲Lua在遊戲領域攻城略地的嘗試,《魔獸世界》功不可沒。
二、大話西遊2
若是說《魔獸世界》開闢Lua在國外遊戲領域地戰場,那麼網易的《大話西遊2》無疑是開啓了國內遊戲製做公司使用Lua的先河。2002年網易開發《大話西遊2》時,決定在客戶端內嵌入新的腳本語言,由於當時使用的微軟JScript存在較多Bug、維護不便、兼容性差。當時該項目技術負責人云風吸收了《大話西遊1》時外掛氾濫的教訓,決定選擇一個新的語言,這樣既能擺脫對JScript的依賴,又能有效地打擊外掛製做者,權衡再三,最終選擇了Lua 4.0。後來《大話西遊2》在市場上取得了成功,國內遊戲開發行業紛紛受此影響採用Lua,能夠說是網易Lua走進了國內開發者的視野,不過到今天爲止,Lua在國內仍然是一門較爲小衆的語言,從《大話西遊2》引領國內開發者將視角轉向Lua到今天將近10餘年地時間,此中原因,只有你們本身去想個清楚啦。
三、古劍奇譚
《古劍奇譚》系列遊戲是由上海燭龍信息科技有限公司研發的大型3DRPG單機遊戲。遊戲設定源自於《山海經》,故事則以武俠和仙俠爲創做題材,以中國神話時代爲背景,講述了中國古代俠骨柔情的仙俠文化。《古劍奇譚》系列遊戲初代做品與二代做品採用的是不一樣的遊戲引擎和不一樣的戰鬥模式,儘管如此,咱們依然能從中找到一個共同點,那就是在初代做品和二代做品中都毫無例外的使Lua做爲遊戲地腳本語言。例以下面是《古劍奇譚》紅葉湖迷宮場景的Lua腳本節選:
4、仙劍奇俠傳
既然提到了古劍奇譚,怎麼能不提仙劍奇俠傳呢?雖然和古劍奇譚初代做品發佈時間僅僅相差一年的《仙劍奇俠傳五》市場反響並無像遊戲製做方所預料地那樣成功,不過這部做品值得稱讚地地方仍是蠻多的,由於進步老是要比缺點多的嘛,畢竟時代在進步,咱們不能老是拿仙劍初代做品的高度去要求後續做品,由於咱們已經再也不是那個年齡的人,而仙劍依然要不斷地突破自身、大膽創新和進取。好了,咱們暫時先感慨到這裏,仙劍4、仙劍五以及仙劍五前傳都使用了RenderWare引擎,可能惟一的不一樣就是仙劍五和仙劍五前傳都使用了Lua吧,下面一樣是一段從遊戲中提取的腳本:
5、金庸羣俠傳Lua復刻版
1、Lua堆棧
若是咱們想要理解Lua語言與其它語言交互的實質,咱們首先就要理解Lua堆棧。簡單來講,Lua語言之因此能和C/C++進行交互,主要是由於存在這樣一個無處不在的虛擬棧。棧的特色是先進後出,在Lua語言中,Lua堆棧是一種索引能夠是正數或者負數的結構,並規定正數1永遠表示棧底,負數-1永遠表示棧頂。換句話說呢,在不知道棧大小的狀況下,咱們能夠經過索引-1取得棧底元素、經過索引1取得棧頂元素。下面呢,咱們經過一個實例來加深咱們對於這段話的理解:
在上面的這段代碼中,咱們能夠能夠看到咱們首先建立了一個lua_State類型的變量L,咱們能夠將它理解成一個Lua運行環境的上下文(Context),這裏咱們在Lua堆棧中壓入了四個元素:20、1五、"Lua"、"C"而後將其輸出,若是你們理解了Lua堆棧中的索引,那麼最終輸出的結果應該是:20、1五、"Lua"、"C",由於索引1始終指向棧底,最早入棧的元素會處於棧底。所以當咱們按照遞增的索引順序來輸出棧中的元素的話,其實是自下而上輸出,這樣咱們就能獲得這樣的結果了。
好了,若是這段代碼沒有什麼問題的話,接下來咱們來說解Lua爲C/C++提供的接口,它們均被定義在lua.h文件中。Lua提供的C/C++接口大部分與棧操做有關,所以深刻理解Lua堆棧是學習Lua語言的重點和難點。經過數據結構的知識,咱們能夠知道棧有出棧和入棧兩種基本操做,Lua提供的C API中入棧能夠經過push系列的方法來實現,以下圖所示:
而出棧或者說查詢的方法則能夠經過to系列的方法來實現,以下圖:
這兩部分是學習Lua語言必定要去了解的內容,由於之後若是須要咱們將Lua整合到其它項目中這些內容,這些東西能夠說是原理性、核心性的東西。好了,下面咱們利用這裏的API對一個示例代碼進行改造,這裏加入了對棧中元素類型的判斷:
2、Lua與C++交互
Lua與C++的交互從宿主語言的選擇劃分上能夠分爲C++調用Lua和Lua調用C++兩中類型:
一、C++調用Lua
使用C++調用Lua時咱們能夠直接利用C++中的Lua環境來直接Lua腳本,例如咱們在外部定義了一個lua腳本文件,咱們如今須要使用C++來訪問這個腳本該怎麼作呢?在這裏咱們可使用luaL_loadfile()、luaL_dofile()這兩個方法個方法來實現,其區別是前者僅加載腳本文件然後者會在加載的同時調用腳本文件。咱們一塊兒來看下面的代碼:
二、Lua調用C++
首先咱們在C++中定義一個方法,該方法必須以Lua_State做爲參數,返回值類型爲int,表示要返回的值的數目。
LuaPlus、LuaBind。這樣相信你們對於C++中的方法如何在Lua中綁定會有更好的認識吧!
3、Lua與C#交互
首先看下不一樣版本Lua介紹:
luainterface、nlua、ulua、unilua、cstolua、slua
luainterface:LuaInterface是開源的C#的lua橋接庫,配合開源庫luanet,能輕鬆實現Lua,C#相互調用和參數事件傳遞。但做者僅完成了windows程序的功能實現,跨平臺並無完成,做者於2013年4月30日中止更新luainterface,並推薦你們關注luainterface的一個分支Nlua。Nlua是實現跨平臺的Luainterface的升級版,uLua和NLua都是基於此庫升級編寫
nlua:是LuaInterface的一個分支,繼承了Luainterface的全部優勢,並將Luanet庫功能集成到代碼中,實現跨平臺(Windows, Linux, Mac, iOS , Android, Windows Phone 7 and 8),對ios平臺作了特殊處理,如支持了委託的橋接。
配合NLua有2種Lua實現,第一種是KeraLua,基於原生Lua,將C API 進行簡單的包裝,使C# 能夠方便使用 Lua C API,第二種是KopiLua,C#實現的Lua vm(對,和UniLua同樣也是純C#實現的Lua vm)。如下爲關於兩種方案的比較。
使用KeraLua,必須將lua 編譯成 Unity3D Plugin,並將編譯好的文件放到Plugins文件夾下相應的平臺文件夾中。並定義#define USE_KERALUA
使用KopiLua,定義#define USE_KERALUA便可
ulua:基於luainterface升級版,uLua = Lua + LuaJIT + LuaInterface,全平臺支持。在原生C的基礎上使用LuaJit進行加速,若是uLua效率高,LuaJit有很大功勞,做者僅僅提供了uLua插件包,並未提供整套插件源碼。此外,做者重寫了loadfile、print等api,使用很是簡單,導入package,就能夠開始編寫代碼了。
unilua:是雲風團隊阿南的做品,是lua5.2的C#版,
純C#的Lua 5.2實現,是否是感受似曾相識,對的,KopiLua也是純C#實現的Lua vm,雖然Unilua出名,可是沒有KopiLua的配套庫好用,其自身同的Ffi庫,是實驗性質的庫,不完善,做者不推薦使用,雖然做者在其商業項目中使用,可是這只是其中一部分代碼,Unilua和C#中間層的代碼做者並無開源。UniLua僅僅提供了Lua原生的接口,若是要在Lua代碼中調用C#,使用就須要把Luanet 移植到Unilua代碼中,總的來講很蛋疼,據推測Unilua方法都是使用Lua標準的命名方式,因此將luanet源碼中全部C接口所有手動改寫成Unilua 的接口,就可使用,這個工做量,等閒的時候把玩比較好。
cstolua:cstolua是做者對ulua的擴展,提升了效率
slua:也是從ulua擴展而來,官方說效率比cstolua還高,不過也有不少人質疑過 http://www.ulua.org/cstolua.html http://www.slua.net/ http://www.sineysoft.com/post/164
效率
cstolua > ulua > nlua > luainterface > unilua
既然咱們已經知道了C++是怎樣和Lua完成交互的,理論上咱們能夠經過編寫dll的方式將前面完成的工做繼續在C#中運行,但是這樣作咱們須要花費大量時間在三種語言之間糾結,由於這樣會增長調試的難度。以前有個作coco2dx的朋友抱怨要在C++、Javascript、Lua之間來回跑,我當時沒以爲有什麼,由於我最困難的時候就是C#和Java項目混合的情形,現在我算是深有體會了啊,這算是報應嗎?哈哈,好了,不說這個了,好在C#與Lua的交互目方面前已經有了較好的解決方案,在開源社區咱們能夠找到不少的支持在C#中調用Lua的工具庫,博主這裏向你們推薦的是LuaInterface這個開源項目,這個開源項目我找到了兩個地址:
一、https://github.com/Jakosa/LuaInterface
二、http://code.google.com/p/luainterface
博主我的感受這應該是同一個項目,由於兩個項目的源代碼是同樣的,不過從Github上下載的項目在使用的時候會報錯,估計是我電腦裏的Lua版本和它項目裏所用的Lua的版本不一致形成的吧。
LuaInterface中的核心就是C#經過Pinvoke對Lua C庫調用的封裝,因此,在Unity中,LuaInterface就是C#與Lua進行交互的接口。
Lua是一種很好的擴展性語言,Lua解釋器被設計成一個很容易嵌入到宿主程序的庫。LuaInterface則用於實現Lua和CLR的混合編程。
LuaInterface.Lua類是CLR訪問Lua解釋器的主要接口,一個LuaInterface.Lua類對象就表明了一個Lua解釋器(或Lua執行環境),Lua解釋器能夠同時存在多個,而且它們之間是徹底相互獨立的。
下面的這個項目是可使用的,博主這裏寫了一個簡單的示例:
Lua和C++交互的文章能夠看另一篇:Lua和C++交互詳細總結
C#和Lua相互調用看文章:
一、 C#與Lua相互調用
二、在Unity中使用Lua腳本:語言層和遊戲邏輯粘合層處理
LuaInterface簡介:
LuaInterface.Lua類是CLR訪問Lua解釋器的主要接口,一個LuaInterface.Lua類對象就表明了一個Lua解釋器(或Lua執行環境),Lua解釋器能夠同時存在多個,而且它們之間是徹底相互獨立的。
下面的簡單代碼展現瞭如下功能:
(1)CLR訪問Lua的全局域: 下標/索引操做[]
(2)CLR新建Lua的table:NewTable
(3)CLR中執行Lua腳本代碼或腳本文件:DoFile、DoString
using System; using System.Collections.Generic; using System.Linq; using System.Text; using LuaInterface; namespace TestCSharpAndLuaInterface { static void Main(string[] args) { // 新建一個Lua解釋器,每個Lua實例都相互獨立 Lua lua = new Lua(); // Lua的索引操做[]能夠建立、訪問、修改global域,括號裏面是變量名 // 建立global域num和str lua["num"] = 2; lua["str"] = "a string"; // 建立空table lua.NewTable("tab"); // 執行lua腳本,着兩個方法都會返回object[]記錄腳本的執行結果 lua.DoString("num = 100; print(\"i am a lua string\")"); lua.DoFile("C:\\luatest\\testLuaInterface.lua"); object[] retVals = lua.DoString("return num,str"); // 訪問global域num和str double num = (double)lua["num"]; string str = (string)lua["str"]; Console.WriteLine("num = {0}", num); Console.WriteLine("str = {0}", str); Console.WriteLine("width = {0}", lua["width"]); Console.WriteLine("height = {0}", lua["height"]); } } }
LuaIntrface自動對應Lua和CLR中的一些基礎類型:
[nil, null]
[string, System.String]
[number, System.Double]
[boolean, System.Boolean]
[table, LuaInterface.LuaTable]
[function, LuaInterface.LuaFunction]
以上對應關係反之亦然。
特殊類型:userdata
(1)CLR中不能自動匹配Lua類型的對象(以上基礎類型以外的類型)傳給Lua時,轉換爲userdata,當Lua把這些userdata傳回給CLR時,這些userdata又轉換回原類型對象;
(2)Lua裏面生成的userdata從Lua傳到CLR時轉換爲LuaInterface.LuaUserData。
LuaTable和LuaUserData都有索引操做[],用來訪問或修改域值,索引能夠爲string或number。
LuaFunction和LuaUserData都有call方法用來執行函數,能夠傳入任意多個參數並返回多個值。
Lua調用CLR的函數:RegisterFunction方法用來將CLR函數註冊進Lua解釋器,供Lua代碼調用,看下面這個例子:
namespace TestCSharpAndLuaInterface { class TestClass { private int value = 0; public void TestPrint(int num) { Console.WriteLine("TestClass.TestPrint Called! value = {0}", value = num); } public static void TestStaticPrint() { Console.WriteLine("TestClass.TestStaticPrint Called!"); } } class Program { static void Main(string[] args) { Lua lua = new Lua(); TestClass obj = new TestClass(); // 註冊CLR對象方法到Lua,供Lua調用 lua.RegisterFunction("LuaTestPrint", obj, obj.GetType().GetMethod("TestPrint")); // 也可用 typeof(TestClass).GetMethod("TestPrint") // 註冊CLR靜態方法到Lua,供Lua調用 lua.RegisterFunction("LuaStaticPrint", null, typeof(TestClass).GetMethod("TestStaticPrint")); lua.DoString("LuaTestPrint(10)"); lua.DoString("LuaStaticPrint()"); } } }
(二)CLR from Lua
(1)加載和實例化CLR類型
測試環境有兩種方式:
第一種:純Lua文件中進行測試
將LuaForWindows安裝的LuaInterface.dll和luanet.dll都拷貝到本身註冊的環境變量的目錄下,好比個人是"C:\\luatest\\",而後就能夠在Lua編輯器中編寫測試代碼了,以下:
--package.cpath = "C:\\luatest\\?.dll" require "luanet" --加載CLR的類型、實例化CLR對象 luanet.load_assembly("System.Windows.Forms") luanet.load_assembly("System.Drawing") Form = luanet.import_type("System.Windows.Forms.Form") StartPosition = luanet.import_type("System.Windows.Forms.FormStartPosition") print(Form) print(StartPosition)
上面的代碼演示了若是利用LuaInterface的luanet在Lua中加載CLR的類型。在配置編譯環境的時候必定要注意將兩個dll同時拷貝到一個目錄下,由於luanet.dll是依賴LuaInterfce.dll的。
第二種:在C#工程中測試
仍是在外部單獨編寫lua代碼文件,而後在C#工程中使用lua.DoFile接口運行lua代碼。這種方式比較靈活而且可以更方便的測試LuaInterface所提供的各項功能,咱們後面的測試代碼均是在這種模式系下進行測試。
這種模式下就不須要在lua腳本中手動require "luanet"了,由於已經手動將LuaInterface的引用添加到工程中了,lua腳本中直接使用luanet就能夠訪問各接口了。
luanet.load_assembly函數:加載CLR程序集;
luanet.import_type函數:加載程序集中的類型;
luanet.get_constructor_bysig函數:顯示獲取某個特定的構造函數;
c#主要代碼以下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using LuaInterface; namespace TesLuaInterface { class TestClass2 { public TestClass2(string str) { Console.WriteLine("called TestClass2(string str) str = {0}", str); } public TestClass2(int n) { Console.WriteLine("called TestClass2(int n) n = {0}", n); } public TestClass2(int n1, int n2) { Console.WriteLine("called TestClass2(int n1, int n2) n1 = {0}, n2 = {1}", n1, n2); } } // 加載和實例化CLR類型 static void Main(string[] args) { Lua lua = new Lua(); lua.DoFile("C:\\luatest\\testLuaNet.lua"); } } }
lua主要代碼以下:
-- 加載自定義類型,先加載程序集,在加載類型 luanet.load_assembly("TestEnvi") TestClass = luanet.import_type("TesLuaInterface.TestClass2") obj1 = TestClass(2, 3) -- 匹配public TestClass2(int n1, int n2) obj2 = TestClass("x") -- 匹配public TestClass2(string str) obj3 = TestClass(3) -- 匹public TestClass2(string str) TestClass_cons2 = luanet.get_constructor_bysig(TestClass, 'System.Int32') obj3 = TestClass_cons2(3) -- 匹配public TestClass2(int n)
TestEnvi爲我建的工程代碼的程序集名字,這一項是能夠在工程屬性中進行設置的,TestLuaInterface爲測試代碼的命名空間。
從上面的構造函數的匹配能夠看出,LuaInterface匹配構造函數的規律:
LuaInterface匹配第一個可以匹配的構造函數,在這個過程當中,numerical string(數字字符串)會自動匹配number,而number能夠自動匹配string,因此TestClass(3)匹配到了參數爲string的構造函數。
若是必定要手動匹配某個構造函數,則可使用luanet.get_constructor_bysic函數。
(2)訪問CLR類型對象的字段和方法
Lua代碼中,訪問CLR類型對象的字段的方式和訪問table的鍵索引同樣,好比button1.Text、button["Text"];
Lua代碼中,訪問CLR類型對象的函數的方式和調用table的函數同樣,好比form:ShowDialog()。
規則很簡單,但在訪問函數的時候,有如下幾種狀況須要注意的:
(a)當有重載函數時,函數的匹配過程和上面提到的構造函數的匹配過程同樣,自動匹配第一個可以匹配的函數。若是必定要手動調用某個特定參數的函數,可使用luanet.get_method_bysig函數來制定,好比:
setMethod=get_method_bysig(obj,'setValue','System.String')" setMethod('str')
(b)當函數有out或ref參數時,out參數和ref參數和函數的返回值一塊兒返回,而且調用函數時out參數不須要傳入,好比:
-- calling int obj::OutMethod1(int,out int,out int) retVal,out1,out2 = obj:OutMethod1(inVal) -- calling void obj::OutMethod2(int,out int) retVal,out1 = obj:OutMethod2(inVal) -- retVal ser´a nil -- calling int obj::RefMethod(int,ref int)
(c)若是一個對象有兩個Interface,而且兩個Interface都有某個同名函數好比,IFoo.method()和IBar.method(),這種狀況下obj["IFoo.method"]表示訪問前者。
訪問CLR類型對象的字段和函數的示例代碼以下:
luanet.load_assembly("System.Windows.Forms") luanet.load_assembly("System.Drawing") Form = luanet.import_type("System.Windows.Forms.Form") Button = luanet.import_type("System.Windows.Forms.Button") Point = luanet.import_type("System.Drawing.Point") StartPosition = luanet.import_type("System.Windows.Forms.FormStartPosition") form1 = Form() button1 = Button() button2 = Button() position = Point(10, 10) start_position = StartPosition.CenterScreen button1.Text = "OK" button2["Text"] = "Cancel" button1.Location = position button2.Location = Point(button1.Left, button1.Height + button1.Top + 10) form1.Controls:Add(button1) form1.Controls:Add(button2) form1.StartPosition = start_position form1:ShowDialog()
(3)事件處理,添加和刪除事件委託
LuaInterface爲Event提供了Add和Remove函數來註冊和移除事件處理函數。Add函數傳入一個Lua函數,將其轉換爲一個CLR委託(delegate),並返回這個委託。
function handle_mouseup(sender,args) print(sender:ToString() .. ’ MouseUp!’) button.MouseUp:Remove(handler) end handler = button.MouseUp:Add(handle_mouseup)
(4)LuaInterface三種擴展CLR的方法
LuaInterface提供了三種擴展CLR的方法,第一種就是上面提到的添加委託的方式,在須要delegate的地方傳入Lua function,LuaInterface利用Lua function建立一個CLR delegate 並傳入CLR。
第二種是在須要CLR Interface實例的地方傳入一個Lua Table,好比:
-- interface ISample { int DoTask(int x, int y); } -- SomeType.SomeMethod signature: int SomeMethod(ISample i) -- obj is instance of SomeType sum = {} function sum:DoTask(x,y) return x+y end -- sum is converted to instance of ISample res = obj:SomeMethod(sum)
若是Interface裏面有多個重載接口,那麼Lua Table須要實現每個版本的接口函數,而且要注意out和ref參數的處理:
-- interface ISample2 { void DoTask1(ref int x, out int y); -- int DoTask2(int x, out int y); } -- SomeType.SomeMethod signature: int SomeMethod(ISample i) -- obj is instance of SomeType inc = {} function inc:DoTask1(x) return x+1,x end function inc:DoTask2(x) return x+1,x end res = obj:SomeMethod(sum)
第三種是利用Lua Table繼承CLR Class,也就是用Table做爲其子類,這裏CLR Class必須擁有virtual函數,而且Lua Table必須至少重寫一個virtual函數。主要相關函數是luanet.make_object。
-- class SomeObject { -- public virtual int SomeMethod(int x, int y) { return x+y; } } -- SomeType.SomeMethod signature: int SomeMethod(SomeObject o) -- obj is instance of SomeType some_obj = { const = 4 } function some_obj:SomeMethod(x,y) local z = self.base:SomeMethod(x,y) return z*self.const end SomeObject = luanet.import_type(’SomeObject’) luanet.make_object(some_obj,SomeObject) res = some_obj:SomeMethod(2,3) -- returns 20 res = some_obj:ToString() -- calls base method res = obj:SomeMethod(some_obj) -- passing as argument
由於Table做爲子類實例,那麼就能夠在須要Class的地方傳入這個Table實例。注意,若是Table沒有重寫任何virtual函數,則直接返回父類對象。固然,做爲子類,能夠直接訪問父類中其餘的還接口。
以上三種概括起來就是:Lua Function-->CLR delegate、Lua Table-->CLR Interface、 Lua Table-->CLR Class。
ulua:基於luainterface升級版,uLua = Lua + LuaJIT + LuaInterface,全平臺支持。在原生C的基礎上使用LuaJit進行加速,若是uLua效率高,LuaJit有很大功勞,做者僅僅提供了uLua插件包,並未提供整套插件源碼。此外,做者重寫了loadfile、print等api,使用很是簡單,導入package,就能夠開始編寫代碼了。
uLua = Lua + LuaJit(解析器、解釋器) +LuaInterface。
uLua方案比較成熟,它並無太多本身的代碼,主要是把LuaInterface和Lua解釋器整合了一下,都是比較成熟的代碼,相對會穩定一些。
以下是構建這個例子的步驟。
(1)下載ULua源碼。
(2)在Unity中新建一個項目,並將ULua源碼拷貝到Assets目錄下。
(3)將ulua.dll(就是上面提到的C庫)放到Assets下的Plugins文件夾中。(沒有Plugins文件夾就新建一個)
(4)在Assets下的Script文件夾中新建一個腳本CSharpLuaTest.cs,並將該腳本綁定到Main Camera上。
(5)在CSharpLuaTest.cs中編輯如下內容:
public class CSharpLuaTest : MonoBehaviour { private LuaState luaState = new LuaState(); // 建立lua虛擬機 void Start () { // 在lua虛擬機(全局)中註冊自定義函數 this.luaState.RegisterFunction("CSharpMethod", this, this.GetType().GetMethod("CSharpMethod")); // 加載lua文件(絕對路徑) this.luaState.DoFile(Application.streamingAssetsPath + "/Test.lua"); // 加載完文件後,使用GetFunction獲取lua腳本中的函數,再調用Call執行。 object[] objs = luaState.GetFunction("LuaMethod").Call(999); Debug.Log(string.Format("{0} - {1}" ,objs[0], objs[1])); } //自定義功能函數,將被註冊到lua虛擬機中 public string CSharpMethod(int num) { return string.Format("Hello World {0} !" , num+1); } void Update () { } }
(6)在Assets下的StreamingAssets文件夾中新建一個Lua腳本文件Test.lua,打開Test.lua文件,並編輯以下內容:
1
2
3
4
|
function
LuaMethod(i)
s = CSharpMethod(i); --調用C
#方法
return
i,s;
end
|
(7)運行Unity項目,則能夠看到輸出:999 - Hello World 1000 !
最後簡單說一下上面代碼的要點:
1.若是一個C#方法要被Lua調用,則首先要將其註冊到Lua虛擬機中(LuaState.RegisterFunction)。以後,在Lua中就能夠經過註冊的名稱來調用這個C#方法。
2.若是C#要調用Lua中的函數,則
(1)首先要在Lua虛擬機中加載該函數(LuaState.DoFile)。
(2)拿到目標函數(LuaState.GetFunction)。
(3)執行目標函數(LuaFunction.Call)。