Lua與遊戲的不解之緣

本文轉載自秦元培博客:blog.csdn.net/qinyuanpei

一、什麼是Lua?

         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就是我們一直在尋找地這種語言。


      二、Lua可以做什麼?

     儘管博主已經告訴了大家太多的關於Lua語言的優秀特性,相信大家仍然會對Lua語言的能力存在懷疑。大家或許會想,Lua到底可以做什麼呢?在《Lua遊戲開發》一書中作者已經告訴了我們答案:

1、編輯遊戲的用戶界面
2、定義、存儲和管理基礎遊戲數據
3、管理實時遊戲事件
4、創建和維護開發者友好的遊戲存儲和載入系統
5、編寫遊戲的人工智能系統
6、創建功能原型,可以之後用高性能語言移植

這時候我們似乎覺得Lua語言在某種程度上就是專門爲遊戲開發而誕生的,因爲它將大量的優秀特性全部指向了遊戲開發領域,因此Lua語言走進走進遊戲開發領域變得順利成章,那麼,讓我們接着往下看吧,Lua在遊戲開發領域有那些成熟的案例吧。


      三、哪些遊戲使用了Lua?

       1、魔獸世界

       如果提到Lua在遊戲領域中第一次嶄露頭角,我們就不能不說《魔獸世界》這款遊戲,由於《魔獸世界》在其客戶端中使用了Lua,使得Lua在遊戲領域的作用第一次被展示出來,Lua語言因此在遊戲開發領域成名。Lua語言的虛擬機很輕巧,可以很容易地嵌入到客戶端程序中。如果需要更新客戶端,只需要更新腳本程序即可,無需重新編譯整個客戶端。這樣地優點使得Lua在遊戲開發領域一戰成名,可以說是《魔獸世界》爲遊戲開發領域帶來了這樣激動人心的偉大語言,作爲Lua在遊戲領域攻城略地的嘗試,《魔獸世界》功不可沒。

      2、大話西遊2

      如果說《魔獸世界》開闢Lua在國外遊戲領域地戰場,那麼網易的《大話西遊2》無疑是開啓了國內遊戲製作公司使用Lua的先河。2002年網易開發《大話西遊2》時,決定在客戶端內嵌入新的腳本語言,因爲當時使用的微軟JScript存在較多Bug、維護不便、兼容性差。當時該項目技術負責人云風吸取了《大話西遊1》時外掛氾濫的教訓,決定選擇一個新的語言,這樣既能擺脫對JScript的依賴,又能有效地打擊外掛製作者,權衡再三,最終選擇了Lua 4.0。後來《大話西遊2》在市場上取得了成功,國內遊戲開發行業紛紛受此影響採用Lua,可以說是網易Lua走進了國內開發者的視野,不過到今天爲止,Lua在國內仍然是一門較爲小衆的語言,從《大話西遊2》引領國內開發者將視角轉向Lua到今天將近10餘年地時間,此中緣由,只有大家自己去想個清楚啦。

      3、古劍奇譚

      《古劍奇譚》系列遊戲是由上海燭龍信息科技有限公司研發的大型3DRPG單機遊戲。遊戲設定源自於《山海經》,故事則以武俠和仙俠爲創作題材,以中國神話時代爲背景,講述了中國古代俠骨柔情的仙俠文化。《古劍奇譚》系列遊戲初代作品與二代作品採用的是不同的遊戲引擎和不同的戰鬥模式,儘管如此,我們依然能從中找到一個共同點,那就是在初代作品和二代作品中都毫無例外的使Lua作爲遊戲地腳本語言。例如下面是《古劍奇譚》紅葉湖迷宮場景的Lua腳本節選:

[plain] view plain copy
  1. require("Necessary")  
  2. require("StoryUtility")  
  3. require("BigMap")  
  4. require("Script_DLC4")  
  5.   
  6. --------------以下爲初始化函數-------------  
  7.   
  8. function OnEnterLevel()  
  9.      if GetStoryVersion() == 2 then  
  10.          OnDLCEnterLevelM01()  
  11.      else  
  12.          if GetMainStory() == 10100 then  
  13.              callTaskFunction("story10100")  
  14.          elseif GetMainStory() == 161900 then  
  15.              callTaskFunction("story161900")  
  16.          end  
  17.   
  18.          if gValue.MK == 1 then  
  19.              showNPC("NPC 06", false)  
  20.              showNPC("NPC 07", false)  
  21.              enableTrigger("Tri_MK",false)  
  22.          elseif gValue.MK >1 then  
  23.              showNPC("NPC 04", false)  
  24.              showNPC("NPC 05", false)  
  25.              showNPC("NPC 06", false)  
  26.              showNPC("NPC 07", false)  
  27.              enableTrigger("Tri_MK",false)  
  28.              enableTrigger("Tri_MK 02",false)  
  29.          end  
       4、仙劍奇俠傳

       既然提到了古劍奇譚,怎麼能不提仙劍奇俠傳呢?雖然和古劍奇譚初代作品發佈時間僅僅相差一年的《仙劍奇俠傳五》市場反響並沒有像遊戲製作方所預料地那樣成功,不過這部作品值得稱讚地地方還是蠻多的,因爲進步總是要比缺點多的嘛,畢竟時代在進步,我們不能總是拿仙劍初代作品的高度去要求後續作品,因爲我們已經不再是那個年齡的人,而仙劍依然要不斷地突破自身、大膽創新和進取。好了,我們暫時先感慨到這裏,仙劍四、仙劍五以及仙劍五前傳都使用了RenderWare引擎,可能唯一的不同就是仙劍五和仙劍五前傳都使用了Lua吧,下面同樣是一段從遊戲中提取的腳本:

[plain] view plain copy
  1. function baoxiang(id,npcID)  
  2.       
  3.     player.Control(0)  
  4.     pid=player.GetMainPlayer()  
  5.     player.SetAnim(pid,203)   
  6.     global.Print(id)  
  7.     global.Wait(1)  
  8.     y=flag.GetValue(15093)  
  9.       
  10.     ---------江洋大盜稱號獲得-------------    
  11.     jyd=flag.GetValue(15255)  
  12.     jyd=jyd+1  
  13.     flag.SetValue(15255,jyd)  
  14.     global.Print(jyd)  
  15.     global.AddTimer(0.5,13279)  
  16. -----------------------------------------  
  17.       
  18.     if id~=17711 then  
  19.         npc.SetAnim(npcID,501)  
  20.         global.Wait(1)  
  21.     end  
  22.           
     5金庸羣俠傳Lua復刻版

    、帶你走進Lua的世界

    最後想和大家分享是Lua語言編程的一個簡單的示例,因爲博主覺得以後做遊戲用腳本語言的場景會越來越多,所以能學會一門腳本語言能爲你的遊戲開發之路增色不少。因爲博主剛開始學,所以腳本中有不足之處,希望大家能諒解,在學校的時間一天天地在減少,博主希望能和大家共同度過最後的這段時間。博主使用的是Lua5.2,使用的Sublime Text2作爲腳本編輯器配合LuaDev插件進行編程的,如果大家想用懶惰點的辦法,可以使用Lua for Windows這個集成環境。好了,下面開始吧,作爲第一個Lua程序,我們直接給出代碼,具體的語法及API大家可以自己去查閱。
[plain] view plain copy
  1. --while-do示例代碼  
  2. myValue=10  
  3. while(myValue <= 20) do  
  4.     print(myValue)  
  5.     myValue=myValue+1  
  6. end  
  7. --sample table && for-do示例代碼  
  8. myTables={"Item0","Item1","Item2","Item3"}  
  9. for i=1,table.maxn(myTables) do  
  10.     print(myTables[i])  
  11. end  
  12. --complex table示例代碼  
  13. myTables={}  
  14. myTables["A"]="ItemA"  
  15. myTables["B"]="ItemA"  
  16. myTables["C"]="ItemA"  
  17. myTables["D"]="ItemA"  
  18. print(myTables["A"])--"ItemA"  
  19. --function示例代碼  
  20. function fib(n)  
  21.   if(n<2) then   
  22.     return n  
  23.   else  
  24.     return fib(n-1)+fib(n-2)  
  25.   end  
  26. end  
  27. --math示例代碼  
  28. maxValue=math.max(12,23,56,18,10)--56  
  29. minValue=math.min(25,34,12,75,8)--8  
  30. print(maxValue-minValue)--48  
  31. --字符串演示  
  32. myString="Hello this is the cool program language called Lua";    
  33. print(string.find(myString,"Lua"))--48,50  
  34. --io演示  
  35. io.write("Hello I get a powerful program language called Lua \n")  
  36. io.write(string.format("This Lua is %s and now is %s \n",_VERSION,os.date()))  

運行結果是:

 一、Lua堆棧

    如果我們想要理解Lua語言與其它語言交互的實質,我們首先就要理解Lua堆棧。簡單來說,Lua語言之所以能和C/C++進行交互,主要是因爲存在這樣一個無處不在的虛擬棧。棧的特點是先進後出,在Lua語言中,Lua堆棧是一種索引可以是正數或者負數的結構,並規定正數1永遠表示棧底,負數-1永遠表示棧頂。換句話說呢,在不知道棧大小的情況下,我們可以通過索引-1取得棧底元素、通過索引1取得棧頂元素。下面呢,我們通過一個實例來加深我們對於這段話的理解:

[cpp] view plain copy
  1. #include <iostream>  
  2.   
  3. extern "C" {  
  4. #include "lua.h"  
  5. #include "lualib.h"  
  6. #include "lauxlib.h"  
  7. }  
  8.   
  9. using namespace std;  
  10.   
  11. int main()  
  12. {  
  13.     //創建Lua環境  
  14.     lua_State* L=lua_open();  
  15.     //打開Lua標準庫,常用的標準庫有luaopen_base、luaopen_package、luaopen_table、luaopen_io、  
  16.     //luaopen_os、luaopen_string、luaopen_math、luaopen_debug  
  17.     luaL_openlibs(L);  
  18.     //壓入一個數字20  
  19.     lua_pushnumber(L,20);  
  20.     //壓入一個數字15  
  21.     lua_pushnumber(L,15);  
  22.     //壓入一個字符串Lua  
  23.     lua_pushstring(L,"Lua");  
  24.     //壓入一個字符串C  
  25.     lua_pushstring(L,"C");  
  26.     //獲取棧元素個數  
  27.     int n=lua_gettop(L);  
  28.     //遍歷棧中每個元素  
  29.     for(int i=1;i<=n;i++)  
  30.     {  
  31.         cout << lua_tostring(L ,i) << endl;  
  32.     }  
  33.     return 0;  
  34. }  

在上面的這段代碼中,我們可以可以看到我們首先創建了一個lua_State類型的變量L,我們可以將它理解成一個Lua運行環境的上下文(Context),這裏我們在Lua堆棧中壓入了四個元素:20、15、"Lua"、"C"然後將其輸出,如果大家理解了Lua堆棧中的索引,那麼最終輸出的結果應該是:20、15、"Lua"、"C",因爲索引1始終指向棧底,最先入棧的元素會處於棧底。因此當我們按照遞增的索引順序來輸出棧中的元素的話,實際上是自下而上輸出,這樣我們就能得到這樣的結果了。

       好了,如果這段代碼沒有什麼問題的話,接下來我們來講解Lua爲C/C++提供的接口,它們均被定義在lua.h文件中。Lua提供的C/C++接口大部分與棧操作有關,因此深入理解Lua堆棧是學習Lua語言的重點和難點。通過數據結構的知識,我們可以知道棧有出棧和入棧兩種基本操作,Lua提供的C API中入棧可以通過push系列的方法來實現,如下圖所示:


而出棧或者說查詢的方法則可以通過to系列的方法來實現,如下圖:


這兩部分是學習Lua語言一定要去了解的內容,因爲以後如果需要我們將Lua整合到其它項目中這些內容,這些東西可以說是原理性、核心性的東西。好了,下面我們利用這裏的API對一個示例代碼進行改造,這裏加入了對棧中元素類型的判斷:

[cpp] view plain copy
  1. #include <iostream>  
  2.   
  3. extern "C" {  
  4. #include "lua.h"  
  5. #include "lualib.h"  
  6. #include "lauxlib.h"  
  7. }  
  8.   
  9. using namespace std;  
  10.   
  11. int main()  
  12. {  
  13.     //創建Lua環境  
  14.     lua_State* L=lua_open();  
  15.     //打開Lua標準庫,常用的標準庫有luaopen_base、luaopen_package、luaopen_table、luaopen_io、  
  16.     //luaopen_os、luaopen_string、luaopen_math、luaopen_debug  
  17.     luaL_openlibs(L);  
  18.     //壓入一個數字20  
  19.     lua_pushnumber(L,20);  
  20.     //壓入一個字符串15  
  21.     lua_pushnumber(L,15);  
  22.     //壓入一個字符串Lua  
  23.     lua_pushstring(L,"Lua");  
  24.     //壓入一個字符串C  
  25.     lua_pushstring(L,"C");  
  26.     //獲取棧中元素個數  
  27.     int n=lua_gettop(L);  
  28.     //遍歷棧中每個元素  
  29.     for(int i=1;i<=n;i++)  
  30.     {  
  31.         //類型判斷  
  32.         switch(lua_type(L,i))  
  33.        {  
  34.           case LUA_TSTRING:  
  35.             cout << "This value's type is string" << endl;  
  36.           break;  
  37.           case LUA_TNUMBER:  
  38.             cout << "This value's type is number" << endl;  
  39.           break;  
  40.         }  
  41.         //輸出值  
  42.         cout << lua_tostring(L ,i) << endl;  
  43.     }  
  44.   
  45.     //釋放Lua  
  46.     lua_close(L);  
  47. }  

    二、Lua與C++交互

   Lua與C++的交互從宿主語言的選擇劃分上可以分爲C++調用Lua和Lua調用C++兩中類型:

   1、C++調用Lua

    使用C++調用Lua時我們可以直接利用C++中的Lua環境來直接Lua腳本,例如我們在外部定義了一個lua腳本文件,我們現在需要使用C++來訪問這個腳本該怎麼做呢?在這裏我們可以使用luaL_loadfile()、luaL_dofile()這兩個方法個方法來實現,其區別是前者僅加載腳本文件而後者會在加載的同時調用腳本文件。我們一起來看下面的代碼:

[cpp] view plain copy
  1. #include <iostream>  
  2.   
  3. using namespace std;  
  4.   
  5. #include <iostream>  
  6.   
  7. extern "C" {  
  8. #include "lua.h"  
  9. #include "lualib.h"  
  10. #include "lauxlib.h"  
  11. }  
  12.   
  13. using namespace std;  
  14.   
  15. int main()  
  16. {  
  17.     //創建Lua環境  
  18.     lua_State* L=luaL_newstate();  
  19.     //打開Lua標準庫,常用的標準庫有luaopen_base、luaopen_package、luaopen_table、luaopen_io、  
  20.     //luaopen_os、luaopen_string、luaopen_math、luaopen_debug  
  21.     luaL_openlibs(L);  
  22.   
  23.     //下面的代碼可以用luaL_dofile()來代替  
  24.     //加載Lua腳本  
  25.     luaL_loadfile(L,"script.lua");  
  26.     //運行Lua腳本  
  27.     lua_pcall(L,0,0,0);  
  28.   
  29.     //將變量arg1壓入棧頂  
  30.     lua_getglobal(L,"arg1");  
  31.     //將變量arg2壓入棧頂  
  32.     lua_getglobal(L,"arg2");  
  33.   
  34.     //讀取arg1、arg2的值  
  35.     int arg1=lua_tonumber(L,-1);  
  36.     int arg2=lua_tonumber(L,-2);  
  37.   
  38.     //輸出Lua腳本中的兩個變量  
  39.     cout <<"arg1="<<arg1<<endl;  
  40.     cout <<"arg2="<<arg2<<endl;  
  41.   
  42.     //將函數printf壓入棧頂  
  43.     lua_getglobal(L,"printf");  
  44.     //調用printf()方法  
  45.     lua_pcall(L,0,0,0);  
  46.   
  47.     //將函數sum壓入棧頂  
  48.     lua_getglobal(L,"sum");  
  49.     //傳入參數  
  50.     lua_pushinteger(L,15);  
  51.     lua_pushinteger(L,25);  
  52.     //調用printf()方法  
  53.     lua_pcall(L,2,1,0);//這裏有2個參數、1個返回值  
  54.     //輸出求和結果  
  55.     cout <<"sum="<<lua_tonumber(L,-1)<<endl;  
  56.   
  57.     //將表table壓入棧頂  
  58.     lua_getglobal(L,"table");  
  59.     //獲取表  
  60.     lua_gettable(L,-1);  
  61.     //輸出表中第一個元素  
  62.     cout <<"table.a="<<lua_tonumber(L,-2)<<endl;  
  63.   
  64. }  
在這段代碼中我們調用了一個外部的文件script.lua。這是一個Lua腳本文件,在調試階段,我們需要將其放置在和C++項目源文件同級的目錄下,而在正式運行階段,我們只需要將其和最終的可執行文件放在同一個目錄下就好了。下面是腳本代碼:

[cpp] view plain copy
  1. --在Lua中定義兩個變量  
  2. arg1=15  
  3. arg2=20  
  4.   
  5. --在Lua中定義一個表  
  6. table=  
  7. {  
  8.     a=25,  
  9.     b=30  
  10. }  
  11.   
  12. --在Lua中定義一個求和的方法  
  13. function sum(a,b)  
  14.   return a+b  
  15. end  
  16.   
  17. --在Lua中定義一個輸出的方法  
  18. function printf()  
  19.   print("This is a function declared in Lua")  
  20. end  
我們注意到在腳本文件中我們定義了一些變量和方法,在C++代碼中我們首先用lua_getglobal()方法來講Lua腳本中的變量或函數壓入棧頂,這樣我們就可以使用相關的to系列方法去獲取它們,由於每次執行lua_getglobal()都是在棧頂,因爲我們使用索引值-1來獲取棧頂的元素。C++可以調用Lua中的方法,第一步和普通的變量相同,是將Lua中定義的方法壓入棧頂,因爲只有壓入棧中,我們才能夠使用這個方法,接下來,我們需要通過push系列的方法爲棧中的方法傳入參數,在完成參數傳入後,我們可以使用一個lua_pcall()的方法來執行棧中的方法,它有四個參數,第一個參數是Lua環境狀態Lua_State,第二個參數是要傳入的參數個數,第三個參數是要返回的值的數目,第四個參數一般默認爲0。由於Lua支持返回多個結果,因此,我們可以充分利用Lua的這一特點來返回多個值。執行該方法後,其結果會被壓入棧頂,所以我們可以索引值-1來獲取函數的結果。如果函數有多個返回值,則按照函數中定義的return 順序,依次入棧,索引值-1代表最後一個返回值。好了,這就是C++調用Lua的具體實現了。

     2、Lua調用C++

     首先我們在C++中定義一個方法,該方法必須以Lua_State作爲參數,返回值類型爲int,表示要返回的值的數目。

