XLua在Unity中的熱更用法摘要

1.安裝xLua與快速入門

1.1 下載xLua

在下載頁面中下載所需資源希柿,其中xlua_vx.x.x.zip和xlua_vx.x.x_luajit.zip是XLua框架的兩個不同版本酪耳,二者互斥,必須二選一妹孙。
xlua_vx.x.x.zip - 【必須/二選一】用于Unity的xLua的Lua版本,其中x.x.x為版本號
xlua_vx.x.x_luajit.zip - 【必須/二選一】用于Unity的xLua的LuaJit版本惰爬,性能更好
xlua_vx.x.x_example.zip - 【非必須】用法示例
xlua_vx.x.x_tutorial.zip - 【非必須】官方教程的配套代碼
xlua_vx.x.x_general.zip - 【非必須】xLua的通用版本循签,不局限于Unity

1.2 安裝xLua

以xlua_vx.x.x.zip為例,解壓xlua_vx.x.x.zip议薪,將其中的Assets文件夾與希望使用xLua的Unity工程的Assets文件夾合并尤蛮,不要更改Assets文件夾的目錄結(jié)構(gòu)。合并完成后斯议,即可在代碼中使用xLua产捞。
如果要將xLua安裝到其他目錄,請參考FAQ哼御。

1.3實例化與釋放LuaEnv對象

LuaEnv luavm = new LuaEnv();//實例化
luavm.Dispose();//釋放

1.4在C#中執(zhí)行Lua代碼

通過LuaEnv.DoString(string)方法來在C#中執(zhí)行Lua代碼坯临,代碼的執(zhí)行方式有兩種。
第一種方式是直接通過參數(shù)傳入Lua代碼文本恋昼,但不建議使用這種方式看靠。下面的示例中傳入了一行Lua代碼 print('hello world'),將會在Unity控制臺打印hello world液肌。

luavm.DoString("print('hello world')");

第二種方式是通過參數(shù)傳遞Lua代碼文件名稱(或位置)挟炬,建議使用這種方式。下面的示例中,將會查找名為 hello_world.lua 的Lua腳本文件并執(zhí)行該文件中的Lua代碼谤祖。

luavm.DoString("require 'lua_script_file'");

建議的腳本加載方式是:整個程序中只有一處 DoString("require 'main'") 婿滓,然后在main.lua中加載其他的Lua腳本(類似于在Lua命令行執(zhí)行 $ lua main.lua )。
指令 require 會依次調(diào)用不同的加載器去加載Lua文件泊脐,當(dāng)某個加載器成功加載Lua文件后就不再調(diào)用其他加載器空幻,如果所有加載器都沒能加載到參數(shù)中指定地文件,則報告錯誤容客。xLua對Lua腳本文件的存放位置有要求,如果要加載自定義位置的约郁、來自網(wǎng)絡(luò)的缩挑、經(jīng)過壓縮或加密的Lua文件,則需要實現(xiàn)自定義加載器鬓梅。下文中會介紹Lua文件存放位置和自定義加載器的相關(guān)內(nèi)容供置。

1.5C#與Lua的相互調(diào)用

在C#中,使用 LuaEnv.Global.Get<T>("obj_name") 方法來獲取名為obj_name的Lua全局對象绽快,該對象可以是任意能夠映射到Lua的C#類型芥丧;在Lua中,所有C#類都位于CS模塊中坊罢,可以直接使用 CS.命名空間.類名 或 CS.命名空間.類名.字段/屬性/方法 C#的類续担、字段、屬性和方法活孩。例如物遇,在Lua中調(diào)用Unity的Debug.Log()方法打印hello world:luavm.DoString("CS.UnityEngine.Debug.Log('hello world')");。

1.6生成代碼

通過Unity編輯器窗口的 XLua - Generate Code 選項可以生成用于實現(xiàn)C#和Lua交互的適配代碼憾儒。生成代碼后程序的性能更好询兴,建議使用。如果沒有生成代碼起趾,則xLua會使用反射進行交互诗舰,這種方式性能不高,但是能夠減小安裝包大小训裆。
在Unity編輯器中眶根,不生成代碼也能夠正常運行程序呛牲,建議在開發(fā)階段不要生成代碼粱侣。在打包手機版應(yīng)用和做性能測試、調(diào)優(yōu)前必須生成代碼

實例代碼:

public class Example : MonoBehaviour{ 
   private LuaEnv luavm;
    private void Start()    { 
       luavm = new LuaEnv(); 
       // 直接執(zhí)行Lua代碼       
 luavm.DoString("print('hello world')"); 
       // 查找Lua腳本文件并執(zhí)行其中的代碼 
       //luavm.DoString("require 'lua_script_file'");
    }
    private void OnDestroy()    { 
       // 記得要釋放 
       if(luavm != null)  
      {            luavm.Dispose();        } 
   }}

1.7Lua腳本文件的加載位置

