Unity ToLua & LuaFramework_UGUI學習筆記

基礎部分:

解決的問題

更新頻繁,而IOS審覈長

IOS無法用DLL反射機制去做代碼更新

如果無熱更新,客戶端每次都需要重新下載一次安裝包。用戶體驗不好

解決發佈包的容量問題,一切都可以增量下載

原理

首先要清楚Unity的打包原理,也就是AssetBundle的打包機制,他會把prefab打包成.asset格式作爲傳輸的數據。

通過校驗文件的MD5值來判斷是否需要更新,如果需要更新則下載差異文件。

lua屬於解釋性文件所以能通過www直接下載到本地,通過C#與lua交互,把邏輯寫在lua裏,從而實現代碼熱更新。注意記得加密lua。

實現方案

這裏我推薦LuaFramework 這個框架(下簡稱tolua),以上內容他已經完全封裝好,包括上述沒提到的CG的一些操作。

我們只需要在tolua裏面寫屬於自己模塊部分的邏輯就能簡單的實現熱更新。

Tolua學習

簡介

tolua#是Unity靜態綁定lua的一個解決方案,它通過C#提供的反射信息分析代碼並生成包裝的類。它是一個用來簡化在C#中集成lua的插件,可以自動生成用於在lua中訪問Unity的綁定代碼,並把C#中的常量、變量、函數、屬性、類以及枚舉暴露給lua。它是從cstolua衍變而來。從它的名字可以看出,它是集成了原來的tolua代碼通過二次封裝寫了一個C#與tolua(c)的一箇中間層。

基礎

要想了解tolua#是如何集成的我們需要對C#的一些特性有一些瞭解,比如瞭解C#與原生代碼交互的方式等。,我們假設讀者已經對lua和tolua++有一個比較熟悉,我們略過lua與c或者C++的交互方式,主要介紹一些C#的特性,來幫助我們接下來分析tolua#的集成原理。

C#特性

Attribute

Attribute 是一種可由用戶自由定義的修飾符(Modifier),可以用來修飾各種需要被修飾的目標。特性Attribute 的作用是添加元數據。元數據可以被工具支持,比如:編譯器用元數據來輔助編譯,調試器用元數據來調試程序。Unity以及tolua#中就會用Attribute來輔助做一些事情。

值類型與引用類型

只所以要提這兩個概念,是因爲很好得理解這兩個概念有助於我們寫出比較高效的C#代碼。

我們知道,C#中的每一種類型要麼是值類型,要麼是引用類型。所以每個對象要麼是值類型的實例,要麼是引用類型的實例。

引用類型和值類型都繼承自System.Object類。不同的是,幾乎所有的引用類型都直接從System.Object繼承,而值類型則繼承其子類,即直接繼承System.ValueType。

作爲所有類型的基類,System.Object提供了一組方法,這些方法在所有類型中都能找到,其中包含toString方法及clone等方法。

System.ValueType直接繼承System.Object,即System.ValueType本身是一個類類型,而不是值類型;System.ValueType沒有添加任何成員,但覆蓋了所繼承的一些方法,使其更適合於值類型。例如,ValueType重寫了Equals()方法,從而對值類型按照實例的值來比較,而不是引用地址來比較。

 

簡單瞭解了值類型與引用類型那麼我們下面來看下C#中的裝箱和拆箱的概念。

裝箱和拆箱

裝箱和拆箱是值類型和引用類型之間相互轉換是要執行的操作。

1.    裝箱在值類型向引用類型轉換時發生

2.    拆箱在引用類型向值類型轉換時發生

```

object objValue = 4;

int value = (int)objValue;

```

上面的兩行代碼會執行一次裝箱操作將整形數字常量4裝箱成引用類型object變量objValue;然後又執行一次拆箱操作,將存儲到堆上的引用變量objValue存儲到局部整形值類型變量value中。

同樣我們需要看下IL代碼:

```

locals init (

[0] object objValue,

[1] int32 'value'

//上面IL聲明兩個局部變量object類型的objValue和int32類型的value變量

IL_0000: nop

IL_0001: ldc.i4.4 //將整型數字4壓入棧

IL_0002: box [mscorlib]System.Int32 //執行IL box指令,在內存堆中申請System.Int32類型需要的堆空間

IL_0007: stloc.0 //彈出堆棧上的變量,將它存儲到索引爲0的局部變量中

IL_0008: ldloc.0//將索引爲0的局部變量(即objValue變量)壓入棧

IL_0009: unbox.any [mscorlib]System.Int32 //執行IL 拆箱指令unbox.any 將引用類型object轉換成System.Int32類型

IL_000e: stloc.1 //將棧上的數據存儲到索引爲1的局部變量即value

```

