1.安裝xLua與快速入門
1.1 下載xLua
在下載頁(yè)面中下載所需資源,其中xlua_vx.x.x.zip和xlua_vx.x.x_luajit.zip是XLua框架的兩個(gè)不同版本,二者互斥,必須二選一。
xlua_vx.x.x.zip - 【必須/二選一】用于Unity的xLua的Lua版本,其中x.x.x為版本號(hào)
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安裝到其他目錄,請(qǐng)參考FAQ。
1.3實(shí)例化與釋放LuaEnv對(duì)象
LuaEnv luavm = new LuaEnv();//實(shí)例化
luavm.Dispose();//釋放
1.4在C#中執(zhí)行Lua代碼
通過(guò)LuaEnv.DoString(string)方法來(lái)在C#中執(zhí)行Lua代碼,代碼的執(zhí)行方式有兩種。
第一種方式是直接通過(guò)參數(shù)傳入Lua代碼文本,但不建議使用這種方式。下面的示例中傳入了一行Lua代碼 print('hello world'),將會(huì)在Unity控制臺(tái)打印hello world。
luavm.DoString("print('hello world')");
第二種方式是通過(guò)參數(shù)傳遞Lua代碼文件名稱(或位置),建議使用這種方式。下面的示例中,將會(huì)查找名為 hello_world.lua 的Lua腳本文件并執(zhí)行該文件中的Lua代碼。
luavm.DoString("require 'lua_script_file'");
建議的腳本加載方式是:整個(gè)程序中只有一處 DoString("require 'main'") ,然后在main.lua中加載其他的Lua腳本(類似于在Lua命令行執(zhí)行 $ lua main.lua )。
指令 require 會(huì)依次調(diào)用不同的加載器去加載Lua文件,當(dāng)某個(gè)加載器成功加載Lua文件后就不再調(diào)用其他加載器,如果所有加載器都沒能加載到參數(shù)中指定地文件,則報(bào)告錯(cuò)誤。xLua對(duì)Lua腳本文件的存放位置有要求,如果要加載自定義位置的、來(lái)自網(wǎng)絡(luò)的、經(jīng)過(guò)壓縮或加密的Lua文件,則需要實(shí)現(xiàn)自定義加載器。下文中會(huì)介紹Lua文件存放位置和自定義加載器的相關(guān)內(nèi)容。
1.5C#與Lua的相互調(diào)用
在C#中,使用 LuaEnv.Global.Get<T>("obj_name") 方法來(lái)獲取名為obj_name的Lua全局對(duì)象,該對(duì)象可以是任意能夠映射到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生成代碼
通過(guò)Unity編輯器窗口的 XLua - Generate Code 選項(xiàng)可以生成用于實(shí)現(xiàn)C#和Lua交互的適配代碼。生成代碼后程序的性能更好,建議使用。如果沒有生成代碼,則xLua會(huì)使用反射進(jìn)行交互,這種方式性能不高,但是能夠減小安裝包大小。
在Unity編輯器中,不生成代碼也能夠正常運(yùn)行程序,建議在開發(fā)階段不要生成代碼。在打包手機(jī)版應(yīng)用和做性能測(cè)試、調(diào)優(yōu)前必須生成代碼
實(shí)例代碼:
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會(huì)在哪些文件夾中查找Lua腳本文件,需要將Lua腳本文件放在這些位置系統(tǒng)才能正確加載它們。除了在Unity安裝文件夾中查找Lua腳本文件外,xLua還會(huì)在項(xiàng)目的下列位置查找Lua腳本文件:
- 內(nèi)置Lua庫(kù)
- 自定義加載器
- Resources文件夾
- 項(xiàng)目根目錄(與Assets文件夾同級(jí),打包后則與可執(zhí)行exe文件同級(jí),下同)
- 項(xiàng)目根目錄中同名文件夾中的init.lua
- 項(xiàng)目根目錄中的同名DLL文件
- Assets/StreamingAssets文件夾
需要注意的地方是,Unity系統(tǒng)在打包應(yīng)用時(shí)無(wú)法識(shí)別擴(kuò)展名為lua的文件,所以當(dāng)需要將Lua腳本文件作為TextAsset打包時(shí)(例如放到Resources文件夾中),應(yīng)該將Lua腳本文件的擴(kuò)展名改為txt,例如 my_script.lua.txt 。
如果需要將Lua腳本文件放置到自定義位置,或者加載網(wǎng)絡(luò)文件、壓縮文件或者加密文件,則需要實(shí)現(xiàn)自定義加載器。其中自定義Lua文件加載位置的功能也可以通過(guò)直接在Lua腳本中向 package.path 中添加路徑名稱來(lái)實(shí)現(xiàn)。例如,添加 /Assets/myluafiles/ 文件夾到加載路徑
luavm.DoString (@"package.path = './Assets/myluafiles/?.lua;' .. package.path");
注意要對(duì)Lua文件名使用半角問號(hào)(?)通配符,多個(gè)路徑之間使用半角分號(hào)(;)分隔,并且文件夾層級(jí)使用斜杠(/)而不是反斜杠(\)表示。
2.1 自定義加載器
實(shí)現(xiàn)自定義加載器只需要?jiǎng)?chuàng)建 CustomLoader 委托實(shí)例并通過(guò) LuaEnv.AddLoader() 方法將其添加到LuaEnv實(shí)例中即可。
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) {
// 通過(guò)自定義filepath的解析方式來(lái)實(shí)現(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\. 從自定義的默認(rèn)位置加載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) ,該方法具有多個(gè)重載,各個(gè)重載的具體區(qū)別請(qǐng)查看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#中定義一個(gè)class或struct,其中含有同名的public字段,并且具有無(wú)參構(gòu)造方法。以class為例:
public class TableClass{ public int f1; public int f2;}
table和class的成員個(gè)數(shù)不必完全相同,在映射過(guò)程中,table中多出的成員會(huì)被忽略,class中多出的成員會(huì)被初始化成默認(rèn)值。在此示例中,忽略了字符串f3和函數(shù)add()。
在使用這種方式時(shí),可以為C#類型添加 [GCOptimize] 特性來(lái)降低生成開銷,具體說(shuō)明請(qǐng)查看xLua配置文檔。
需要注意的是,這一映射過(guò)程是值拷貝過(guò)程,對(duì)class對(duì)象的修改不會(huì)同步到table對(duì)象,反之亦然。
示例代碼:
TableClass table = luavm.Global.Get<TableClass>("my_table");Debug.Log(table.f1 + table.f2);
3.2.2 將table映射到interface【引用形式】【建議用法】
將table映射到interface依賴代碼生成,如果沒有生成代碼會(huì)拋出 InvalidCastException 異常。接口方式實(shí)現(xiàn)的是引用形式的映射,對(duì)class對(duì)象的修改會(huì)同步到table對(duì)象,反之亦然。建議使用該方式進(jìn)行映射。仍然以上一節(jié)中的Lua數(shù)據(jù)結(jié)構(gòu)為例,現(xiàn)在需要定義一個(gè)與其相匹配的C#接口,并為這個(gè)接口添加用于指明需要生成代碼的特性標(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這種更輕量級(jí)的方式。這種方式會(huì)選擇table中能夠匹配上的成員進(jìn)行映射,并且采用了值拷貝形式。
仍然以第一節(jié)中的Lua數(shù)據(jù)結(jié)構(gòu)為例,將其映射到Dictionary<TKey, TValue>和List的示例代碼為:
// 映射到Dictionary<TKey, TValue>// 因類型不匹配,字符串f3和函數(shù)add()會(huì)被忽略Dictionary<string, int> tableDict = luavm.Global.Get<Dictionary<string, int>>("my_table");Debug.Log(tableDict["f1"] + tableDict["f2"]);// 映射到List<T>// 因類型不匹配,字符串f3和函數(shù)add()會(huì)被忽略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類的好處是不需要生成代碼即可實(shí)現(xiàn)引用形式的映射,但其執(zhí)行速度慢(比第2種方式要慢一個(gè)數(shù)量級(jí)),而且沒有類型檢查。
仍然以第一節(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ù)使用,而且類型安全;其缺點(diǎn)是需要生成代碼,如果沒有生成代碼,則會(huì)拋出 InvalidCastException 異常。
在聲明delegate時(shí),其訪問權(quán)限應(yīng)該是 public 的,delegate的每個(gè)普通參數(shù)和使用 ref 修飾的參數(shù)從左到右依次對(duì)應(yīng)目標(biāo)function的參數(shù),out 修飾的參數(shù)不會(huì)被映射到目標(biāo)function的參數(shù)中;delegate的返回值和使用 out 、 ref 修飾的參數(shù)從左到右依次對(duì)應(yīng)function的(多個(gè))返回值。參數(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分別對(duì)應(yīng)Lua function的參數(shù)a、b、c,C# delegate的返回值和輸出參數(shù)sum、pro分別對(duì)應(yīng)Lua function 的3個(gè)返回值。C# delegate和Lua function的參數(shù)名稱不必完全相同,也不限定輸入輸出參數(shù)的順序,只要類型匹配即可。建議綁定一次重復(fù)使用,生成代碼后,通過(guò)C# delegate調(diào)用Lua function不會(huì)產(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實(shí)例時(shí)報(bào)出
InvalidOperationException: try to dispose a LuaEnv with C# callback! ,說(shuō)明代碼中有綁定了Lua function的委托實(shí)例沒有釋放,找到這個(gè)委托實(shí)例并將其釋放即可,具體信息可以查看官方的常見問題解答頁(yè)面。
3.3.2 將function映射到LuaFunction類
將function映射到LuaFunction類比較簡(jiǎn)單,不需要生成代碼,但這種方式性能較差,而且沒有類型檢查。在LuaFunction類中有一個(gè)變參的 Call() 方法,可以傳遞任意類型的參數(shù),這些參數(shù)對(duì)應(yīng)Lua function的參數(shù),這一方法的返回值是一個(gè)object數(shù)組,其中的元素分別對(duì)應(yīng)Lua function的多個(gè)返回值。
仍然以上一節(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三個(gè)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,這是因?yàn)?,在C#中參數(shù)(或字段)類型是object時(shí),默認(rèn)以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全局?jǐn)?shù)據(jù),尤其是訪問table和function時(shí),代價(jià)比較大,建議盡量減少訪問次數(shù)??梢栽诔绦虺跏蓟A段把要調(diào)用的Lua function綁定到C# delegate并緩存下來(lái),以后直接調(diào)用這個(gè)delegate即可,table與之類似。
如果Lua方面的實(shí)現(xiàn)部分都以delegate和interface的方式提供,那么使用方可以完全與xLua解耦 —— 由一個(gè)專門的模塊負(fù)責(zé)xLua的初始化以及delegate和interface的映射,然后把這些delegate和interface實(shí)例設(shè)置到要用到它們的地方。
4. 在Lua中調(diào)用C#
本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/LuaCallCSharp/LuaCallCs.cs中找到使用示例。
在Lua中調(diào)用C#時(shí),首先要注意以下幾點(diǎn):
xLua中所有的C#類都被放到了 CS 模塊中。
Lua語(yǔ)言中沒有new關(guān)鍵字;
Lua語(yǔ)言運(yùn)算符:+ , - , * , / , % , ^ , == , ~= , < , > , <= , >= , and , or , not , .. , #
Lua語(yǔ)言不支持泛型
Lua語(yǔ)言不支持類型轉(zhuǎn)換
標(biāo)識(shí)生成代碼的特性標(biāo)簽:[LuaCallCSharp]
除此之外,在xLua中可以像寫普通的C#代碼那樣調(diào)用C#。
xLua支持以下功能:
創(chuàng)建C#對(duì)象
通過(guò)C#子類訪問C#父類的靜態(tài)屬性和方法
通過(guò)C#子類對(duì)象訪問C#父類的成員屬性和方法
帶有默認(rèn)參數(shù)的C#方法
帶有可變參數(shù)的C#方法
C#方法重載
C#擴(kuò)展方法
C#操作符重載
C#枚舉
自動(dòng)轉(zhuǎn)換C#復(fù)雜類型和Lua table
C#的delegate和event
下面將對(duì)在Lua中訪問C#時(shí)的幾點(diǎn)特殊情況加以說(shuō)明,并在最后給出示例代碼。
4.1 Lua的點(diǎn)語(yǔ)法和冒號(hào)語(yǔ)法
在Lua中,使用點(diǎn)(.)語(yǔ)法調(diào)用對(duì)象的成員方法時(shí)方法的第一個(gè)參數(shù)應(yīng)該傳入對(duì)象自身,而使用冒號(hào)(:)語(yǔ)法調(diào)用對(duì)象的成員方法時(shí)可以省略這一參數(shù)。建議使用冒號(hào)語(yǔ)法。示例代碼:
local gameObject = CS.UnityEngine.GameObject()-- 使用冒號(hào)語(yǔ)法不用傳入對(duì)象自身gameObject:SetActive(false)-- 使用點(diǎn)語(yǔ)法需要傳入對(duì)象自身gameObject.SetActive(gameObject, true)
4.2 C#復(fù)雜類型和Lua table的自動(dòng)轉(zhuǎn)換
在Lua中可以直接使用table來(lái)代替帶有無(wú)參構(gòu)造方法的C#復(fù)雜類型(class和struct)。下面示例中展示了C# Vector3和Lua table的自動(dòng)轉(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的自動(dòng)轉(zhuǎn)換myObj:ComplexStructTest({x=1.0, y=2.0, z=3.0})
4.3 參數(shù)和返回值的處理規(guī)則
參數(shù)處理規(guī)則:C#方法的普通參數(shù)和ref 參數(shù)會(huì)按照從左到右的順序依次映射到Lua function的形參,out 參數(shù)不會(huì)被映射到Lua function的形參。
返回值處理規(guī)則:C#方法的返回值會(huì)映射到Lua function的第一個(gè)返回值,然后C#方法的 out 和 ref 參數(shù)會(huì)按照從左到右的順序依次映射到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() 方法可以將一個(gè)整數(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#中一樣使用 + 和 - 運(yùn)算符向delegate調(diào)用鏈中添加方法,不過(guò)Lua中沒有 += 和 -= 運(yùn)算符。方法的添加順序會(huì)影響調(diào)用順序。需要注意的兩點(diǎn)是:
在C#中聲明delegate時(shí)需要為其添加一個(gè)默認(rèn)的實(shí)現(xiàn),否則在Lua中向其添加方法時(shí)會(huì)拋出異常;
調(diào)用delegate時(shí)應(yīng)該使用點(diǎn)語(yǔ)法,如果使用冒號(hào)語(yǔ)法傳入的參數(shù)會(huì)變成 nil 。
在xLua中為event添加和移除監(jiān)聽的寫法有些不同,不能直接通過(guò)加減運(yùn)算來(lái)實(shí)現(xiàn),而是要使用 EventName('+', func_name) 和 EventName('-', func_name) 這種寫法來(lái)實(shí)現(xiàn)添加和移除監(jiān)聽,并且需要使用冒號(hào)語(yǔ)法。另外,在xLua中不能直接通過(guò) EventName(params) 這種形式來(lái)觸發(fā)事件,而是要在C#代碼中添加一個(gè)間接觸發(fā)方法。
C#示例代碼:
[LuaCallCSharp]public class MyClass{ // 委托,需要有默認(rèn)實(shí)現(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ǎn)語(yǔ)法,否則調(diào)用委托時(shí)參數(shù)會(huì)變成nil-- Lua中沒有+=操作符,方法的添加順序會(huì)影響調(diào)用順序myObj.MyDelegate = myObj.MyDelegate + my_lua_callbackmyObj.MyDelegate('delegate callback')-- event使用冒號(hào)語(yǔ)法,不能直接使用MyEvent來(lái)觸發(fā)事件myObj:MyEvent('+', my_lua_callback)myObj:TriggerEvent('event callback 1')myObj:MyEvent('-', my_lua_callback)myObj:TriggerEvent('event callback 2')
4.6 擴(kuò)展方法
在C#中定義了擴(kuò)展方法后,為該擴(kuò)展方法所在的類添加 [LuaCallCSharp] 特性標(biāo)簽,就可以在Lua中直接使用這個(gè)擴(kuò)展方法,
4.7 泛型方法
xLua不支持泛型方法,但可以使用擴(kuò)展方法為泛型方法添加針對(duì)特定類型的轉(zhuǎn)換方法,實(shí)現(xiàn)一個(gè)假的泛型。例如,下面的示例為GenericTest()方法實(shí)現(xiàn)了針對(duì)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() 方法實(shí)現(xiàn)了類似的功能,該方法讓xLua使用指定的生成代碼去調(diào)用一個(gè)對(duì)象。有些時(shí)候,第三方庫(kù)對(duì)外暴露的接口是一個(gè)interface或者抽象類,其實(shí)現(xiàn)類是隱藏的,這時(shí)就沒辦法對(duì)實(shí)現(xiàn)類進(jìn)行代碼生成,xLua會(huì)通過(guò)反射來(lái)訪問這個(gè)實(shí)現(xiàn)類。如果這種訪問很頻繁,會(huì)很影響性能。這時(shí)就可以把第三方庫(kù)暴露出來(lái)的interface或抽象類添加到生成代碼列表,然后指定用這個(gè)interface或抽象類的生成代碼來(lái)訪問對(duì)象,類似于將對(duì)象轉(zhuǎn)換成了interface或抽象類的類型。例如,下面的Lua示例代碼指定了使用CS.MyInterface的生成代碼來(lái)訪問myObj對(duì)象:
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需要有默認(rèn)值,否則Lua中會(huì)報(bào)錯(cuò)
public Action<string> MyDelegate = (arg) => { };
public event Action<string> MyEvent;
public MyClass() { id = "id_default";
}
public MyClass(string id) { this.id = id; }
// 帶有默認(rèn)參數(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 {
// 擴(kuò)展方法 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); } }}
// 這里請(qǐng)參考第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)用成員方法,注意冒號(hào)語(yǔ)法和點(diǎn)語(yǔ)法的參數(shù)區(qū)別
obj:SetActive(false)
obj.SetActive(obj, true)
-- 用于測(cè)試delegate和event
function my_lua_callback(arg)
print('my_lua_callback: ' .. arg)
end
-- 訪問MyClass類
function lua_call_cs()
local MyClass = CS.MyNamespace.MyClass
-- 實(shí)例化C#對(duì)象,方法重載
local myObj0 = MyClass()
local myObj1 = MyClass('id_1')
-- 操作符重載
local myObj2 = myObj0 + myObj1
print('Operator Overload: ' .. myObj2.id)
-- 默認(rèn)參數(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的自動(dòng)轉(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()方法可以將一個(gè)整數(shù)或字符串轉(zhuǎn)換到枚舉值
myObj0:EnumTest(MyEnum.__CastFrom(1))
myObj0:EnumTest(MyEnum.__CastFrom('C'))
-- delegate使用點(diǎn)語(yǔ)法,否則調(diào)用委托時(shí)參數(shù)會(huì)變成nil
-- Lua中沒有+=操作符,方法的添加順序會(huì)影響調(diào)用順序
myObj0.MyDelegate = myObj0.MyDelegate + my_lua_callback
myObj0.MyDelegate('delegate callback')
-- event使用冒號(hào)語(yǔ)法,不能直接使用MyEvent來(lái)觸發(fā)事件
myObj0:MyEvent('+', my_lua_callback)
myObj0:TriggerEvent('event callback 1')
myObj0:MyEvent('-', my_lua_callback)
myObj0:TriggerEvent('event callback 2')
-- 擴(kuò)展方法
myObj0:MyExtensionMethod('hello')
-- xLua不支持泛型方法,這里是假的泛型方法
myObj0:GenericTestOfString('fake')
-- Lua沒有類型轉(zhuǎn)換功能,但xLua提供了cast方法實(shí)現(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)列表和動(dòng)態(tài)列表。
對(duì)于xLua的配置,有兩個(gè)必須和兩個(gè)建議:
列表方式都必須在靜態(tài)類中進(jìn)行配置
列表方式都必須使用靜態(tài)字段/屬性
建議不要使用特性標(biāo)簽,這種方式在IL2CPP模式下會(huì)增加不少的代碼量
建議將列表方式的配置放到Editor目錄(如果是 Hotfix 配置,而且類位于 Assembly-CSharp.dll 之外的其它dll中,必須放Editor目錄)
5.1 特性標(biāo)簽
xLua通過(guò)白名單來(lái)指明要為哪些類生成代碼,而白名單通過(guò)特性標(biāo)簽(Attribute)來(lái)配置。為類添加 [CSharpCallLua] 或 [LuaCallCSharp] 特性標(biāo)簽后,通過(guò)Unity編輯器菜單欄的 XLua - Generate Code 按鈕即可為該類生成適配代碼。
如果一個(gè)C#類型添加了 [LuaCallCSharp] 特性標(biāo)簽,那么xLua會(huì)生成這個(gè)類的適配代碼,包括構(gòu)造方法、成員屬性和方法、靜態(tài)屬性和方法;如果沒有為類型添加這個(gè)特性標(biāo)簽,那么xLua會(huì)嘗試使用性能較差的反射方式訪問C#類。xLua只會(huì)為添加了該特性標(biāo)簽的類型生成代碼,不會(huì)自動(dòng)為該類型的父類生成代碼,當(dāng)子類對(duì)象訪問父類方法時(shí),如果父類也添加了特性標(biāo)簽,則執(zhí)行父類的適配代碼,否則將嘗試使用反射來(lái)訪問父類。反射方式除了性能不佳外,在IL2CPP模式下還有可能因?yàn)榇a裁剪而導(dǎo)致無(wú)法訪問。建議所有要在Lua中訪問的C#代碼,要么加上 [LuaCallCSharp] 特性標(biāo)簽,要么加上 [ReflectionUse] 特性標(biāo)簽,這樣才能夠保證程序在各平臺(tái)都能正常運(yùn)行。
如果需要把一個(gè)Lua function綁定到C# delegate,或者需要把一個(gè)Lua table映射到C# interface,那么需要為delegate或者interface添加 [CSharpCallLua] 特性標(biāo)簽。
特性標(biāo)簽方便使用,但在IL2CPP模式下會(huì)增加不少的代碼量,不建議使用。
5.2 靜態(tài)列表
有時(shí)候無(wú)法直接給一個(gè)類型添加特性標(biāo)簽,例如系統(tǒng)API、沒有源碼的DLL等,這時(shí)可以在一個(gè)靜態(tài)類中聲明一個(gè)靜態(tài)字段,這一字段只要實(shí)現(xiàn)了 IEnumerable<Type> 并且沒有使用 BlackList 和 AdditionalProperties 特性標(biāo)簽即可,例如 List<Type> ,然后為這個(gè)靜態(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 動(dòng)態(tài)列表
與靜態(tài)列表類似,動(dòng)態(tài)列表需要在一個(gè)靜態(tài)類中聲明一個(gè)靜態(tài)屬性,并為其添加相應(yīng)的特性標(biāo)簽。在靜態(tài)屬性的Getter代碼塊中,可以實(shí)現(xiàn)很多效果,例如按命名空間配置、按程序及配置等。建議將動(dòng)態(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)簽的詳細(xì)介紹請(qǐng)查看xLua配置文檔。
| 特性標(biāo)簽 | 用途簡(jiǎn)述 |
| XLua.LuaCallCSharp | 生成C#類型的適配代碼 |
| XLua.CSharpCallLua | 生成C# delegate或interface的適配代碼 |
| XLua.ReflectionUse | 阻止IL2CPP進(jìn)行代碼裁剪 |
| XLua.DoNotGen | 不生成某個(gè)方法、字段或?qū)傩缘倪m配代碼,通過(guò)反射訪問 |
| XLua.GCOptimize | 優(yōu)化C#純值類型的轉(zhuǎn)換性能 |
| XLua.AdditionalProperties | 通過(guò)屬性訪問私有字段 |
| XLua.BlackList | 不生成某些類成員的適配代碼 |