從上表可以看到xLua會在哪些文件夾中查找Lua腳本文件平痰,需要將Lua腳本文件放在這些位置系統(tǒng)才能正確加載它們艺骂。除了在Unity安裝文件夾中查找Lua腳本文件外诸老,xLua還會在項目的下列位置查找Lua腳本文件:

  • 內(nèi)置Lua庫
  • 自定義加載器
  • Resources文件夾
  • 項目根目錄(與Assets文件夾同級,打包后則與可執(zhí)行exe文件同級,下同)
  • 項目根目錄中同名文件夾中的init.lua
  • 項目根目錄中的同名DLL文件
  • Assets/StreamingAssets文件夾

需要注意的地方是别伏,Unity系統(tǒng)在打包應(yīng)用時無法識別擴展名為lua的文件蹄衷,所以當(dāng)需要將Lua腳本文件作為TextAsset打包時(例如放到Resources文件夾中),應(yīng)該將Lua腳本文件的擴展名改為txt厘肮,例如 my_script.lua.txt 愧口。

如果需要將Lua腳本文件放置到自定義位置,或者加載網(wǎng)絡(luò)文件类茂、壓縮文件或者加密文件耍属,則需要實現(xiàn)自定義加載器。其中自定義Lua文件加載位置的功能也可以通過直接在Lua腳本中向 package.path 中添加路徑名稱來實現(xiàn)巩检。例如厚骗,添加 /Assets/myluafiles/ 文件夾到加載路徑

luavm.DoString (@"package.path = './Assets/myluafiles/?.lua;' .. package.path");

注意要對Lua文件名使用半角問號(?)通配符,多個路徑之間使用半角分號(;)分隔兢哭,并且文件夾層級使用斜杠(/)而不是反斜杠(\)表示领舰。

2.1 自定義加載器

實現(xiàn)自定義加載器只需要創(chuàng)建 CustomLoader 委托實例并通過 LuaEnv.AddLoader() 方法將其添加到LuaEnv實例中即可。

CustomLoader委托的簽名為:

public delegate byte[] CustomLoader(ref string filepath);

示例代碼:

public class CustomLoaderExample : MonoBehaviour{
    private void Start()    { 
       LuaEnv luavm = new LuaEnv();
        // 添加自定義加載器  
      luavm.AddLoader(MyCustomLoader); 
       string filepath = Application.dataPath + "/myluafiles/test.lua";  
      luavm.DoString(string.Format("require '{0}'", filepath)); 
       luavm.Dispose();    }  
  // 自定義的Lua文件加載器迟螺。
    // 參數(shù)filepath:【require 'filepath'】中的【filepath】    // 返回值:文件內(nèi)容   
 private byte[] MyCustomLoader(ref string filepath)    {  
      // 通過自定義filepath的解析方式來實現(xiàn)特殊加載功能 
       // 1\. 從指定的路徑加載Lua文件  
      if (filepath.Contains("/"))        {
            if (File.Exists(filepath)) 
           {                return File.ReadAllBytes(filepath);  
              //string script = File.ReadAllText(filepath);  
              //return System.Text.Encoding.UTF8.GetBytes(script); 
           }        
}        // 2\. 從自定義的默認位置加載Lua文件     
   else        {        
    string defaultFolder = Application.dataPath + "/myluafiles/";  
          string file = defaultFolder + filepath + ".lua"; 
           if (File.Exists(file))     
      {                return File.ReadAllBytes(file);            }  
      }       
 // 其他加載方式:        // 3\. 加載網(wǎng)絡(luò)文件        // 4\. 加載壓縮文件并解壓        // 5\. 加載加密文件并解密       
 return null;    }}

3. 在C#中訪問Lua數(shù)據(jù)結(jié)構(gòu)

本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/CSharpCallLua/CSCallLua.cs中找到使用示例冲秽。

在C#中訪問Lua數(shù)據(jù)結(jié)構(gòu)的主要方法是 LuaEnv.Global.Get<T>(string name) ,該方法具有多個重載矩父,各個重載的具體區(qū)別請查看xLua API文檔锉桑。

3.1 訪問全局的基本數(shù)據(jù)類型

luavm.Global.Get<int>("a");  // 訪問名為a的整型變量luavm.Global.Get<bool>("b");  // 訪問名為b的布爾變量luavm.Global.Get<string>("c");  // 訪問名為c的字符串變量

3.2 訪問全局的table

3.2.1將table映射到class或struct【值拷貝】

假設(shè)現(xiàn)在有如下的Lua數(shù)據(jù)結(jié)構(gòu):

my_table = {    f1 = 1,    f2 = 2,    f3 = 'string',    add = function(self, a, b)        return a + b    end}

要將上面的Lua數(shù)據(jù)結(jié)構(gòu)映射到C#,需要在C#中定義一個class或struct浙垫,其中含有同名的public字段刨仑,并且具有無參構(gòu)造方法。以class為例:

public class TableClass{    public int f1;    public int f2;}