[cpp] view plain copy
  1. static int AverageAndSum(lua_State *L)  
  2. {  
  3.     //返回棧中元素的個數  
  4.     int n = lua_gettop(L);  
  5.     //存儲各元素之和  
  6.     double sum = 0;  
  7.     for (int i = 1; i <= n; i++)  
  8.     {  
  9.         //參數類型處理  
  10.         if (!lua_isnumber(L, i))  
  11.         {  
  12.             //傳入錯誤信息  
  13.             lua_pushstring(L, "Incorrect argument to 'average'");  
  14.             lua_error(L);  
  15.         }  
  16.         sum += lua_tonumber(L, i);  
  17.     }  
  18.     //傳入平均值  
  19.     lua_pushnumber(L, sum / n);  
  20.     //傳入和  
  21.     lua_pushnumber(L, sum);  
  22.   
  23.     //返回值的個數,這裏爲2  
  24.     return 2;  
  25. }  
接下來我們在C++中使用lua_register()方法完成對該方法的註冊

[cpp] view plain copy
  1. lua_register(L, "AverageAndSum", AverageAndSum);  
這樣我們就可以在Lua環境中使用這個方法啦,前提是定義必須在執行代碼之前完成,我們在Lua腳本文件下加入對該方法的調用:

[plain] view plain copy
  1. --在Lua中調用C++中定義並且註冊的方法  
  2. average,sum=AverageAndSum(20,52,75,14)  
  3. print("Average=".average)  
  4. print("Sum=".sum)  
如果我們需要在C++中查看該方法調用的結果,那麼這個在C++中調用Lua是一樣的。好了,C++和Lua的交互終於講完了,被這塊的代碼糾結了好幾天,這下總算是搞明白了。當然這只是對原理的一種學習和理解啦,如果希望更好的使用Lua調用C++,建議瞭解這幾個項目:

LuaPlusLuaBind。這樣相信大家對於C++中的方法如何在Lua中綁定會有更好的認識吧!



    三、Lua與C#交互

   既然我們已經知道了C++是怎樣和Lua完成交互的,理論上我們可以通過編寫dll的方式將前面完成的工作繼續在C#中運行,可是這樣做我們需要花費大量時間在三種語言之間糾結,因爲這樣會增加調試的難度。之前有個做coco2dx的朋友抱怨要在C++、Javascript、Lua之間來回跑,我當時沒覺得有什麼,因爲我最困難的時候就是C#和Java項目混合的情形,如今我算是深有體會了啊,這算是報應嗎?哈哈,好了,不說這個了,好在C#與Lua的交互目方面前已經有了較好的解決方案,在開源社區我們可以找到很多的支持在C#中調用Lua的工具庫,博主這裏向大家推薦的是LuaInterface這個開源項目,這個開源項目我找到了兩個地址:

1、https://github.com/Jakosa/LuaInterface

2、http://code.google.com/p/luainterface

博主個人感覺這應該是同一個項目,因爲兩個項目的源代碼是一樣的,不過從Github上下載的項目在使用的時候會報錯,估計是我電腦裏的Lua版本和它項目裏所用的Lua的版本不一致造成的吧。下面的這個項目是可以使用的,博主這裏寫了一個簡單的示例:

[csharp] view plain copy
  1. //------------------------------------------------------------------------------  
  2. // <summary>  
  3. //     這是一個用以演示LuaInterface的簡單程序,通過LuaInterface我們可以實現在C#與Lua的  
  4. //     的相互通信。Lua是一個輕巧而高效的語言,它可以和任何語言混合使用。Lua語言最初並不是  
  5. //     爲遊戲開發而誕生,卻是因爲遊戲開發而成名。目前,在世界上有大量的遊戲使用了Lua作爲它  
  6. //     的腳本語言。如圖Unity使用了C#作爲它的語言,Lua在遊戲開發領域發揮着不可忽視的重要作  
  7. //     用。使用LuaInterface的方法如下:  
  8. //     1.C#  
  9. //     註冊Lua中可調用方法:  
  10. //    mLua.RegisterFunction(Lua調用方法名, 類, 類.GetMethod(C#方法名));  
  11. //    注:C#不要使用方法級泛型,即 void Fun<T>(string str);,如果使用,系統自動判定T爲第一個參數的類型。  
  12. //     加載Lua代碼  
  13. //     mLua.DoString(Lua代碼);  
  14. //    mLua.DoFile(Lua文件絕對路徑);  
  15. //     調用Lua方法  
  16. //     mLua.GetFunction(Lua方法).Call(參數);  注:此處參數不要傳遞dynamic類型的類,否則Lua中無法獲取屬性值  
  17. //     2.Lua  
  18. //     調用C#方法時需要先註冊註冊後按照Lua方法處理  
  19. // </summary>  
  20. //------------------------------------------------------------------------------  
  21. using System;  
  22. using LuaInterface;  
  23. namespace LuaExample  
  24. {  
  25.     public class LuaScript  
  26.     {  
  27.         //定義LuaFile屬性以便於從外部調用一個Lua腳本  
  28.         private string mLuaFile;  
  29.         public string LuaFile {  
  30.             get {  
  31.                 return mLuaFile;  
  32.             }  
  33.             set {  
  34.                 mLuaFile = value;  
  35.             }  
  36.         }  
  37.   
  38.         //Lua虛擬機  
  39.         private Lua mLua;  
  40.   
  41.         //構造函數  
  42.         public LuaScript ()  
  43.         {  
  44.             //初始化Lua虛擬機  
  45.             mLua=new Lua();  
  46.             //註冊Printf方法  
  47.             mLua.RegisterFunction("Printf",this,this.GetType().GetMethod("Printf"));  
  48.         }  
  49.   
  50.         //定義一個C#方法供Lua使用  
  51.         public void Printf(string str)  
  52.         {  
  53.             Console.WriteLine("This Method is Invoked by Lua:" + str);  
  54.         }  
  55.   
  56.         //在C#中調用Lua方法  
  57.         public void DoFile()  
  58.         {  
  59.             if(mLuaFile!="")  
  60.                 //執行Lua腳本中的代碼  
  61.                 mLua.DoFile(mLuaFile);  
  62.         }  
  63.   
  64.         //在C#中調用Lau方法  
  65.         public void DoString()  
  66.         {  
  67.             //以字符串形式定義的Lua腳本  
  68.             string mFuncString="function Add(a,b) io.write(a+b) end";  
  69.             //在Lua中定義該方法  
  70.             mLua.DoString(mFuncString);  
  71.             //調用該方法  
  72.             mLua.GetFunction("Add").Call(4,8);  
  73.         }  
  74.   
  75.         //在Lua中調用C#腳本  
  76.         public void Invoke()  
  77.         {  
  78.             //調用註冊的Printf方法  
  79.             mLua.GetFunction("Printf").Call("Hello Lua");  
  80.         }  
  81.     }  
  82. }  