拆箱操作的執行過程和裝箱操作過程正好相反,是將存儲在堆上的引用類型值轉換爲值類型並給值類型變量。

C#調用原生代碼

因爲tolua#底層庫是使用的tolua(c語言編寫),那麼就需要通過C#來調用原生代碼,我們從LuaDll.cs中摘取一段代碼來演示如何從C#中調用原生代碼

```

Public class LuaDll

{

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]

public static extern void lua_close(IntPtr luaState);

}

```

其中LUADLL對應的字符串就是tolua,在不同的平臺上mono會去加載對應的tolua.dll或者tolua.so等文件並調用對應的函數。具體可以參考mono官方的教程。

tolua#集成

tolua#集成主要分爲兩部分,一部分是運行時需要的代碼包括一些手寫的和自動生成的綁定代碼,另一部分是編輯器相關代碼,主要提供代碼生成、編譯lua文件等操作,具體就是Unity編輯器中提供的功能。用mono打開整個tolua#的工程,

Runtime

Source

Generate 這個文件裏面是生成的綁定代碼

LuaConst.cs 這個文件是一些lua路徑等配置文件。

ToLua

BaseLua 一些基礎類型的綁定代碼

Core 提供的一些核心功能,包括封裝的LuaFunction LuaTable LuaThread LuaState LuaEvent、調用tolua原生代碼等等。

Examples 示例

Misc 雜項,目前有LuaClient LuaCoroutine(協程) LuaLooper(用於tick) LuaResLoader(用於加載lua文件)

Reflection 反射相關

Editor

Editor

Custom

CustomSettings.cs 自定義配置文件,用於定義哪些類作爲靜態類型、哪些類需要導出、哪些附加委託需要導出等。

ToLua

Editor

Extend 擴展一些類的方法。

ToLuaExport.cs 真正生成lua綁定的代碼

ToLuaMenu.cs Lua菜單上功能對應的代碼

ToLuaTree.cs 輔助樹結構

Generate All 流程

瞭解了tolua#的大致文件結構,下面我們來看下tolua#的Generate All 這個功能來分析下它的生成過程。生成綁定代碼主要放在ToLuaExport.cs裏面,我們並不會對每一個函數進行細緻的講解,如果有什麼不瞭解的地方,可以直接看它的代碼。

 

GenLuaDelegates函數

生成委託綁定的代碼,它會從CustomSettings.customDelegateList裏面取出所有自定義導出的委託列表,然後把CustomSettings.customTypeList裏面的所有類型中的委託根據一定規則加入到list中,最後調用ToLuaExport.GenDelegates()方法來生成委託綁定的代碼,生成的代碼在DelegateFactory.cs文件中。

由於該函數比較簡單,我們這裏不做展開,可以直接查看ToLuaExport.cs中的GenDelegates()並配合DelegateFactory.cs來查看。

GenerateClassWraps 函數

遍歷所有需要導出的類,然後調用ToLuaExport.Generate()方法來生成類的綁定代碼。

下面我們來看下ToLuaExport.Generate()方法,其基本流程如下所示:

 

從上面的流程圖我們可以看到,整個過程還是比較清楚的,如果這個類是枚舉類型,那麼它會調用枚舉導出的接口,而如果這個類型是一個普通的類,那麼它就會調用上圖所示的相應的流程將代碼導出。至於結構體類型,目前應該是隻支持一些特定的結構體,需要在lua中對應一份實現(Assets\ToLua\Lua目錄中),當然它生成的代碼也有一些依賴於tolua#的核心運行時,我們前面簡單的講解了如何在編輯器中生成綁定代碼,接下來我們講一下它的核心運行時。

tolua#的核心運行時

tolua#的運行代碼包含SourceàGenerate下面的綁定代碼以及ToLuaàBaseType代碼以及Core下面的核心代碼。接下來我們着重講一下Core下面的幾個主要類。

LuaAttribute.cs

我們前面基礎知識部分已經講過,它在tolua#生成綁定代碼時做一些標示使用。

LuaBaseRef.cs

Lua中對象對應C#中對象的一個基類,主要作用是有一個reference指向lua裏面的對象,引用計數判斷兩個對象是否相等等。