table和class的成員個數(shù)不必完全相同夹姥,在映射過程中杉武,table中多出的成員會被忽略,class中多出的成員會被初始化成默認值辙售。在此示例中轻抱,忽略了字符串f3和函數(shù)add()。

在使用這種方式時旦部,可以為C#類型添加 [GCOptimize] 特性來降低生成開銷祈搜,具體說明請查看xLua配置文檔。

需要注意的是士八,這一映射過程是值拷貝過程容燕,對class對象的修改不會同步到table對象,反之亦然婚度。

示例代碼:

TableClass table = luavm.Global.Get<TableClass>("my_table");Debug.Log(table.f1 + table.f2);

3.2.2 將table映射到interface【引用形式】【建議用法】

將table映射到interface依賴代碼生成蘸秘,如果沒有生成代碼會拋出 InvalidCastException 異常。接口方式實現(xiàn)的是引用形式的映射,對class對象的修改會同步到table對象醋虏,反之亦然寻咒。建議使用該方式進行映射。仍然以上一節(jié)中的Lua數(shù)據(jù)結(jié)構(gòu)為例颈嚼,現(xiàn)在需要定義一個與其相匹配的C#接口毛秘,并為這個接口添加用于指明需要生成代碼的特性標(biāo)簽 [CSharpCallLua] :

[CSharpCallLua]public interface ITable{    int f1 { get; set; }    int f2 { get; set; }    int add(int a, int b);}

示例代碼:

ITable table = luavm.Global.Get<ITable>("my_table");Debug.Log(table.add(table.f1, table.f2));

3.2.3 將table映射到Dictionary<TKey, TValue>和List【值拷貝】

如果不想定義class/struct或interface,可以選擇將table映射到Dictionary<TKey, TValue>或List這種更輕量級的方式阻课。這種方式會選擇table中能夠匹配上的成員進行映射叫挟,并且采用了值拷貝形式。

仍然以第一節(jié)中的Lua數(shù)據(jù)結(jié)構(gòu)為例限煞,將其映射到Dictionary<TKey, TValue>和List的示例代碼為:

// 映射到Dictionary<TKey, TValue>// 因類型不匹配霞揉,字符串f3和函數(shù)add()會被忽略Dictionary<string, int> tableDict = luavm.Global.Get<Dictionary<string, int>>("my_table");Debug.Log(tableDict["f1"] + tableDict["f2"]);// 映射到List<T>// 因類型不匹配,字符串f3和函數(shù)add()會被忽略List<int> tableList = luavm.Global.Get<List<double>>("my_table");for(int i = 0; i < tableList.Count; i++){    Debug.Log(tableList[i]);}

3.2.4 將table映射到LuaTable類【引用形式】

將table映射到LuaTable類的好處是不需要生成代碼即可實現(xiàn)引用形式的映射晰骑,但其執(zhí)行速度慢(比第2種方式要慢一個數(shù)量級),而且沒有類型檢查绊序。

仍然以第一節(jié)中的Lua數(shù)據(jù)結(jié)構(gòu)為例硕舆,將其映射到LuaTable的示例代碼為:

LuaTable luaTable = luavm.Global.Get<LuaTable>("my_table");Debug.Log(luaTable.Get<int>("f1") + luaTable.Get<int>("f2") + luaTable.Get<string>("f3"));

3.3 訪問全局的function

3.3.1 將function映射到delegate【建議用法】

將function映射到delegate是官方建議使用的方式。這種方式的好處是性能好骤公,綁定一次即可重復(fù)使用抚官,而且類型安全;其缺點是需要生成代碼阶捆,如果沒有生成代碼凌节,則會拋出 InvalidCastException 異常。

在聲明delegate時洒试,其訪問權(quán)限應(yīng)該是 public 的倍奢,delegate的每個普通參數(shù)和使用 ref 修飾的參數(shù)從左到右依次對應(yīng)目標(biāo)function的參數(shù),out 修飾的參數(shù)不會被映射到目標(biāo)function的參數(shù)中垒棋;delegate的返回值和使用 out 卒煞、 ref 修飾的參數(shù)從左到右依次對應(yīng)function的(多個)返回值。參數(shù)和返回值支持各種基礎(chǔ)類型和復(fù)雜類型叼架。

假設(shè)現(xiàn)在有如下的Lua function:

function luafunc(a, b, c, d)    v3 = {x = a, y = b, z = c}    sum = a + b + c + d    pro = a * b * c * d    return v3, sum, proend

則可以將其映射到下面的C# delegate中畔裕。在下面的示例代碼中,C# delegate的輸入?yún)?shù)a乖订、b扮饶、c分別對應(yīng)Lua function的參數(shù)a、b乍构、c甜无,C# delegate的返回值和輸出參數(shù)sum、pro分別對應(yīng)Lua function 的3個返回值。C# delegate和Lua function的參數(shù)名稱不必完全相同毫蚓,也不限定輸入輸出參數(shù)的順序占键,只要類型匹配即可。建議綁定一次重復(fù)使用元潘,生成代碼后畔乙,通過C# delegate調(diào)用Lua function不會產(chǎn)生gc alloc。