接下來我們編寫一個主類來調用這個類:

[csharp] view plain copy
  1. using System;  
  2. using LuaInterface;  
  3.   
  4. namespace LuaExample  
  5. {  
  6.     class MainClass  
  7.     {  
  8.         public static void Main (string[] args)  
  9.         {  
  10.             //實例化LuaSxript  
  11.             LuaScript mLua=new LuaScript();  
  12.             //設置LuaFile  
  13.             mLua.LuaFile="D:\\test.lua";  
  14.             //調用字符串中定義的Lua方法  
  15.             mLua.DoString();  
  16.             //爲美觀考慮增加一個空行  
  17.             Console.WriteLine();  
  18.             //執行Lua文件中定義的腳本  
  19.             mLua.DoFile();  
  20.             //調用C#中定義的方法  
  21.             mLua.Invoke();  
  22.         }  
  23.     }  
  24. }  
好了,C#與Lua的交互解決了,更多的內容期待着大家自行到該項目源代碼中去尋找。好了,先這樣吧!

    四、Lua與Java交互

    和C#類似的一點是在Java中我們可以使用JNI來調用C++代碼,因此理論上Lua和Java應該是可以通過JNI來交互的,這塊博主目前沒有展開研究。這裏只給大家推薦以下工具庫:

1、LuaJava

2、luaj


好了,下面我們就來一起學習在Unity3D項目中如何使用Lua語言吧,Unity3D基於Mono虛擬機,所以理論上.NET的類庫是可以直接在Unity3D中使用的。可是考慮到Unity3D跨平臺的需要,我們選擇的工具必須在各個平臺獲得良好的支持。在前文中提到的LuaInterface理論上是可以在Unity3D中使用的,可是由於IOS不支持反射機制,所以這個類庫我們無法直接在Unity3D中使用的。在開源社區中,博主發現了雲風團隊的阿楠開發的UniLua,雲風團隊的風雲是一個在國內遊戲開發領域比較著名的人物,那麼我們今天就來選擇UniLua來作爲我們的一個工具庫吧,該項目是一個開源項目,參考了LuaInterface項目,不過在處理反射這個問題上使用了新的方法,所以目前可以完美地支持各個平臺。相信大家有了前面兩篇文章的基礎,現在已經可以從容地面對Lua API了吧。好了,我們,現在來創建一個簡單地Unity項目:

        第一步是下載UniLua:http://github.com/xebecnan/UniLua。將UniLua引用到項目中有兩種方法,一種是將該項目中的UniLua編譯成dll然後在Unity項目中使用,一種是將該項目中的UniLua直接複製到Unity 項目中,我們這裏使用第二種方法,因爲博主比較懶,呵呵。將UniLua的命名空間添加到我們項目中,我們就可以開始動手寫程序了。不過這裏,博主想說的是Mono可能會導致的一個錯誤,估計是阿楠在寫這個項目的時候使用了.NET4.0以上的版本,而在.NET4.0以上的版本是支持默認參數的構造函數的。可是由於Mono默認使用的是.NET3.5,所以在編譯項目的時候就會報錯,我們可以通過Project->Assembly-CSharp->Build->General將.NET的目標框架設爲4.0,這樣就可以解決這個問題了。好了,下面我們開始寫代碼啦,首先創建一個InvokeScript.cs的腳本:

[csharp] view plain copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using UniLua;  
  4.   
  5. public class InvokeScript : MonoBehaviour {  
  6.       
  7.     //Lua腳本文件,我們將在C#調用該腳本  
  8.     public TextAsset LuaFile;  
  9.     //Lua虛擬機  
  10.     private ILuaState mLua;  
  11.   
  12.     void Start()  
  13.     {  
  14.         //初始化Lua虛擬機  
  15.         mLua=LuaAPI.NewState();  
  16.         //加載Lua標準庫  
  17.         mLua.L_OpenLibs();  
  18.         //引用一個靜態地C#庫  
  19.         mLua.L_RequireF(CSharpLib.CLASSNAME,CSharpLib.InitLib,false);   
  20.   
  21.         //執行Lua腳本  
  22.         mLua.L_DoString(LuaFile.text);  
  23.     }  
  24.   
  25.     void OnGUI()  
  26.     {  
  27.         if(GUILayout.Button("調用Lua腳本",GUILayout.Height(30)))  
  28.         {  
  29.             InvokeLua();  
  30.         }  
  31.         if(GUILayout.Button("調用C#腳本",GUILayout.Height(30)))  
  32.         {  
  33.             InvokeCSharp();  
  34.         }  
  35.     }  
  36.  
  37.     #region 調用C#腳本  
  38.     void InvokeCSharp()  
  39.     {  
  40.         //獲取方法並傳入參數  
  41.         mLua.GetGlobal("SumAndSub");  
  42.         mLua.PushInteger(12);  
  43.         mLua.PushInteger(8);  
  44.         mLua.PCall(2,4,0);  
  45.     }  
  46.     #endregion  
  47.      
  48.     #region 調用Lua腳本  
  49.     void InvokeLua()  
  50.     {  
  51.         //獲取Lua腳本中的arg1參數  
  52.         mLua.GetGlobal("arg1");  
  53.         //輸出arg1  
  54.         Debug.Log("Lua腳本中的變量arg1="+mLua.L_ToString(-1));  
  55.   
  56.         //獲取Lua腳本中的arg2參數  
  57.         mLua.GetGlobal("arg2");  
  58.         //輸出arg2  
  59.         Debug.Log("Lua腳本中的變量arg2="+mLua.L_ToString(-1));  
  60.   
  61.         //獲取Lua腳本中的Printf方法  
  62.         mLua.GetGlobal("Printf");  
  63.         //調用Lua腳本中的Printf方法  
  64.         mLua.PCall(0,0,0);  
  65.   
  66.         //獲取Lua腳本中的Sum方法  
  67.         mLua.GetGlobal("Sum");  
  68.         //傳入參數12和25  
  69.         mLua.PushInteger(12);  
  70.         mLua.PushInteger(25);  
  71.         //調用此方法  
  72.         mLua.PCall(2,3,0);  
  73.         //獲取傳入的兩個參數及求和結果  
  74.         int a=mLua.ToInteger(-3);  
  75.         int b=mLua.ToInteger(-2);  
  76.         int sum=mLua.ToInteger(-1);  
  77.         //輸出  
  78.         Debug.Log("調用Lua腳本中的Sum方法:"+a+"+"+b+"="+sum);  
  79.     }  
  80.     #endregion  
  81.       
  82. }  