ToLua的官方地址

國內的資料不是太多,所以有什麼疑問可以去官網或交流羣查詢。

本篇文章使用的UGUI + tolua的 一個組合,Unity版本必須是5.x,因爲Unity5對於之前的版本的ab打包策略更改。

Tolua的UI架構是使用的puremvc,給我的感覺是把簡單的東西所複雜化,但是優點在於把所複雜的邏輯解耦成機械操作,其實對於成熟的項目來說,我還是推薦不要捨棄mvc,當然如果實在不喜,也是可以自己修改,或者寫插件便於開發。

例子的實現

1.先下載LuaFramework UGUI,我們的項目也是從這個項目所修改而來。

很多東西別人寫過的,我就不直接複製過來,而是給出鏈接,各位自行去看。

2.LuaFramework目錄詳解

鏈接中的目錄是ulua的結構目錄,其實這是一樣的,tolua核心的思想還是和ulua是沒差別的。

3.找到Examples裏面的,main.scene。

按照LuaFramework的基本用法去進行操作,可以實現熱更新。在打包的過程中,我們發現生成了StreamingAssets這個文件夾,這是個可讀文件夾。在各個平臺都有對應的目錄,所以之後所有的更新文件都會在這個目錄下,而我們所打包的文件也是通過更新StreamingAssets文件去實現更新迭代的。

lua源碼目錄介紹:

---3rd:裏面是第三方的一些插件lua、實例源碼文件,比如:cjson、pbc、pblua、sproto等。

---Common:公用的lua文件目錄,如define.lua文件,一些變量聲明,全局配置等,functions.lua常用函數庫,通訊的protocal.lua協議文件。

---Controller:控制器目錄,它不依賴於某一個Lua面板,它是獨立存活在Luavm中的一個操作類,操作數據、控制面板顯示而已。

---Logic:目錄裏面存放的是一些管理器類,比如GameManager遊戲管理器、NetworkManager網絡管理器,如果你有新的管理器可以放到裏面。

---System:這個目錄是cstolua的系統目錄,裏面存放都是一些常用的lua類,爲了優化lua調用速度,用lua重寫的unity常用類。

---View:這是面板的視圖層,裏面都是一些被Unity調用的面板的變量,走的是Unity GameObject的生命週期的事件調用。

lua和C#的交互兩個工具類:

LuaHelper.cs靜態類//lua調用C#

Util.cs //靜態方法CallMethod C#調用lua

希望大家一起學習一起分享,讓lua網絡資料更豐富一些!

如果有什麼理解的不好不到位,求大神指點!

下面是我做的幾個分析圖:

應用部分:

1、安裝框架

只要在http://www.ulua.org/index.html下載LuaFramework,然後用Unity3D打開,這裏用的是LuaFramework_UGUI-1.0.4.109版本以及Unity3D 5.2,其他版本理應相似。打開之後需要點擊lua菜單裏面的Generate All和LuaFramework菜單裏Build XXX Resources,以生成一些必要的文件。

安裝過程可以參見http://pan.baidu.com/s/1gd8fG4N裏面的01_uLua_Windows.avi和02_SimpleFramework_UGUI_Windows.avi兩個視頻(如果在windows系統下)。框架結構請參見http://doc.ulua.org/article/ngui/simpleframework_base1.html,這裏不再複述。

若運行後能夠彈出示範界面,證明安裝成功,可以進入下一步。

 

成功運行示範界面(可要客戶端能夠運行起來就行)

 

2、運行Lua代碼

這一步的目標很簡單,就是讓框架運行我們自己寫的lua代碼,顯示一句“helloWorld”。下一步再考慮代碼的熱更新問題。

1)新建場景,並在任意物體中添加Main組件。其實Main組件裏面只是調用了AppFacade.Instance.StartUp(),這是框架的起點。框架將會自動完成資源加載、熱更新等等事項。

 

添加Main組件

 

2)刪掉示例的調用。現在不需要框架自帶的示例了,需要刪掉一些代碼,使框架只運行我們編寫的lua文件。打開Assets\LuaFramework\Scripts\Manager\GameManager.cs,將OnInitialize修改成下圖這個樣子。這是lua的入口,框架會調用Main.lua的Main方法。

 

lua入口

3)打開Assets\LuaFramework\Lua\main.lua,編寫lua代碼。這裏只添加一句“LuaFramework.Util.Log("HelloWorld");”(如下所示),它的功能相當於Debug.Log("HelloWorld")。

 