// 聲明委托翩概,輸出參數(shù)不是必須排在最后

[CSharpCallLua]public delegate Vector3 LuaFuncDelegate(int a, int b, int c, out int sum, ref int pro);// 綁定LuaFuncDelegate luaFunc = luavm.Global.Get<LuaFuncDelegate>("luafunc");// 調(diào)用Vector3 v3;int sum, pro = 4;v3 = luaFunc(1, 2, 3, out sum, ref pro);Debug.Log(v3 + " " + sum + " " + pro);

如果在釋放LuaEnv實例時報出

InvalidOperationException: try to dispose a LuaEnv with C# callback! 牲距,說明代碼中有綁定了Lua function的委托實例沒有釋放,找到這個委托實例并將其釋放即可钥庇,具體信息可以查看官方的常見問題解答頁面牍鞠。

3.3.2 將function映射到LuaFunction類

將function映射到LuaFunction類比較簡單,不需要生成代碼评姨,但這種方式性能較差难述,而且沒有類型檢查。在LuaFunction類中有一個變參的 Call() 方法吐句,可以傳遞任意類型的參數(shù)胁后,這些參數(shù)對應(yīng)Lua function的參數(shù),這一方法的返回值是一個object數(shù)組嗦枢,其中的元素分別對應(yīng)Lua function的多個返回值攀芯。

仍然以上一節(jié)中給出的Lua function為例,相應(yīng)的C#部分代碼是:

// 映射LuaFunction luaFunc = luavm.Global.Get<LuaFunction>("luafunc");// 調(diào)用object[] results = luaFunc.Call(1, 2, 3, 4);// 取值// 注意文虏,Lua function中的v3在這里變成了LuaTable侣诺,其中含有x、y氧秘、z三個keyLuaTable table = results[0] as LuaTable;Vector3 v3 = new Vector3(table.Get<int>("x"), table.Get<int>("y"), table.Get<int>("z"));long sum = (long)results[1];long pro = (long)results[2];Debug.Log(v3 + " " + sum + " " + pro);

在上面的示例代碼中年鸳,sum和pro的類型由int變成了long,這是因為敏储,在C#中參數(shù)(或字段)類型是object時阻星,默認以long類型傳遞整數(shù)。如果要指明整數(shù)的類型已添,比如int妥箕,可以在Lua中使用XLua提供的 CS.XLua.Cast.Int32() 方法,例如:

function luafunc(a, b, c)    v3 = {x = a, y = b, z = c}    sum = CS.XLua.Cast.Int32(a + b + c + d)    pro = CS.XLua.Cast.Int32(a * b * c * d)    return v3, sum, proend

3.4 使用建議

在C#中訪問Lua全局數(shù)據(jù)更舞,尤其是訪問table和function時畦幢,代價比較大,建議盡量減少訪問次數(shù)缆蝉∮畲校可以在程序初始化階段把要調(diào)用的Lua function綁定到C# delegate并緩存下來瘦真,以后直接調(diào)用這個delegate即可,table與之類似黍瞧。

如果Lua方面的實現(xiàn)部分都以delegate和interface的方式提供诸尽,那么使用方可以完全與xLua解耦 —— 由一個專門的模塊負責(zé)xLua的初始化以及delegate和interface的映射,然后把這些delegate和interface實例設(shè)置到要用到它們的地方印颤。

4. 在Lua中調(diào)用C#

本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/LuaCallCSharp/LuaCallCs.cs中找到使用示例您机。

在Lua中調(diào)用C#時,首先要注意以下幾點:

xLua中所有的C#類都被放到了 CS 模塊中年局。

Lua語言中沒有new關(guān)鍵字际看;

Lua語言運算符:+ , - , * , / , % , ^ , == , ~= , < , > , <= , >= , and , or , not , .. , #

Lua語言不支持泛型

Lua語言不支持類型轉(zhuǎn)換

標(biāo)識生成代碼的特性標(biāo)簽:[LuaCallCSharp]

除此之外,在xLua中可以像寫普通的C#代碼那樣調(diào)用C#矢否。

xLua支持以下功能:

創(chuàng)建C#對象

通過C#子類訪問C#父類的靜態(tài)屬性和方法

通過C#子類對象訪問C#父類的成員屬性和方法

帶有默認參數(shù)的C#方法

帶有可變參數(shù)的C#方法

C#方法重載

C#擴展方法

C#操作符重載

C#枚舉

自動轉(zhuǎn)換C#復(fù)雜類型和Lua table

C#的delegate和event

下面將對在Lua中訪問C#時的幾點特殊情況加以說明仲闽,并在最后給出示例代碼。

4.1 Lua的點語法和冒號語法

在Lua中僵朗,使用點(.)語法調(diào)用對象的成員方法時方法的第一個參數(shù)應(yīng)該傳入對象自身赖欣,而使用冒號(:)語法調(diào)用對象的成員方法時可以省略這一參數(shù)。建議使用冒號語法验庙。示例代碼:

local gameObject = CS.UnityEngine.GameObject()-- 使用冒號語法不用傳入對象自身gameObject:SetActive(false)-- 使用點語法需要傳入對象自身gameObject.SetActive(gameObject, true)

4.2 C#復(fù)雜類型和Lua table的自動轉(zhuǎn)換

在Lua中可以直接使用table來代替帶有無參構(gòu)造方法的C#復(fù)雜類型(class和struct)畏鼓。下面示例中展示了C# Vector3和Lua table的自動轉(zhuǎn)換:

C#代碼:

[LuaCallCSharp]public class MyClass{    public void ComplexStructTest(Vector3 v3)    {        Debug.Log("ComplexStructTest: " + v3);    }}

Lua代碼:

local myObj = CS.MyClass()-- C# Vector3與Lua table的自動轉(zhuǎn)換myObj:ComplexStructTest({x=1.0, y=2.0, z=3.0})

4.3 參數(shù)和返回值的處理規(guī)則

參數(shù)處理規(guī)則:C#方法的普通參數(shù)和ref 參數(shù)會按照從左到右的順序依次映射到Lua function的形參,out 參數(shù)不會被映射到Lua function的形參壶谒。

返回值處理規(guī)則:C#方法的返回值會映射到Lua function的第一個返回值,然后C#方法的 out 和 ref 參數(shù)會按照從左到右的順序依次映射到Lua function的其他返回值膳沽。

C#示例代碼:

[LuaCallCSharp]public class MyClass{    public int RefOutTest(int a, out int b, ref int c)    {        b = 32;        return a + b + c;    }}

Lua示例代碼:

local myObj = CS.MyClass()

-- ret汗菜、1、2分別映射到C#方法的返回值挑社、參數(shù)a陨界、參數(shù)c

local ret = myObj:RefOutTest(1, 2)

4.4 枚舉類型

在xLua中可以像使用C#類的靜態(tài)屬性一樣使用枚舉成員。枚舉的 __CastFrom() 方法可以將一個整數(shù)或字符串轉(zhuǎn)換到枚舉值痛阻。示例代碼:

C#代碼:

[LuaCallCSharp]public enum MyEnum{    A, B, C}

Lua代碼:

-- 訪問枚舉成員CS.MyEnum.A-- 將整數(shù)轉(zhuǎn)換到枚舉值CS.MyEnum.__CastFrom(1)-- 將字符串轉(zhuǎn)換到枚舉值CS.MyEnum.__CastFrom('C')

4.5 delegate和event

在xLua中可以像在C#中一樣使用 + 和 - 運算符向delegate調(diào)用鏈中添加方法菌瘪,不過Lua中沒有 += 和 -= 運算符。方法的添加順序會影響調(diào)用順序阱当。需要注意的兩點是:

在C#中聲明delegate時需要為其添加一個默認的實現(xiàn)俏扩,否則在Lua中向其添加方法時會拋出異常;

調(diào)用delegate時應(yīng)該使用點語法弊添,如果使用冒號語法傳入的參數(shù)會變成 nil 录淡。

在xLua中為event添加和移除監(jiān)聽的寫法有些不同,不能直接通過加減運算來實現(xiàn)油坝,而是要使用 EventName('+', func_name) 和 EventName('-', func_name) 這種寫法來實現(xiàn)添加和移除監(jiān)聽嫉戚,并且需要使用冒號語法刨裆。另外,在xLua中不能直接通過 EventName(params) 這種形式來觸發(fā)事件彬檀,而是要在C#代碼中添加一個間接觸發(fā)方法帆啃。

C#示例代碼:

[LuaCallCSharp]public class MyClass{    // 委托,需要有默認實現(xiàn)    public Action<string> MyDelegate = (arg) => { };    // 事件    public event Action<string> MyEvent;    // 在Lua中調(diào)用此方法間接觸發(fā)事件    public void TriggerEvent(string arg)    {        if(MyEvent != null) MyEvent(arg);    }}

Lua示例代碼:

local function my_lua_callback(arg)    print('my_lua_callback: ' .. arg)endlocal myObj = CS.MyClass()-- delegate使用點語法窍帝,否則調(diào)用委托時參數(shù)會變成nil-- Lua中沒有+=操作符努潘,方法的添加順序會影響調(diào)用順序myObj.MyDelegate = myObj.MyDelegate + my_lua_callbackmyObj.MyDelegate('delegate callback')-- event使用冒號語法,不能直接使用MyEvent來觸發(fā)事件myObj:MyEvent('+', my_lua_callback)myObj:TriggerEvent('event callback 1')myObj:MyEvent('-', my_lua_callback)myObj:TriggerEvent('event callback 2')

4.6 擴展方法

在C#中定義了擴展方法后盯桦,為該擴展方法所在的類添加 [LuaCallCSharp] 特性標(biāo)簽慈俯,就可以在Lua中直接使用這個擴展方法,

4.7 泛型方法

xLua不支持泛型方法拥峦,但可以使用擴展方法為泛型方法添加針對特定類型的轉(zhuǎn)換方法贴膘,實現(xiàn)一個假的泛型。例如略号,下面的示例為GenericTest()方法實現(xiàn)了針對string類型的轉(zhuǎn)換方法:

C#代碼:

[LuaCallCSharp]public class MyClass{   
 public void GenericTest<T>(T t)    {   
     Debug.Log("GenericTest: " + typeof(T) + "-" + t.ToString());
    }}[LuaCallCSharp]public static class MyExtensions{  
  public static void GenericTestOfString(this MyClass obj, string arg)  
  {        obj.GenericTest<string>(arg);    }}

Lua代碼:

local myObj = CS.MyClass()myObj:GenericTestOfString('fake')

4.8 類型轉(zhuǎn)換

Lua沒有類型轉(zhuǎn)換功能刑峡,但xLua提供了 cast() 方法實現(xiàn)了類似的功能,該方法讓xLua使用指定的生成代碼去調(diào)用一個對象玄柠。有些時候突梦,第三方庫對外暴露的接口是一個interface或者抽象類,其實現(xiàn)類是隱藏的羽利,這時就沒辦法對實現(xiàn)類進行代碼生成宫患,xLua會通過反射來訪問這個實現(xiàn)類。如果這種訪問很頻繁这弧,會很影響性能娃闲。這時就可以把第三方庫暴露出來的interface或抽象類添加到生成代碼列表,然后指定用這個interface或抽象類的生成代碼來訪問對象匾浪,類似于將對象轉(zhuǎn)換成了interface或抽象類的類型皇帮。例如,下面的Lua示例代碼指定了使用CS.MyInterface的生成代碼來訪問myObj對象:

cast(myObj, typeof(CS.MyInterface))

4.9 完整示例代碼

建議在Lua種使用局部變量緩存需要經(jīng)常訪問的類蛋辈,這樣不僅能夠提高開發(fā)效率属拾,還能提高性能。例如:

local GameObject = CS.UnityEngine.GameObjectGameObject.Find('obj_name')

C#示例代碼:

namespace MyNamespace{    
[LuaCallCSharp] 
   public class MyClass 
   {        
public string id;  
      // delegate需要有默認值冷溶,否則Lua中會報錯 
       public Action<string> MyDelegate = (arg) => { }; 
       public event Action<string> MyEvent; 
       public MyClass() { id = "id_default"; 
}       
 public MyClass(string id) { this.id = id; }  
      // 帶有默認參數(shù)的方法  
      public void DefaultParamsTest(int a, int b = 1)     
   {            Debug.Log("DefaultParamsTest: " + (a + b));        } 
       // 帶有可變參數(shù)的方法    
    public void VariableParamsTest(int a, params int[] args)  
      {            int sum = a;       
     foreach (var arg in args) sum += arg;   
         Debug.Log("VariableParamsTest: " + sum);   
     }      
  // 帶有ref渐白、out參數(shù)的方法      
  public int RefOutTest(int a, out int b, ref int c)        {            b = 32;            return a + b + c;        }     
   // 帶有復(fù)雜類型(非基本類型)參數(shù)的方法      
  public void ComplexStructTest(Vector3 v3)        {            Debug.Log("ComplexStructTest: " + v3);        }  
      // 枚舉     
   public void EnumTest(MyEnum e)        {            Debug.Log("EnumTest: " + e.ToString());        }  
      // 觸發(fā)事件,不能再Lua中直接使用MyEvent觸發(fā)事件逞频,添加一層轉(zhuǎn)接      
  public void TriggerEvent(string arg)        {            if (MyEvent != null)            {                MyEvent(arg);            }        } 
       // 操作符重載      
  public static MyClass operator +(MyClass a, MyClass b)        {            MyClass sum = new MyClass(a.id + "&" + b.id);            return sum;        }        // 泛型方法        public void GenericTest<T>(T t)        {            Debug.Log("GenericTest: " + typeof(T) + "-" + t.ToString());        }    }    [LuaCallCSharp]    
public enum MyEnum    {        A, B, C    }    [LuaCallCSharp]    public static class MyExtensions    {      
  // 擴展方法        public static void MyExtensionMethod(this MyClass obj, string msg)  
      {            Debug.Log("MyExtensionMethod - " + msg);        }       
 // xLua不支持泛型方法假裝支持string泛型       
 public static void GenericTestOfString(this MyClass obj, string arg)       
 {            obj.GenericTest<string>(arg);        }    }}
// 這里請參考第5.2節(jié)(靜態(tài)列表)
[LuaCallCSharp]
public static class CsLuaCaster{    // 靜態(tài)列表    public static List<Type> LuaCallCsCastList = new List<Type>()   
 {        typeof(Action),        typeof(Action<string>)    };}

Lua示例代碼:

-- 緩存經(jīng)常訪問的類
local Time = CS.UnityEngine.Time
-- 讀靜態(tài)屬性
Time.deltaTime
-- 寫靜態(tài)屬性
Time.timeScale = 0.5
-- 調(diào)用靜態(tài)方法
local obj = CS.UnityEngine.GameObject.Find('obj_name')
-- 讀(父類)成員屬性
obj.name
-- 寫(父類)成員屬性
obj.name = 'new_name'
-- 調(diào)用成員方法礼预,注意冒號語法和點語法的參數(shù)區(qū)別
obj:SetActive(false)
obj.SetActive(obj, true)
-- 用于測試delegate和event
function my_lua_callback(arg)
    print('my_lua_callback: ' .. arg)
end
-- 訪問MyClass類
function lua_call_cs()
    local MyClass = CS.MyNamespace.MyClass
    -- 實例化C#對象,方法重載
    local myObj0 = MyClass()
    local myObj1 = MyClass('id_1')
    -- 操作符重載
    local myObj2 = myObj0 + myObj1
    print('Operator Overload: ' .. myObj2.id)
    -- 默認參數(shù)
    myObj0:DefaultParamsTest(1)
    -- 可變參數(shù)
    myObj0:VariableParamsTest(1, 2, 3)
    -- ref虏劲、out參數(shù)
    local ret = myObj0:RefOutTest(1, 2)
    print('RefOutTest: ' .. ret)
    
    -- C#復(fù)雜類型與Lua table的自動轉(zhuǎn)換
    myObj0:ComplexStructTest({x=1.0, y=2.0, z=3.0})
    
    -- 枚舉托酸,像使用靜態(tài)屬性一樣使用枚舉
    local MyEnum = CS.MyNamespace.MyEnum
    myObj0:EnumTest(MyEnum.A)
    -- 枚舉的__CastFrom()方法可以將一個整數(shù)或字符串轉(zhuǎn)換到枚舉值
    myObj0:EnumTest(MyEnum.__CastFrom(1))
    myObj0:EnumTest(MyEnum.__CastFrom('C'))

    -- delegate使用點語法褒颈,否則調(diào)用委托時參數(shù)會變成nil
    -- Lua中沒有+=操作符,方法的添加順序會影響調(diào)用順序
    myObj0.MyDelegate = myObj0.MyDelegate + my_lua_callback
    myObj0.MyDelegate('delegate callback')
    -- event使用冒號語法励堡,不能直接使用MyEvent來觸發(fā)事件
    myObj0:MyEvent('+', my_lua_callback)
    myObj0:TriggerEvent('event callback 1')
    myObj0:MyEvent('-', my_lua_callback)
    myObj0:TriggerEvent('event callback 2')
    -- 擴展方法
    myObj0:MyExtensionMethod('hello')
    -- xLua不支持泛型方法谷丸,這里是假的泛型方法
    myObj0:GenericTestOfString('fake')
    -- Lua沒有類型轉(zhuǎn)換功能,但xLua提供了cast方法實現(xiàn)了類似的功能
    -- 指定使用MyClass類的生成代碼訪問myObj0应结,類似于把myObj0轉(zhuǎn)換成MyInterface類型
    -- cast(myObj0, typeof(CS.MyNamespace.MyInterface))
end
lua_call_cs()

5. 代碼生成配置

xLua的所有配置都支持3種方式:特性標(biāo)簽刨疼、靜態(tài)列表和動態(tài)列表。

對于xLua的配置鹅龄,有兩個必須和兩個建議:

列表方式都必須在靜態(tài)類中進行配置

列表方式都必須使用靜態(tài)字段/屬性

建議不要使用特性標(biāo)簽揩慕,這種方式在IL2CPP模式下會增加不少的代碼量

建議將列表方式的配置放到Editor目錄(如果是 Hotfix 配置,而且類位于 Assembly-CSharp.dll 之外的其它dll中扮休,必須放Editor目錄)

5.1 特性標(biāo)簽

xLua通過白名單來指明要為哪些類生成代碼迎卤,而白名單通過特性標(biāo)簽(Attribute)來配置。為類添加 [CSharpCallLua] 或 [LuaCallCSharp] 特性標(biāo)簽后玷坠,通過Unity編輯器菜單欄的 XLua - Generate Code 按鈕即可為該類生成適配代碼蜗搔。

如果一個C#類型添加了 [LuaCallCSharp] 特性標(biāo)簽,那么xLua會生成這個類的適配代碼八堡,包括構(gòu)造方法樟凄、成員屬性和方法、靜態(tài)屬性和方法兄渺;如果沒有為類型添加這個特性標(biāo)簽缝龄,那么xLua會嘗試使用性能較差的反射方式訪問C#類。xLua只會為添加了該特性標(biāo)簽的類型生成代碼挂谍,不會自動為該類型的父類生成代碼二拐,當(dāng)子類對象訪問父類方法時,如果父類也添加了特性標(biāo)簽凳兵,則執(zhí)行父類的適配代碼,否則將嘗試使用反射來訪問父類企软。反射方式除了性能不佳外庐扫,在IL2CPP模式下還有可能因為代碼裁剪而導(dǎo)致無法訪問。建議所有要在Lua中訪問的C#代碼仗哨,要么加上 [LuaCallCSharp] 特性標(biāo)簽形庭,要么加上 [ReflectionUse] 特性標(biāo)簽,這樣才能夠保證程序在各平臺都能正常運行厌漂。

如果需要把一個Lua function綁定到C# delegate萨醒,或者需要把一個Lua table映射到C# interface,那么需要為delegate或者interface添加 [CSharpCallLua] 特性標(biāo)簽苇倡。

特性標(biāo)簽方便使用富纸,但在IL2CPP模式下會增加不少的代碼量囤踩,不建議使用。

5.2 靜態(tài)列表

有時候無法直接給一個類型添加特性標(biāo)簽晓褪,例如系統(tǒng)API堵漱、沒有源碼的DLL等,這時可以在一個靜態(tài)類中聲明一個靜態(tài)字段涣仿,這一字段只要實現(xiàn)了 IEnumerable<Type> 并且沒有使用 BlackList 和 AdditionalProperties 特性標(biāo)簽即可勤庐,例如 List<Type> ,然后為這個靜態(tài)類或靜態(tài)字段添加 [LuaCallCSharp] 特性標(biāo)簽即可好港。建議將靜態(tài)列表放到Editor目錄中愉镰。示例代碼:

[LuaCallCSharp]
public static class StaticListClass
{
    // 靜態(tài)列表
    public static List<Type> LuaCallCsStaticList = new List<Type>()
    {
        typeof(GameObject),
        typeof(Action<string>),
        typeof(Dictionary<string, GameObject>),
    };
}

5.3 動態(tài)列表

與靜態(tài)列表類似,動態(tài)列表需要在一個靜態(tài)類中聲明一個靜態(tài)屬性钧汹,并為其添加相應(yīng)的特性標(biāo)簽丈探。在靜態(tài)屬性的Getter代碼塊中,可以實現(xiàn)很多效果崭孤,例如按命名空間配置类嗤、按程序及配置等。建議將動態(tài)列表放到Editor目錄中辨宠。示例代碼:

public static class DynamicListClass
{
    [Hotfix]
    public static List<Type> LuaCallCsDynamicList
    {
        get
        {
            return (
                from type in Assembly.Load("Assembly-CSharp").GetTypes()
                where type.Namespace == "Xxx"
                select type
            ).ToList();
        }
    }
}

5.4 xLua特性標(biāo)簽列表

xLua特性標(biāo)簽的詳細介紹請查看xLua配置文檔遗锣。

| 特性標(biāo)簽 | 用途簡述 |
| XLua.LuaCallCSharp | 生成C#類型的適配代碼 |
| XLua.CSharpCallLua | 生成C# delegate或interface的適配代碼 |
| XLua.ReflectionUse | 阻止IL2CPP進行代碼裁剪 |
| XLua.DoNotGen | 不生成某個方法、字段或?qū)傩缘倪m配代碼嗤形,通過反射訪問 |
| XLua.GCOptimize | 優(yōu)化C#純值類型的轉(zhuǎn)換性能 |
| XLua.AdditionalProperties | 通過屬性訪問私有字段 |
| XLua.BlackList | 不生成某些類成員的適配代碼 |

個人學(xué)習(xí)博客感謝關(guān)注

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末精偿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赋兵,更是在濱河造成了極大的恐慌笔咽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霹期,死亡現(xiàn)場離奇詭異叶组,居然都是意外死亡,警方通過查閱死者的電腦和手機历造,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門甩十,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吭产,你說我怎么就攤上這事侣监。” “怎么了臣淤?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵橄霉,是天一觀的道長。 經(jīng)常有香客問我邑蒋,道長姓蜂,這世上最難降的妖魔是什么按厘? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮覆糟,結(jié)果婚禮上刻剥,老公的妹妹穿的比我還像新娘。我一直安慰自己滩字,他們只是感情好造虏,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著麦箍,像睡著了一般漓藕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挟裂,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天享钞,我揣著相機與錄音,去河邊找鬼诀蓉。 笑死栗竖,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的渠啤。 我是一名探鬼主播狐肢,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沥曹!你這毒婦竟也來了份名?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤妓美,失蹤者是張志新(化名)和其女友劉穎僵腺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壶栋,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡辰如,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贵试。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琉兜。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锡移,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漆际,我是刑警寧澤淆珊,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站奸汇,受9級特大地震影響施符,放射性物質(zhì)發(fā)生泄漏往声。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一戳吝、第九天 我趴在偏房一處隱蔽的房頂上張望浩销。 院中可真熱鬧,春花似錦听哭、人聲如沸慢洋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽普筹。三九已至,卻和暖如春隘马,著一層夾襖步出監(jiān)牢的瞬間太防,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工酸员, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蜒车,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓幔嗦,卻偏偏與公主長得像酿愧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崭添,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容