熱更新解決方案–xlua學習筆記
一.熱更新方案簡介
在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#中已經調用過的值類型。