[csharp] view plain copy
  1.   

在這段腳本中,我們首先初始化了Lua環境,這一點和我們在C++中使用Lua是一樣的,因爲UniLua在設計API的時候在命名上和LuaAPI保持了高度的一致,如果你對Lua API足夠熟悉的話,那麼現在這一切對你而言應該會很簡單的。接下來,我們通過Require的形式引入了我們編寫的一個C#庫,它是一個靜態庫,目的是封裝C#方法以便於Lua腳本來調用,這一部分我們稍後會講到。接下來,我們通過Unity的AssetText加載了一個Lua腳本文件,該腳本的文件的擴展名是.txt,因爲我們只需要Lua腳本的內容。在腳本中我們定義了兩個方法InvokeLua和InvokeSharp來分別調用Lua腳本和C#腳本。好了,接下來,我們重點來講Lua調用C#腳本的這部分,因爲UniLua在調用函數這塊兒和LuaInterface不太一樣,所以我們不能再用原來的先註冊C#方法然後再像Lua腳本方法一樣,不過博主覺得這裏的原理是一樣的,不過UniLua提供了更好的方法綁定機制,我們來看下面的腳本:

[csharp] view plain copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using UniLua;  
  4.   
  5. public static class CSharpLib  
  6. {  
  7.     //當前類文件名稱,我們將在Lua腳本中使用這個名稱  
  8.     public const string CLASSNAME="CSharpLib.cs";  
  9.   
  10.     //C#庫初始化  
  11.     public static int InitLib(ILuaState lua)  
  12.     {  
  13.         NameFuncPair[] define=new NameFuncPair[]  
  14.         {  
  15.             new NameFuncPair("SumAndSub",SumAndSub),  
  16.         };  
  17.         lua.L_NewLib(define);  
  18.           
  19.         return 1;  
  20.     }  
  21.   
  22.     //我們在C#中定義一個求和差的方法  
  23.     public static int SumAndSub(ILuaState lua)  
  24.     {  
  25.         //第一個參數  
  26.         int a=lua.L_CheckInteger(1);  
  27.         //第二個參數  
  28.         int b=lua.L_CheckInteger(2);  
  29.         //計算和  
  30.         int c=a+b;  
  31.         //計算差  
  32.         int d=a-b;  
  33.           
  34.         //將參數及計算結果壓入棧  
  35.         lua.PushInteger(a);  
  36.         lua.PushInteger(b);  
  37.         lua.PushInteger(c);  
  38.         lua.PushInteger(d);  
  39.           
  40.         //有四個返回值, 儘管在C#中不支持返回多個值,可是在Lua中這樣是支持的  
  41.         return 4;  
  42.     }  
  43. }  

大家一定注意到這裏有個NameFuncPair類吧,這就是在UniLua中用來將一個C#方法和Lua方法進行綁定的方法,我們首先構造這樣一個NameFuncPair數組,然後將其加入到lua_L_NewLib()的參數中,這樣相當於是註冊了一個庫,我覺得應該就是註冊了一個方法集合吧.而CLASSNAME是一個表示當前類名稱的常量,可以取任意字符,這裏我們使用該類的文件名我們將在Lua腳本是用這個值來查找當前類.接下來,我們可以看到博主構造了一個求和差的C#方法,這個方法和Lua API中定義的方法是一致的,即我們需要指定該方法會返回的值得數目.如果我們需要返回一個值,就要把它通過push系列的方法壓入棧中.這裏我們返回了四個值,大家一定會問好是C#還支持返回多個值啊,其實呢,這是Lua語言提供給我們的一個福利啊,比如我們需要返回一個物體在3D世界裏的座標,通常情況下,我們需要用三個賦值語句才能獲取吧,可是你用Lua的話,一行代碼就可以搞定啦.好,現在我們回到InvokeScript腳本的Start方法中,大家可以注意到這裏有一個L_RequireF()的方法,前面只是輕描淡寫地說它引入了一個庫,那麼現在我們看看它具體做了什麼吧,第一個參數表示這個類的名字,指向我們定義好的CLASSNAME,第二個參數是這個類的初始化方法指向InitLib()方法,第三個參數是是否要在全局空間中使用這個庫,這裏我們選在false.好了,這樣,我們就完成了C#腳本的編寫.好了,下面我們在項目中創建一個純文本文件,我們輸入如下代碼:

[plain] view plain copy
  1. local csharplib=require"CSharpLib.cs"  
  2. arg1="Unity3D"  
  3. arg2="Unreal"  
  4. arg3="Coco2dX"  
  5.   
  6. function Printf()  
  7.   print("This is the methods invoked in Lua")  
  8. end  
  9.   
  10. function Sum(a,b)  
  11.   return a,b,a+b  
  12. end  
  13.   
  14. function SumAndSub(a,b)  
  15.   print(csharplib.SumAndSub(a,b))  
  16. end  