“LuaFramework.Util.Log("HelloWorld")”中的Util是c#裏定義的類,供lua中調用。可以打開Assets\LuaFramework\Editor\CustomSettings.cs看到所有可以供lua調用的類,如下圖是CustomSettings.cs的部分語句。

CustomSettings.cs的部分語句

再由具體的類可以查找所有的API(參見下面兩個圖),如下圖是Util類的部分語句。

4)運行遊戲。點擊菜單欄中LuaFramework→Build Windows Resource,生成資源文件。然後運行遊戲,即可在控制檯中看到打印出的HelloWorld。

生成資源文件

 

運行結果

5)調試模式。按照默認的設置,每更改一次lua代碼,都需要執行Build XXX Resource才能生效。讀者可以將Assets\LuaFramework\Scripts\ConstDefine\AppConst.cs中的LuaBundleMode修改爲false,這樣代碼文件便不會以AssetBundle模式讀取,會直接生效,以方便調試。

設置LuaBundleMode

3、代碼熱更新

熱更新的原理

接下來便要嘗試代碼熱更新,讓程序下載服務器上的lua文件,然後運行它。在說明熱更新之前,需要先看看Unity3D熱更新的一般方法。如下圖所示,Unity3D的熱更新會涉及3個目錄。

熱更新的過程

遊戲資源目錄:裏面包含Unity3D工程中StreamingAssets文件夾下的文件。安裝遊戲之後,這些文件將會被一字不差地複製到目標機器上的特定文件夾裏,不同平臺的文件夾不同,如下所示(上圖以windows平臺爲例)

數據目錄:由於“遊戲資源目錄”在Android和IOS上是隻讀的,不能把網上的下載的資源放到裏面,所以需要建立一個“數據目錄”,該目錄可讀可寫。第一次開啓遊戲後,程序將“遊戲資源目錄”的內容複製到“數據目錄中”(步驟1,這個步驟只會執行一次,下次再打開遊戲就不復制了)。遊戲過程中的資源加載,都是從“數據目錄”中獲取、解包(步驟3)。不同平臺下,“數據目錄”的地址也不同,LuaFramework的定義如下:

網絡資源地址:存放遊戲資源的網址,遊戲開啓後,程序會從網絡資源地址下載一些更新的文件到數據目錄。

這些目錄包含着不同版本的資源文件,以及用於版本控制的files.txt。Files.txt的內容如下圖所示,裏面存放着資源文件的名稱和md5碼。程序會先下載“網絡資源地址”上的files.txt,然後與“數據目錄”中文件的md5碼做比較,更新有變化的文件(步驟2)。

 

files.txt

LuaFramework的熱更新代碼定義在Assets\LuaFramework\Scripts\Manager\GameManager.cs,真正用到項目時可能還需少許改動。

開始熱更新代碼吧!

那麼開始測試熱更新代碼的功能吧!熱更上述實現的“HelloWorld”。

1)修改配置。框架的默認配置是從本地加載文件,需要打開AppConst.cs將UpdateMode設置爲true(纔會執行步驟2),將LuaBundleMode設置爲true,將WebUrl設置成服務器地址。如下圖所示。

AppConst的配置

2)配置“網絡資源”。筆者使用iis開啓本地服務器,然後將StreamingAssets裏面的所有內容複製到服務器上面。必要時要配置一些權限,讓所有文件都都可以下載

3)測試熱更。改一下Lua腳本(如將HelloWorld改爲Hello Lpy2),點擊Build Windows Resource,將“工程目錄/StreamingAssets”裏面的文件複製到服務器上。再將腳本改成其他內容,然後Build Windows Resource,覆蓋掉本地資源。運行遊戲,如果程序顯示“Hello Lpy2”的代碼,證明成功從網上拉取了文件。

 

代碼熱更新

附錄:

一、語法:

局部變量聲明:用local聲明 相當於javascript var

聲明類: classA={}

int類型轉換成string:

tostring(i);

字符串相加+用“..”: "A".."B"="AB"

屬性獲取用. 調用方法用:   A.a;  A:a();

if條件語句: if a==0 then  print('幹啥') end

if..else條件控制:if 條件 then … elseif 條件 then … else … end

注意:lua 中沒有 switch case 語句

1.應用類庫using UnityEngine:luanet.load_assembly('UnityEngine')

2.lua全局使用C# Coment ,define.lua聲明後其他lua腳本直接使用:

GameObject=UnityEngine.GameObject

ParticleSystem=UnityEngine.ParticleSystem

3.添加腳本:newGameObj:AddComponent(luanet.ctype(ParticleSystem))

4.場景新建一個GameObject: local newGameObj=GameObject('NewObj')

5.Unity對象轉lua對象:

localGo= newObject(obj);

6.C#獲得Lua函數LuaFunction f=l.GetFunction("函數名");f.Call(參數)調用

7.lua協程開啓:coroutine.start(方法名);等待:coroutine.wait(時間s);

8.根據transform查找子對象:

local label = go.transform:FindChild('##/Text');

9.獲取腳本:transform:GetComponent('LuaBehaviour');

10.輸出信息:

logWarn("OnDestroy---->>>");

log(go.name);

一、tolua#

c#調用lua:LuaState[變量名/函數名]

1.LuaState

a.執行lua代碼段

DoString(string)

DoFile(.lua文件名)

Require(.lua文件名(但沒有.lua後綴))

b.獲取lua函數或者表

LuaFunction func = lua.GetFunction(函數名);             或者           LuaFunction func = lua[函數名] as LuaFunction;

LuaTable table = lua.GetTable(表名);

c.Start():如果需要使用wrap,則需要調用該方法

2.LuaFunction

Call()

3.LuaTable

LuaTable[變量名/函數名]

ToArray()

lua調用c#

在c#中將引用傳遞到lua中後:

1.通過“.” (點號)來使用非靜態的變量以及靜態的變量與方法

2.通過'':“ (冒號)來使用非靜態的方法

3.通過"{}"來傳遞數組給c#

4.創建GameObject:newObject(變量)

5.摧毀GameObject:destroy(變量)

6.獲取組件:GetComponent('LuaBehaviour')

二、LuaFramework(使用PureMVC)

a.基礎模塊

1.Util:對Mono的功能進行封裝,這樣不繼承Mono的類就能使用Mono的東西了(如transform.Find、GetComponent);還有其他的工具方法

2.AppFacade:繼承Facade,整套框架的入口

3.Base:繼承MonoBehaviour,是一切View和Manager的基類;持有各種Manager的引用;能註冊移除(view所感興趣的)信息

4.View:只有一個方法:public virtual void OnMessage(IMessage message) 這是處理信息的方法

5.Manager:繼承Base

6.AppView:繼承View,是一個範例:註冊View所感興趣的信息,處理信息

b.lua模塊

1.LuaFileUtils:通過.lua文件路徑和AssetBundle文件路徑這兩種方式來找.lua文件,並讀取返回byte[]

2.LuaLoader:繼承LuaFileUtils,並無重要變化

3.LuaEvent:類似c#中的event,提供Add和Remove方法

4.LuaLooper:繼承MonoBehaviour,在Update / LateUpdate / FixedUpdate中執行對應的LuaEvent

5.LuaBinder:如果執行的.lua文件需要用到unity中的類型,則需要用這個類給LuaState進行綁定

6.LuaManager:繼承Manager,入口類,初始化Lua代碼加載路徑(調試模式下是在Assets \ LuaFramework目錄下,非調試模式是在C:\luaframework\lua(window系統),默認是非調試模式),引用一個LuaState並封裝其功能(讀取lua文件、調用方法等)

7.LuaBehaviour:繼承View,在Awake / Start中調用lua中對應的方法;並提供點擊事件的相關處理

c.Manager模塊

1.ResourceManager:加載AssetBundle的相關操作。在pc平臺上默認加載的是Assets\StreamingAssets裏的東西,移動平臺上則是Application.persistentDataPath。那麼如果我們想在pc平臺上像移動平臺一樣讀取外部路徑(使用www),在pc平臺模擬熱更新,那麼就可以在Initialize這個方法修改:m_BaseDownloadingURL = "file:///" + Util.DataPath;

例子1

展示了最小的tolua#環境,以及執行一段lua代碼操作代碼如下:

```

LuaStatelua=newLuaState();lua.Start();stringhello=            @"print('hello tolua#')";lua.DoString(hello, "HelloWorld.cs");lua.CheckTop();lua.Dispose();lua= null;

```

LuaState封裝了對lua 主要數據結構 lua_State 指針的各種堆棧操作。

一般對於客戶端,推薦只創建一個LuaState對象。如果要使用多State需要在Unity中設置全局宏 MULTI_STATE

