一.熱更新方案簡介
在Unity遊戲工程中,C#程式碼(編譯型語言)資源和Resources資料夾下的資源打包後都不可以更改,因此這部分內容不能進行熱更新,而lua程式碼(解釋型語言)邏輯不需要進行預編譯再執行,可以在遊戲執行過程中進行修改,AB包資源也可以在遊戲執行過程中下載解壓縮並使用其中的資源。因此客戶端可以在啟動時檢驗伺服器端的AB包資源是否有更新,如果有更新先下載更新,將lua程式碼資源和其他更新資源打包為AB包放在伺服器端,客戶端下載後直接在執行過程中解壓縮並使用更新資源,實現了客戶端不中斷執行即完成更新的目的,也就是熱更新。
二.xlua熱更新方案簡介
xlua框架提供了C#和lua相互呼叫的功能及Hotfix熱補丁的功能,主要目的是方便我們將純C#工程在不重做的情況下改造成具備熱更新功能的工程。
三.準備工作--說明:使用的Unity版本為2019.4.18f1c1,匯入的xlua為2021年4月4日從GitHub上直接clone的工程檔案,沒有下載release版本。
1.xlua框架匯入
在GitHub上搜尋xlua,找到騰訊的xlua專案,下載專案的壓縮包。
下載完成後解壓,發現下載的是一個Unity工程檔案:
在工程檔案中,核心程式碼是Assets目錄下的Plugins和XLua這兩個資料夾中的內容,將其複製到自己的工程檔案中即可。
將這兩個資料夾複製到自己的工程中後稍等一會兒,就會發現在選單欄中Windows選單選項左側出現了XLua選單選項,沒有報錯的話說明成功匯入。
2.AB包工具匯入
在Unity中通過PackageManager匯入AB包工具,匯入方法詳見:熱更新基礎--AssetBundle學習筆記
3.AB包管理器
為了方便載入AB包,我們可以製作一個AB包的管理器指令碼,指令碼詳見:熱更新基礎--AssetBundle學習筆記
四.C#呼叫lua
1.lua解析器
void Start() { //Lua解析器,能夠在Unity中執行Lua LuaEnv env = new LuaEnv(); //執行單行Lua語言,使用DoString成員方法 env.DoString("print('hello world!')"); //執行lua指令碼,一般都是直接呼叫lua語言的require關鍵字執行lua指令碼 //預設尋找指令碼的路徑是在Resources下 //lua字尾Unity不能識別,需要將lua檔案新增上.txt以便Unity識別 env.DoString("require('Main')"); //清除lua中沒有手動釋放的物件,相當於垃圾回收,一般在幀更新中定時執行或者切換場景時執行 env.Tick(); //銷燬lua解析器,但是一般不會銷燬,因為最好保持解析器的唯一性以節約效能 env.Dispose(); }
2.lua檔案載入重定向,即更改使用require關鍵字載入lua檔案時尋找lua檔案的位置(預設lua指令碼在Resources下才能識別,這和熱更新的目的衝突)
void Start() { LuaEnv env = new LuaEnv(); //使用AddLoader方法新增重定向,即自定義檔案載入的規則 //引數為一個委託,這個委託有一個ref引數,自動執行傳入require執行的指令碼檔名,在委託中拼接好完整的路徑;委託的返回值為lua檔案轉化出的位元組陣列 //新增委託後,委託在執行require語句時自動執行 env.AddLoader(MyCustomLoader); //使用require語句執行lua檔案,會自動先呼叫新增的重定向方法尋找lua檔案,如果找不到再到預設路徑Resources下尋找 env.DoString("require('Main')"); } /// <summary> /// 重定向方法 /// </summary> /// <param name="filePath">檔名</param> /// <returns></returns> private byte[] MyCustomLoader(ref string filePath) { //拼接完整的lua檔案所在路徑 string path = Application.dataPath + "/Lua/" + filePath + ".lua"; Debug.Log(path); //判斷檔案是否存在,存在返回讀取的檔案位元組陣列,不存在列印提醒資訊,返回null if (File.Exists(path)) { return File.ReadAllBytes(path); } else { Debug.Log("MyCustomLoader重定向失敗,檔名為" + filePath); } return null; }
3.lua解析器管理器:對lua解析器的進一步封裝以方便使用
/// <summary> /// lua管理器,對lua解析器的進一步封裝,保證lua解析器的唯一性 /// </summary> public class LuaManager { //單例模組 private static LuaManager instance; public static LuaManager Instance { get { if (instance == null) instance = new LuaManager(); return instance; } } private LuaManager() { //在構造方法中就為唯一的lua解析器賦值 luaEnv = new LuaEnv(); //載入lua指令碼重定向 //重定向到lua資料夾下 luaEnv.AddLoader((ref string filePath) => { //拼接完整的lua檔案所在路徑 string path = Application.dataPath + "/Lua/" + filePath + ".lua"; //判斷檔案是否存在,存在返回讀取的檔案位元組陣列,不存在列印提醒資訊,返回null if (File.Exists(path)) { return File.ReadAllBytes(path); } else { Debug.Log("MyCustomLoader重定向失敗,檔名為" + filePath); } return null; }); //重定向載入AB包中的lua指令碼 luaEnv.AddLoader((ref string filePath) => { /*//載入AB包 string path = Application.streamingAssetsPath + "/lua"; AssetBundle bundle = AssetBundle.LoadFromFile(path); //載入lua檔案,返回 TextAsset texts = bundle.LoadAsset<TextAsset>(filePath + ".lua"); //返回載入到的lua檔案的byte陣列 return texts.bytes;*/ /*//使用AB包管理器載入,非同步載入 byte[] luaBytes = null; AssetBundleManager.Instance.LoadResAsync<TextAsset>("lua", filePath + ".lua", (lua) => { if (lua != null) luaBytes = lua.bytes; else Debug.Log("重定向失敗,從AB包載入lua檔案失敗"); }); return luaBytes;*/ //使用AB包管理器載入,同步載入 return AssetBundleManager.Instance.LoadRes<TextAsset>("lua", filePath + ".lua").bytes; }); } //持有一個唯一的lua解析器 private LuaEnv luaEnv; //luaEnv中的大G表,提供給外部呼叫 public LuaTable Global { get { //校驗一下instance是否是null,避免dispose後無法獲取的情況出現 if (instance == null) instance = new LuaManager(); return luaEnv.Global; } } /// <summary> /// 執行單句lua程式碼 /// </summary> /// <param name="luaCodeString"></param> public void DoString(string luaCodeString) { luaEnv.DoString(luaCodeString); } /// <summary> /// 執行lua檔案的程式碼,直接提供檔名即可執行檔案,不需要再書寫lua的require語句,在方法內部拼接lua語句 /// </summary> /// <param name="fileName">lua檔名</param> public void DoLuaFile(string fileName) { luaEnv.DoString("require('" + fileName + "')"); } /// <summary> /// 釋放解析器 /// </summary> public void Tick() { luaEnv.Tick(); } /// <summary> /// 銷燬解析器 /// </summary> public void Dispose() { luaEnv.Dispose(); //銷燬解析器後將lua解析器物件和單例變數都置空,下次呼叫時會自動呼叫建構函式建立lua解析器,以免報空 luaEnv = null; instance = null; } }
4.訪問變數
void Start() { LuaManager.Instance.DoLuaFile("Main"); //使用_G表獲取luaenv中的global變數值 Debug.Log(LuaManager.Instance.Global.Get<int>("testNumber")); Debug.Log(LuaManager.Instance.Global.Get<bool>("testBool")); Debug.Log(LuaManager.Instance.Global.Get<float>("testFloat")); //使用_G表修改luaenv中的global變數值 LuaManager.Instance.Global.Set("testBool",false); Debug.Log(LuaManager.Instance.Global.Get<bool>("testBool")); //不能直接獲取和設定本地變數 }
5.訪問函式,使用委託接收
//自定義委託,對於有引數和返回值的委託,必須加上特性[CSharpCallLua],否則無法處理,無參無返回值的委託不需要 //特性起作用還需要在Unity中生成指令碼 [CSharpCallLua] public delegate void CustomCall(int a); //自定義含有out或者ref引數的委託用於接收多返回值函式,out和ref根據需要選擇,都可以用於接收多返回值 [CSharpCallLua] public delegate int CustomCall2(out int a, out int b); [CSharpCallLua] public delegate void CustomCall3(params int[] args);
void Start() { LuaManager.Instance.DoLuaFile("Main"); //獲取函式,使用委託儲存 UnityAction npnr = LuaManager.Instance.Global.Get<UnityAction>("NoParamNoReturn"); npnr(); //xlua提供了獲取函式的方法,但是不推薦使用,推薦使用Unity或者C#提供的委託或者自定義委託儲存 LuaFunction luaFun = LuaManager.Instance.Global.Get<LuaFunction>("NoParamNoReturn"); luaFun.Call(); //有參無返回值 //使用自定義的委託需要宣告特性且在Unity中生成程式碼 CustomCall hpnr = LuaManager.Instance.Global.Get<CustomCall>("HaveParamNoReturn"); hpnr(2); //使用C#提供的委託儲存,不用宣告特性 Action<int> hpnr2 = LuaManager.Instance.Global.Get<Action<int>>("HaveParamNoReturn"); hpnr2(2); //多返回值 //不能使用系統自帶的委託,多返回值需要自定義委託 CustomCall2 mr = LuaManager.Instance.Global.Get<CustomCall2>("ManyReturns"); int m; int n; int p = mr(out m, out n); Debug.Log(m + "-" + n + "-" + p); //變長引數 CustomCall3 vp = LuaManager.Instance.Global.Get<CustomCall3>("VariableParams"); vp(1, 2, 3, 4, 5); }
6.表對映為List或者Dictionary
void Start() { LuaManager.Instance.DoLuaFile("Main"); //得到List List<int> list = LuaManager.Instance.Global.Get<List<int>>("ListTable"); foreach (int i in list) { Debug.Log(i); } //得到Dictionary Dictionary<string, int> dic = LuaManager.Instance.Global.Get<Dictionary<string, int>>("DictionaryTable"); foreach (KeyValuePair<string,int> pair in dic) { Debug.Log(pair.Key + ":" + pair.Value); } }
7.表對映到類物件
/// <summary> /// 宣告一個類來和lua中的類進行對映,變數名稱必須和lua中的對應類一致,但是不必一一對應,對映時會自動丟棄找不到的內容 /// </summary> public class CallLuaClass { public string name; public int age; public int sex; public UnityAction eat; }
void Start() { LuaManager.Instance.DoLuaFile("Main"); //獲得類物件 CallLuaClass clc = LuaManager.Instance.Global.Get<CallLuaClass>("testClass"); Debug.Log(clc.name + "-" + clc.age + "-" + clc.sex); clc.eat(); }
8.表對映到介面
/// <summary> /// 使用一個介面接收表的對映,但是介面中的變數不允許被賦值,這裡使用屬性 /// 必須加上特性[CSharpCallLua] /// </summary> [CSharpCallLua] public interface ICSharpCallLua { string name { get; set; } int age { get; set; } int sex { get; set; } Action eat { get; set; } }
void Start() { LuaManager.Instance.DoLuaFile("Main"); //得到介面物件 ICSharpCallLua icscl = LuaManager.Instance.Global.Get<ICSharpCallLua>("testClass"); Debug.Log(icscl.name + "-" + icscl.age + "-" + icscl.sex); icscl.eat(); }
注意:之前實現的所有拷貝都是引用拷貝,也就是c#中的拷貝值發生改變,lua程式碼不受影響,但是介面的拷貝是引用拷貝,也就是改變C#中的拷貝的值,lua中的值也發生了改變。
9.對映到luaTable類
void Start() { LuaManager.Instance.DoLuaFile("Main"); //得到LuaTable,對應lua中的table。 //本質上Global也是LuaTable型別的變數,使用方法和之前通過Global獲取各種變數函式等方法相同 //不推薦使用LuaTable和LuaFunction,效率低 //LuaTable的拷貝是引用拷貝 LuaTable table = LuaManager.Instance.Global.Get<LuaTable>("testClass"); Debug.Log(table.Get<int>("age")); Debug.Log(table.Get<string>("name")); Debug.Log(table.Get<int>("sex")); table.Get<LuaFunction>("eat").Call(); }
LuaTable類對應Lua中的表,本質上Global變數也是LuaTable型別,所以LuaTable的使用方法和之前講的通過Global獲取各種變數的方法相同。LuaTable和LuaFunction使用後記得呼叫dispose方法釋放垃圾,否則容易造成記憶體洩漏。
五.lua呼叫C#
1.在Unity中無法直接執行lua,因此需要使用C#指令碼作為lua指令碼的主入口啟動lua指令碼的執行,接下來都不再贅述這一步驟,所有的lua程式碼也都在這個特定的lua指令碼中編寫。
public class Main : MonoBehaviour { private void Start() { //在這個指令碼中啟動特定的lua指令碼,接下來的lua程式碼都在這個指令碼中編寫 LuaManager.Instance.DoLuaFile("Main"); } }
Main.lua這個指令碼作為lua指令碼的入口,接下來再在這個Main.lua指令碼中呼叫其他指令碼。
require("CallClass")
2.建立類物件
--lua中呼叫C#指令碼 --建立類物件 --Unity中的類如GameObject、Transform等類都儲存在CS表中 --使用CS.名稱空間.類名的方式呼叫Unity中的類 local obj1 = CS.UnityEngine.GameObject("使用lua建立的第一個空物體")
--lua中呼叫C#指令碼 --建立類物件 --Unity中的類如GameObject、Transform等類都儲存在CS表中 --使用CS.名稱空間.類名的方式呼叫Unity中的類 --每次都寫名稱空間太麻煩,可以定義全域性變數先把類儲存起來,也能節約效能 GameObject = CS.UnityEngine.GameObject local obj = GameObject("movin") --使用點來呼叫靜態方法 local obj2 = GameObject.Find("movin") --使用.來呼叫物件中的成員變數 Log = CS.UnityEngine.Debug.Log Log(obj.transform.position) Vector3 = CS.UnityEngine.Vector3 --使用物件中的成員方法必須使用:呼叫 obj.transform:Translate(Vector3.right) Log(obj.transform.position) --自定義類的呼叫 --直接使用CS點的方式呼叫 local customClass = CS.CustomClass() customClass.name = "movin" customClass:Eat() --有名稱空間的類再點一層 local customClassInNamespace = CS.CustomNamespace.CustomClassInNamespace() customClassInNamespace.name = "movin2" customClassInNamespace:Eat() --繼承了mono的類不能new出來,只能獲取元件 --xlua提供了typeof的方法得到類的Type --自定義的指令碼元件直接用CS點出來即可 obj:AddComponent(typeof(CS.Main)) --系統自帶的指令碼一般在UnityEngine名稱空間下 obj:AddComponent(typeof(CS.UnityEngine.Rigidbody))
/// <summary> /// 自定義類 /// </summary> public class CustomClass { public string name; public void Eat() { Debug.Log(name + "在吃飯"); } } /// <summary> /// 自定義類,包裹在名稱空間中 /// </summary> namespace CustomNamespace { public class CustomClassInNamespace { public string name; public void Eat() { Debug.Log(name + "在吃飯"); } } }
3.使用列舉
--呼叫列舉 --呼叫Unity提供的列舉 --Unity提供的列舉一般在UnityEngine中 PrimitiveType = CS.UnityEngine.PrimitiveType GameObject = CS.UnityEngine.GameObject local obj = GameObject.CreatePrimitive(PrimitiveType.Cube) --呼叫自定義的列舉 E_CustomEnum = CS.E_CustomEnum Log = CS.UnityEngine.Debug.Log Log(E_CustomEnum.Idle) --使用_CastFrom方法進行列舉型別轉換,可以從數字轉換成列舉或者字串轉換成列舉 Log(E_CustomEnum.__CastFrom(1)) Log(E_CustomEnum.__CastFrom("Atk"))
4.使用List和Dictionary
local CustomClass = CS.CustomClass local Log = CS.UnityEngine.Debug.Log --呼叫陣列,使用C#的陣列相關API,不要使用lua的方法 obj = CustomClass(); Log(obj.array.Length) --遍歷陣列,注意從0到length-1,按照C#的下標遍歷不是lua的 for i=0,obj.array.Length-1 do Log(obj.array[i]) end Log("******************") --建立陣列,利用陣列類Array的CreateInstance靜態方法建立陣列 --引數意思:建立陣列中儲存元素的型別、建立的陣列的長度 local arr = CS.System.Array.CreateInstance(typeof(CS.System.Int32),5) Log(arr.Length) Log(arr[1]) Log("******************") --呼叫List,呼叫成員方法用: obj.list:Add('M') for i = 0,obj.list.Count-1 do Log(obj.list[i]) end Log("******************") --建立List --老版,方法很麻煩 local list2 = CS.System.Collections.Generic["List`1[System.String]"]() list2:Add("abcde") Log(list2[0]) --新版 版本>v2.1.12 先建立一個類,再例項化出來list local List_String = CS.System.Collections.Generic.List(CS.System.String) local list3 = List_String() list3:Add("aaaaaaaaaa") Log(list3[0]) Log("******************") --呼叫dic obj.dic:Add(1,"abc") obj.dic:Add(2,"def") --遍歷 for k,v in pairs(obj.dic) do Log(k.."--"..v) end Log("******************") --建立dic local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3) local dic2 = Dic_String_Vector3() dic2:Add("abc",CS.UnityEngine.Vector3.right) dic2:Add("def",CS.UnityEngine.Vector3.up) Log(dic2["abc"]) --在lua中建立的字典使用這種方式得不到值,這句程式碼列印出的結果是空值 Log(dic2:get_Item("abc")) --在lua中自己建立的字典使用get_Item方法得到值 dic2:set_Item("abc",CS.UnityEngine.Vector3.left) --同樣地,通過set_Item方法設定字典地值 Log(dic2:get_Item("abc")) print(dic2:TryGetValue("abc")) --也可以通過TryGetValue方法獲取值 for k,v in pairs(dic2) do print(k,v) end
/// <summary> /// 自定義類 /// </summary> public class CustomClass { public string[] array = { "a","b","c","d","e","f","g","h" }; public List<char> list = new List<char>{ 'A','B','C','D','E','F','G','H','I','J' }; public Dictionary<int, string> dic = new Dictionary<int, string>(); }
5.使用C#擴充方法
CustomClass = CS.CustomClass --使用成員方法 local customClass = CustomClass() customClass.name = "movin" customClass:Eat() --使用擴充方法,擴充方法一定是靜態方法,但是呼叫時和成員方法一樣的呼叫方式 --在定義擴充方法的工具類前一定加上特性[LuaCallCSharp],並且生成程式碼 customClass:Move()
/// <summary> /// 自定義類 /// </summary> public class CustomClass { public string name; public void Eat() { Debug.Log(name + "在吃飯"); } } /// <summary> /// 工具類,定義擴充方法 /// </summary> [LuaCallCSharp] public static class Tools { public static void Move(this CustomClass cc) { Debug.Log(cc.name + "在移動"); } }
建議:要在lua中使用的C#類都可以加上[LuaCallCSharp]特性,這樣預先將程式碼生成,可以提高Lua訪問C#類的效能。
6.使用含有ref和out引數的函式
CustomClass = CS.CustomClass local obj = CustomClass() --ref引數,使用多返回值形式接收 --如果函式有返回值,這個返回值是多返回值的第一個 --引數數量不夠,會預設使用預設值補位 local a,b,c = obj:RefFun(1,0,0,1) print(a,b,c) --out引數,還是以多返回值的形式接收 --out引數不需要傳遞值 local a,b,c = obj:OutFun(23,24) print(a,b,c) --綜合來看 --從返回值上看,ref和out都會以多返回值的形式返回,原來如果有返回值的話原來的返回值是多返回值中的第一個 --從引數看,ref引數需要傳遞,out引數不需要傳遞 local a,b,c = obj:RefOutFun(12,23) print(a,b,c)
/// <summary> /// 自定義類 /// </summary> public class CustomClass { public int RefFun(int a ,ref int b,ref int c,int d) { b = a + d; c = a - d; return 100; } public int OutFun(int a,out int b,out int c,int d) { b = a; c = d; return 200; } public int RefOutFun(int a,out int b,ref int c) { b = a * 10; c = a * 20; return 200; } }
7.使用過載函式
CustomClass = CS.CustomClass local customClass = CustomClass() --使用過載函式 --lua支援呼叫C#的過載函式 --lua中的數值型別只有number,所以對C#中多精度的過載函式支援不好,使用時可能出現問題 --如第四個過載函式呼叫結果為0(應該是11.4),所以應避免這種情況 print(customClass:Calc()) print(customClass:Calc(1)) print(customClass:Calc(2,3)) print(customClass:Calc(1.4)) --解決過載函式含糊的問題(效率低,僅作了解) --運用反射 local m1 = typeof(CustomClass):GetMethod("Calc",{typeof(CS.System.Int32)}) local m2 = typeof(CustomClass):GetMethod("Calc",{typeof(CS.System.Single)}) --通過xlua提供的tofunction方法將反射得到的方法資訊轉化為函式 local f1 = xlua.tofunction(m1) local f2 = xlua.tofunction(m2) --再次呼叫函式,非靜態方法需要傳入物件 print(f1(customClass,10)) print(f2(customClass,1.4))
/// <summary> /// 自定義類 /// </summary> public class CustomClass { public int Calc() { return 100; } public int Calc(int a,int b) { return a + b; } public int Calc(int a) { return a; } public float Calc(float a) { return a + 10; } }
8.委託和事件
local customClass = CS.CustomClass() --委託中儲存的是函式,宣告函式儲存到委託中 local fun = function() print("函式fun") end --委託中第一次新增函式使用=新增 customClass.action = fun --委託中第二次新增函式使用+=,lua中不支援+=運算子,需要分開寫 customClass.action = customClass.action + fun --委託中也可以新增匿名函式 customClass.action = customClass.action + function() print("臨時函式") end --使用點呼叫委託還是冒號呼叫委託都可以呼叫,最好使用冒號 customClass:action() print("********************") --事件和委託的使用方法不一致(事件不能在外部呼叫) --使用冒號新增和刪除函式,第一個引數傳入加號或者減號字串,表示新增還是修改函式 --事件也可以新增匿名函式 customClass:eventAction("+",fun) --事件不能直接呼叫,必須在C#中提供呼叫事件的方法,這裡已經提供了DoEvent方法執行事件 customClass:DoEvent() --同樣地,事件不能直接清空,需要在C#中提供對應地方法
/// <summary> /// 自定義類 /// </summary> public class CustomClass { public UnityAction action; public event UnityAction eventAction; public void DoEvent() { if (eventAction != null) eventAction(); } }
9.特殊問題
local customClass = CS.CustomClass() --特殊問題一:得到二維陣列指定位置元素的值 --獲取二維陣列的長度 print("行:"..customClass.array:GetLength(0)) print("行:"..customClass.array:GetLength(1)) --不能通過C#的索引訪問元素(array[0,0]或array[0][0]) --使用陣列提供的成員方法GetValue訪問元素 print(customClass.array:GetValue(0,0)) --遍歷 for i=0,customClass.array:GetLength(0)-1 do for j=0,customClass.array:GetLength(1)-1 do print(customClass.array:GetValue(i,j)) end end print("***********************") --特殊問題二:lua中空值nil和C#中空值null的比較 --往場景物件上新增一個指令碼,存在就不加,不存在再加 GameObject = CS.UnityEngine.GameObject Rigidbody = CS.UnityEngine.Rigidbody local obj = GameObject("測試nil和null") local rigidbody = obj:GetComponent(typeof(Rigidbody)) print(rigidbody) --校驗空值,看是否需要新增指令碼 --nil和null並不相同,在lua中不能使用==進行判空,一定要使用Equals方法進行判斷 --這裡如果rigidbody為空,可能報錯,所以可以自己提供一個判空函式進行判空 --這裡為了筆記方便將函式定義在這裡,這個全域性函式最好定義在lua指令碼啟動的主函式Main中 function IsNull(obj) if obj == nil or obj:Equals(nil) then return true end return false end --使用自定義的判空函式進行判斷 if IsNull(rigidbody) then rigidbody = obj:AddComponent(typeof(Rigidbody)) end print(rigidbody) print("***********************") --特殊問題三:讓lua和系統型別能夠相互訪問 --對於自定義的型別,可以新增CSharpCallLua和LuaCallCSharp這兩個特性使Lua和自定義型別能相互訪問,但是對於系統類或第三方程式碼庫,這種方式並不適用 --為系統類或者第三方程式碼庫加上這兩個特性的寫法比較固定,詳情見C#程式碼
/// <summary> /// 自定義類 /// </summary> public class CustomClass { public int[,] array = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } }; //實現為系統類新增[CSharpCallLua]和[LuaCallCSharp]特性 [CSharpCallLua] public static List<Type> csharpCallLuaList = new List<Type>() { //將需要新增特性的類放入list中 typeof(UnityAction<float>), }; [LuaCallCSharp] public static List<Type> luaCallCsharpList = new List<Type>() { typeof(GameObject), }; }
10.使用協程
--xlua提供了一個工具表,要使用協程必須先呼叫這個工具表 util = require("xlua.util") GameObject = CS.UnityEngine.GameObject WaitForSeconds = CS.UnityEngine.WaitForSeconds local obj = GameObject("Coroutine") local mono = obj:AddComponent(typeof(CS.LuaCallCSharp)) --被開啟的協程函式 fun = function() local a = 1 while true do --lua中不能直接使用C#中的yield return返回 --使用lua中的協程返回方法 coroutine.yield(WaitForSeconds(1)) print(a) a = a + 1 if a>10 then --協程的關閉,必須要將開啟的協程儲存起來 mono:StopCoroutine(startedCoroutine) end end end --啟動協程 --寫法固定,必須使用固定表的cs_generate方法把xlua方法處理成mono能夠使用的協程方法 startedCoroutine = mono:StartCoroutine(util.cs_generator(fun))
11.使用泛型函式
lua中沒有泛型語法,對於C#中的泛型方法,可以直接傳遞引數(因為lua中不需要宣告型別),但是這種寫法並不是所有的泛型方法都支援,xlua只支援有約束且泛型作為引數的泛型函式,其他泛型函式不支援。如果要在lua中呼叫泛型函式,可以使用特定的語法。
local tank = CS.UnityEngine.GameObject.Find("Tank") --xlua提供了得到泛型函式的方法get_generic_method,引數第一個為類名,第二個為方法名 local addComponentFunc = xlua.get_generic_method(CS.UnityEngine.GameObject,"AddComponent") --接著呼叫這個泛型方法,引數為泛型的類,得到一個新方法 local addComponentFunc2 = addComponentFunc(CS.MonoForLua) --呼叫,第一個引數是呼叫的物件,如果有其他引數在後面傳遞 addComponentFunc2(tank)
使用限制:打包時如果使用mono打包,這種方式支援使用;如果使用il2cpp打包,泛型引數需要是引用型別或者是在C#中已經呼叫過的值型別。