第一行代碼同樣是一個require的方法,這是Lua腳本中引用一個庫的方法,該方法可以引用Lua的標準庫,同樣可以引用我們定義的外部庫,大家注意到這裏的名字和我們之前定義的CLASSNAME是一樣的,因爲我們就是通過這個名字來查詢這個庫的,我們在Lua環境中註冊了這個庫,所以現在纔可以引用這個庫.在這段腳本中我們定義了幾個字符型的變量,兩個Lua方法,一個用Lua包裝的C#方法.好了,現在我們將這個文本文件指定到InvokeScript的LuaFile字段,我們通過LuaFille的text獲取腳本內容,然後通過DoString()方法來執行腳本中的內容,注意這裏要先對C#庫進行註冊,然後再執行腳本中的內容,否則會出現錯誤.好了,最後,我們來一起看看運行效果吧:


大家可以看到C#調用的Lua腳本中我們獲取了腳本中的兩個變量arg1、arg2,調用了Lua中定義的兩個方法,而最後一個方法,如我們所願,它返回了四個值,這正是我們所希望的結果.這裏順便說一下啊,在Lua中的print方法和return在Call以後是可以直接在Debug中輸出結果的,無需我們再去做Log。好了,今天的內容就是這樣啦,希望大家喜歡啊,歡迎大家關注我的博客,在下一篇文章中,博主將帶領大家繼續Lua到底,請關注博主的下一篇文章《Unity3D遊戲開發之Lua與遊戲的不解之緣終結篇:UniLua熱更新完全解讀》,謝謝大家.關於UniLua調用非靜態的類和方法,大家可以參考這篇文章:http://www.cnblogs.com/cqgreen/p/3483026.html


我們來說說Unity3D配合AssetBundle和;Lua實現熱更新。

        首先,我們來了解一下什麼是熱更新吧!所謂熱更新是指在不停機的狀態下對系統進行更改,例如我們的Windows可以在不重啓的狀態下完成補丁的更新、Web服務器在 不重啓的前提下完成對數據和文件的替換等都是熱更新的經典實例。那麼對於Unity3D而言,什麼是熱更新呢?如果我們最終發佈的Unity3D遊戲是一個Web遊戲,那麼每次遊戲加載的過程中實現對資源代碼的更新就是熱更新。如果我們最終發佈的Unity3D遊戲是一個客戶端遊戲,那麼我們在重重啓客戶端以後實現對資源代碼的更新就是熱更新。爲什麼這麼說呢?因爲Web遊戲需要保證玩家能夠及時快速地進入遊戲,因此在遊戲加載完之前,我們必須完成對遊戲資源和代碼的更新。可是對於客戶端遊戲而言,玩家可以在等待本次更新結束後再進入遊戲,而且大部分的客戶端程序在更新完後都會要求玩家重啓客戶端,所以對於客戶端遊戲而言,熱更新並非是嚴格意義上的熱更新。那麼,我們爲什麼要進行熱更新呢?答案是爲了縮短用戶獲取新版本客戶端的流程、改進用戶體驗。這其實就是博主在前文中提到的傳統單機遊戲依靠光盤載體進行發售所面臨的問題,玩家爲了獲取最新版本的遊戲,需要下載全新的客戶端並將它安裝到計算機或者手機設備上。在互聯網產品開發中有一種稱爲快速迭代的理念,試想如果我們每次對客戶端進行局部的調整,就需要玩家去下載新版本的客戶端,試問這樣的用戶體驗真得能讓用戶滿意嗎?所以現在爲了方便用戶、留住用戶、進而從留住的用戶身上賺到錢,我們總能在遊戲產品中找到熱更新的影子。我們知道在Unity3D中可以通過AssetBundle來實現對遊戲中資源的更新,在http://blog.csdn.net/janeky/article/details/17666409這篇文章中作者janeky已經給出了較爲完美地解決方案,因爲博主使用的Unity3D免費版無法使用AssetBundle的功能,而博主本人不願意使用破解版,因爲這是一個程序員的良心,所以本文更多的是從代碼更新的這個角度來講Unity3D的熱更新,對於資源的熱更新大家建議大家還是去看janeky的這篇文章吧。好了,下面正式開始Unity3D代碼級的熱更新之旅!

       在Unity官方的API中官方給出了一種基於反射的思路,即將C#腳本存儲爲文本文件,然後將其轉化爲byte字節,再通過反射技術取得該類型及其方法。理論上這樣當然沒有問題,可是我們知道由於IOS是一個封閉的系統,設計者出於安全的考慮不允許在該平臺下使用反射技術。那麼問題來了,反射並不是一個完美地解決方案。關於反射技術實現Unity3D的熱更新,大家可以參考這篇文章:http://blog.csdn.net/janeky/article/details/25923151。好了,下面我們來說說博主是如何通過Lua實現Unity3D的熱更新的吧。我們知道在Lua提供的C#接口中有一個DoString()的方法,該方法可以直接利用Lua虛擬機執行字符串中的腳本。所以,我們可以通過在本地讀取Lua腳本來執行腳本中的命令,如果我們腳本中的命令可以直接對Unity3D進行操作,那麼我們就可以通過Lua腳本來更新遊戲中的代碼邏輯。那麼,我們怎麼能讓Lua腳本操作Unity3D呢?在前一篇文章中,我們介紹了一種Require的方法,該方法可以將C#庫引入到Lua腳本中並通過Lua來執行C#庫中的方法。順着這樣的思路,博主便有了下面的設想:


在這個設想中,我們首先需要將Unity API封裝成一個C#類庫,在這個類庫中我們將會涉及動態加載場景和動態創建場景,因爲我們更新遊戲的邏輯的時候將會用到這些方法。這些方法通過封裝後我們便可以在Lua腳本通過Require方式來引用,進而我們就可以通過Lua腳本來動態地進行設計。我們設計一個固定的位置來存儲Lua腳本更新文件,這樣我們只需要對比本地版本和服務器版本是否相同就可以知道我們是否需要更新。這裏我們通過WWW來從遠程服務器上下載最新的Lua腳本更新文件,下載下來的Lua腳本處於項目外部,我們無法使用Resource.Load()這樣的方法來加載,可是我們可以通過WWW來加載一個本地文件,這樣我們就實現了Lua腳本文件的更新。當然,我們可以使用AssetBundle來更新Lua腳本文件,可是博主的免費版不支持AssetBundle,所以博主想出了這樣一個曲線救國的方法。當Lua腳本文件更新後,我們就可以在遊戲主邏輯裏通過DoString()方法來執行腳本文件中的代碼。在遊戲主邏輯裏主要的任務是比較當前版本號和服務器版本號來判斷是否需要更新,如果需要更新就下載Lua腳本更新文件然後執行腳本中的代碼,這樣我們就實現了客戶端程序的更新。好了,下面我們繼續以前一篇文章中的項目爲例來將博主的這個設想變成現實。

        首先,我們在CSharpLib.cs這個類中增加下面兩個方法並完成方法的註冊:

[csharp] view plain copy
  1. /// <summary>  
  2. /// 設置場景中物體的座標  
  3. /// </summary>  
  4. /// <returns>返回當前座標</returns>  
  5. /// <param name="lua">Lua.</param>  
  6. public static int SetPosition(ILuaState lua)  
  7. {  
  8.     //物體的名稱  
  9.     string mName=lua.L_CheckString(1);  
  10.     //傳入參數x,y,z  
  11.     float mX=(float)lua.L_CheckNumber(2);  
  12.     float mY=(float)lua.L_CheckNumber(3);  
  13.     float mZ=(float)lua.L_CheckNumber(4);  
  14.     //獲取物體  
  15.     GameObject go=GameObject.Find(mName);  
  16.     //獲取Transform  
  17.     Transform mTrans=go.transform;  
  18.     //設置遊戲體的位置  
  19.     mTrans.position=new Vector3(mX,mY,mZ);  
  20.     //返回遊戲體當前座標  
  21.     lua.PushNumber(mTrans.position.x);  
  22.     lua.PushNumber(mTrans.position.y);  
  23.     lua.PushNumber(mTrans.position.z);  
  24.     return 3;  
  25. }  
  26.   
  27. /// <summary>  
  28. /// 使用本地預設創建一個物體  
  29. /// </summary>  
  30. /// <returns>The resource.</returns>  
  31. /// <param name="lua">Lua.</param>  
  32. public static int CreateResource(ILuaState lua)  
  33. {  
  34.     //傳入資源名稱  
  35.     string mName=lua.L_CheckString(1);  
  36.     //加載本地資源  
  37.     GameObject go=(GameObject)Resources.Load(mName);  
  38.     //傳入座標參數x,y,z  
  39.     float mX=(float)lua.L_CheckNumber(2);  
  40.     float mY=(float)lua.L_CheckNumber(3);  
  41.     float mZ=(float)lua.L_CheckNumber(4);  
  42.     //創建一個新物體  
  43.     Object.Instantiate(go,new Vector3(mX,mY,mZ),Quaternion.identity);  
  44.     //返回該物體的名稱  
  45.     lua.PushString(go.name);  
  46.     return 1;  
  47. }  
好了,這樣我們就完成了一個簡單的C#類庫,下面我們來在主邏輯代碼中增加一個更新腳本的方法UpdateScripts():

[csharp] view plain copy
  1. void UpdateScript()  
  2.     {  
  3.         StartCoroutine("Download");  
  4.     }  
  5.   
  6.     /// <summary>  
  7.     /// 下載Lua腳本更新文件  
  8.     /// </summary>  
  9.     IEnumerator Download()  
  10.     {  
  11.         //從本地加載Lua腳本更新文件,假設文件已經從服務器下載下來  
  12.         WWW _WWW=new WWW(mUpdateFilesPath);  
  13.         yield return _WWW;  
  14.         //讀取服務器版本  
  15.         mLua.L_DoString(_WWW.text);  
  16.     }  

這裏的代碼邏輯很簡單,就是讀取腳本更新本地文件然後執行腳本,其中mUpdateFilePath是腳本更新文件路徑:

[csharp] view plain copy
  1. //初始化路徑  
  2. mUpdateFilesPath="file://D:\\lua_update.txt";  

這裏博主設想的是在本地存儲一個版本號,每次更新前先獲取服務器端的版本號,如果兩個版本號不同則需要從服務器上下載更新腳本文件進行更新。不過博主這裏沒有想到什麼好方法來獲取版本號,所以這裏就只寫了更新。那麼,我們來看看更新腳本文件都做了哪些事情吧!

[csharp] view plain copy
  1. local csharplib=require"CSharpLib.cs"  
  2.   
  3. csharplib.SetPosition("Cube",2,1,0)  
  4. csharplib.CreateResource("Sphere",0,0,0)  
  5. csharplib.CreateResource("Cube",1,1,0)  
首先我們通過Require引入了CSharpLib.cs 這個類庫,接下來,我們將場景中名稱爲Cube的物體的位置設爲(2,1,0)、 利用本地的兩個Prefab資源創建了一個Cube和一個Sphere。那麼,我們的設想能不能實現呢?我們一起來看最終效果吧!

執行Lua腳本更新前:


執行Lua腳本更新後:



        如我們所願,Lua腳本成功地對場景實現了一次更新。可能有的朋友會問,這裏用的是本地資源,如果我想用服務器上的資源怎麼辦呢?答案是博主最不願意提及的AssetBundle,即利用AssetBundle加載遠程資源,然後用Lua實現更新,這些邏輯可以添加到CSharpLib這個類庫中。大家可以設想一下,如果有一天我們能夠將Unity的所有方法都封裝起來,那麼我們就可以直接用Lua來創建場景了,如果要更新客戶端,只要更換Lua文件就可以了,怎麼樣是不是很簡單呢?可是Unity不開源啊,這些想法終究只是想法啦。


C#與Lua是互相調用

一.基本原理

  簡單地說,c#調用lua, 是c# 通過Pinvoke方式調用了lua的dll(一個C庫),然後這個dll執行了lua腳本。

       ULua = Lua + LuaJit(解析器、解釋器) +LuaInterface。

       其中,LuaInterface中的核心就是C#通過Pinvoke對Lua C庫調用的封裝,所以,在Unity中,LuaInterface就是C#與Lua進行交互的接口。

  下面我們以一個簡單的例子來演示C#與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)。    

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章