LuaState.Start 需要在tolua代碼加載到內存後調用。如果使用assetbunblde加載lua文件,調用Start()之前assetbundle必須加載好

LuaState.DoString 執行一段lua代碼,除了例子,比較少用這種方式加載代碼,無法避免代碼重複加載覆蓋等,需調用者自己保證。第二個參數用於調試信息,或者error消息(用於提示出錯代碼所在文件名稱)

LuaState.CheckTop 檢查是否堆棧是否平衡,一般放於update中,c#中任何使用lua堆棧操作,都需要調用者自己平衡堆棧(參考LuaFunction以及LuaTable代碼), 當CheckTop出現警告時其實早已經離開了堆棧操作範圍,這是需自行review代碼。

LuaState.Dispose 釋放LuaState 以及其資源。

注意:此例子無法發佈到手機

例子2

展示了dofile跟require的區別, 代碼如下:

```

LuaStatelua=null;voidStart()    {              lua =newLuaState();                        lua.Start();//如果移動了ToLua目錄,需要自己手動,這裏只是例子就不做配置了stringfullPath= Application.dataPath +"/ToLua/Examples/02_ScriptsFromFile";        lua.AddSearchPath(fullPath);            }voidOnGUI()    {if(GUI.Button(newRect(50,50,120,45),"DoFile"))        {            lua.DoFile("ScriptsFromFile.lua");                                }elseif(GUI.Button(newRect(50,150,120,45),"Require"))        {                        lua.Require("ScriptsFromFile");                    }        lua.Collect();        lua.CheckTop();    }voidOnApplicationQuit()    {        lua.Dispose();        lua =null;    }

```

tolua#DoFile函數,跟lua保持一致行爲,能多次執行一個文件。tolua#加入了新的Require函數,無論c#和lua誰先require一個lua文件, 都能保證加載唯一性

LuaState.AddSearchPath 增加搜索目錄, 這樣DoFile跟Require函數可以只用文件名,無需寫全路徑

LuaState.DoFile 加載一個lua文件, 注意dofile需要擴展名, 可反覆執行, 後面的變量會覆蓋之前的DoFile加載的變量

LuaState.Require 同lua require(modname)操作, 加載指定模塊並且把結果寫入到package.loaded中,如果modname存在, 則直接返回package.loaded[modname]

LuaState.Collect 垃圾回收, 對於被自動gc的LuaFunction, LuaTable, 以及委託減掉的LuaFunction, 延遲刪除的Object之類。等等需要延遲處理的回收, 都在這裏自動執行

注意:雖然有文件加載,但此例子無法發佈到手機, 如果ToLua目錄不在/Assets目錄下, 需要修改代碼中的目錄位置

例子3 LuaFunction

展示瞭如何調用lua的函數, 主要代碼如下:

```

privatestringscript=        @"function luaFunc(num)return num + 1endtest = {}test.luaFunc = luaFunc";LuaFunctionluaFunc=null;LuaStatelua=null;voidStart()    {newLuaResLoader();        lua =newLuaState();        lua.Start();        DelegateFactory.Init();        lua.DoString(script);//Get the function objectluaFunc = lua.GetFunction("test.luaFunc");if(func !=null)        {intnum= luaFunc.Invoke(123456);            Debugger.Log("generic call return: {0}", num);            num = CallFunc();            Debugger.Log("expansion call return: {0}", num);            FuncFunc= luaFunc.ToDelegate>();            num = Func(123456);            Debugger.Log("Delegate call return: {0}", num);                        num = lua.Invoke("test.luaFunc",123456,true);            Debugger.Log("luastate call return: {0}", num);        }                        lua.CheckTop();}voidOnDestroy()    {if(luaFunc !=null)        {            luaFunc.Dispose();            luaFunc =null;        }        lua.Dispose();        lua =null;    }intCallFunc()    {                luaFunc.BeginPCall();                        luaFunc.Push(123456);        luaFunc.PCall();intnum= (int)luaFunc.CheckNumber();                            luaFunc.EndPCall();returnnum;                    }

```

tolua# 簡化了lua函數的操作,通過LuaFunction封裝(並緩存)一個lua函數,並提供各種操作, 建議頻繁調用函數使用無GC方式。

LuaState.GetLuaFunction 獲取並緩存一個lua函數, 此函數支持串式操作, 如"test.luaFunc"代表test表中的luaFunc函數。

LuaState.Invoke 臨時調用一個lua function並返回一個值,這個操作並不緩存lua function,適合頻率非常低的函數調用。

LuaFunction.Call() 不需要返回值的函數調用操作

LuaFunction.Invoke() 有一個返回值的函數調用操作

LuaFunction.BeginPCall() 開始函數調用

LuaFunction.Push() 壓入函數調用需要的參數,通過衆多的重載函數來解決參數轉換gc問題

LuaFunction.PCall() 調用lua函數

LuaFunction.CheckNumber() 提取函數返回值, 並檢查返回值爲lua number類型

LuaFunction.EndPCall() 結束lua函數調用, 清楚函數調用造成的堆棧變化

LuaFunction.Dispose() 釋放LuaFunction, 遞減引用計數,如果引用計數爲0, 則從_R表刪除該函數

注意:無論Call還是PCall只相當於lua中的函數'.'調用。

請注意':'這種語法糖 self:call(...) == self.call(self, ...)

c# 中需要按後面方式調用, 即必須主動傳入第一個參數self

例子4

展示瞭如何訪問lua中變量,table的操作

```

privatestringscript=        @"print('Objs2Spawn is: '..Objs2Spawn)var2read = 42varTable = {1,2,3,4,5}varTable.default = 1varTable.map = {}varTable.map.name = 'map'meta = {name = 'meta'}setmetatable(varTable, meta)function TestFunc(strs)print('get func by variable')end";voidStart(){newLuaResLoader();LuaStatelua=newLuaState();        lua.Start();        lua["Objs2Spawn"] =5;        lua.DoString(script);//通過LuaState訪問Debugger.Log("Read var from lua: {0}", lua["var2read"]);        Debugger.Log("Read table var from lua: {0}", lua["varTable.default"]);//LuaState 拆串式tableLuaFunctionfunc= lua["TestFunc"]asLuaFunction;        func.Call();        func.Dispose();//cache成LuaTable進行訪問LuaTabletable= lua.GetTable("varTable");        Debugger.Log("Read varTable from lua, default: {0} name: {1}", table["default"], table["map.name"]);        table["map.name"] ="new";//table 字符串只能是keyDebugger.Log("Modify varTable name: {0}", table["map.name"]);        table.AddTable("newmap");LuaTabletable1= (LuaTable)table["newmap"];        table1["name"] ="table1";        Debugger.Log("varTable.newmap name: {0}", table1["name"]);        table1.Dispose();        table1 = table.GetMetaTable();if(table1 !=null)        {            Debugger.Log("varTable metatable name: {0}", table1["name"]);        }object[]list= table.ToArray();for(inti=0; i < list.Length; i++)        {            Debugger.Log("varTable[{0}], is {1}", i, list[i]);        }        table.Dispose();                                lua.CheckTop();        lua.Dispose();    }

```

luaState["Objs2Spawn"] LuaState通過重載this操作符,訪問lua _G表中的變量Objs2Spawn

LuaState.GetTable 從lua中獲取一個lua table, 可以串式訪問比如lua.GetTable("varTable.map.name") 等於 varTable->map->name

LuaTable 支持this操作符,但此this不支持串式訪問。比如table["map.name"] "map.name" 只是一個key,不是table->map->name

LuaTable.GetMetaTable() 可以獲取當前table的metatable

LuaTable.ToArray() 獲取數組表中的所有對象存入到object[]表中

LuaTable.AddTable(name) 在當前的table表中添加一個名字爲name的表

LuaTable.GetTable(key) 獲取t[key]值到c#, 類似於 lua_gettable

LuaTable.SetTable(key, value) 等價於t[k] = v的操作, 類似於lua_settable

LuaTable.RawGet(key) 獲取t[key]值到c#, 類似於 lua_rawget

LuaTable.RawSet(key, value) 等價於t[k] = v的操作, 類似於lua_rawset

例子5 協同一

展示瞭如何使用lua協同, lua 代碼如下:

```

functionfib(n)locala, b=0,1whilen>0doa, b=b, a+b                n=n-1endreturnaendfunctionCoFunc()print('Coroutine started')fori=1,10,1doprint(fib(i))                                    coroutine.wait(0.1)endprint("current frameCount:"..Time.frameCount)            coroutine.step()print("yield frameCount:"..Time.frameCount)localwww=UnityEngine.WWW("http://www.baidu.com")            coroutine.www(www)locals=tolua.tolstring(www.bytes)print(s:sub(1,128))print('Coroutine ended')endfunctionTestCortinue()            coroutine.start(CoFunc)endlocalcoDelay=nilfunctionDelay()localc=1whiletruedocoroutine.wait(1)print("Count:"..c)                c=c+1endendfunctionStartDelay()            coDelay=coroutine.start(Delay)endfunctionStopDelay()            coroutine.stop(coDelay)end

```

c#代碼如下:

```

newLuaResLoader();lua= newLuaState();lua.Start();LuaBinder.Bind(lua);DelegateFactory.Init();looper= gameObject.AddComponent();looper.luaState= lua;lua.DoString(luaFile.text, "TestLuaCoroutine.lua");LuaFunctionf=lua.GetFunction("TestCortinue");f.Call();f.Dispose();f= null;

```

必須啓動LuaLooper驅動協同,這裏將一個lua的半雙工協同裝換爲類似unity的全雙工協同

fib函數負責計算一個斐那波契n

coroutine.start 啓動一個lua協同

coroutine.wait 協同中等待一段時間,單位:秒

coroutine.step 協同中等待一幀.

coroutine.www 等待一個WWW完成.

tolua.tolstring 轉換byte數組爲lua字符串緩衝

coroutine.stop 停止一個正在lua將要執行的協同

 

總結

1.對於一個panel,需要添加或修改的文件:

a.添加xxxPanel & xxxCtrl

b.修改define、Game、CtrlManager

詳細的可見:http://blog.csdn.NET/adambieber/article/details/47402805

2.在lua中使用AB包內的資源的兩種方法:

a.panelMgr:CreatePanel('Prompt', this.OnCreate);

b.resMgr:LoadPrefab('prompt', { 'PromptItem' }, this.InitPanel);

其中a是對b的進一步封裝,因此兩者都需要提供AB包名、要訪問的包內資源名字(如果是panel,則默認資源名爲AB包名+"Panel")以及回調方法(參數是AB包中的資源)

3.熱更新的四個步驟:打包、解包、更新和加載。而這四個步驟框架已經給我們封裝好了,基本上就不需要我們去管了,但還是很有必要理解其中的過程。

a.打包:將資源全部打包到StreamingAssets文件夾

打包類:LuaFramework / Editor / Packager

打包lua文件:HandleLuaBundle,對Assets\LuaFramework\Lua 與 Assets\LuaFramework\ToLua\Lua這兩個目錄下的所有lua文件進行打包

打包圖片等資源:HandleExampleBundle

b.解包:在移動端StreamingAssets這個文件夾是隻讀的,但是要做熱跟新的話,就需要寫入文件,因此Application.persistentDataPath這個可讀可寫的路徑纔是數據在移動端的存放路徑,同時也爲了比較MD5的值,就需要將StreamingAssets的東西解包(複製)到Application.persistentDataPath

c.更新:files.txt這個文件記錄了所有的資源文件及其MD5值,每次進入遊戲時都會從服務器下載最新的files.txt,然後對其遍歷比較MD5值,如果值不同或者不存在則下載

d.加載:先加載資源的依賴,再加載資源

那麼,如果我們對外發布了一個版本1.1,然後更改資源,發佈1.2,要做的就是:重新生成apk並上傳,然後將StreamingAssets文件夾下的東西上傳到服務器,具體位置見AppConst.WebUrl;對於用戶來說,如果他安裝的是1.1,那麼就會下載更新,如果他安裝的是1.2,那麼解包之後就得到最新的資源了,無需更新了。

4.整套框架的工作流程:

c#打包好後,啓動遊戲,GameManager會進行一些判斷,如果這是遊戲安裝之後的第一次啓動,那麼就會進行解包操作。如果AppConst.UpdateMode爲false,那麼就不會檢測更新,否則就會進行更新操作。然後進入初始化操作,調用Game.lua中的OnInitOK方法,進入lua邏輯。

lua然後調用指定控制器的Awake方法、PanelManager的CreatePanel方法,調用c#代碼,創建panel,爲其添加LuaBehaviour,調用xxxPanel.lua的方法,獲取控件引用,進行邏輯處理。

 

外部學習鏈接:

1.Unity3d:UI面板管理整合進ToLua

2.Unity3D熱更新LuaFramework入門實戰(1-10)——代碼熱更新

3.ToLua例子腳本解析    

4.Unity5 + kbengine + ULUA(toLua) 一個KBE的Lua熱更新客戶端demo

5.[Unity熱更新]tolua# & LuaFramework(一~十五)

 

發佈了10 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章