一.熱更新方案簡介html
在Unity遊戲工程中,C#代碼(編譯型語言)資源和Resources文件夾下的資源打包後都不能夠更改,所以這部份內容不能進行熱更新,而lua代碼(解釋型語言)邏輯不須要進行預編譯再運行,能夠在遊戲運行過程當中進行修改,AB包資源也能夠在遊戲運行過程當中下載解壓縮並使用其中的資源。所以客戶端能夠在啓動時檢驗服務器端的AB包資源是否有更新,若是有更新先下載更新,將lua代碼資源和其餘更新資源打包爲AB包放在服務器端,客戶端下載後直接在運行過程當中解壓縮並使用更新資源,實現了客戶端不中斷運行即完成更新的目的,也就是熱更新。c#
二.xlua熱更新方案簡介數組
xlua框架提供了C#和lua相互調用的功能及Hotfix熱補丁的功能,主要目的是方便咱們將純C#工程在不重作的狀況下改形成具有熱更新功能的工程。服務器
三.準備工做--說明:使用的Unity版本爲2019.4.18f1c1,導入的xlua爲2021年4月4日從GitHub上直接clone的工程文件,沒有下載release版本。app
1.xlua框架導入框架
在GitHub上搜索xlua,找到騰訊的xlua項目,下載項目的壓縮包。異步
下載完成後解壓,發現下載的是一個Unity工程文件:函數
在工程文件中,核心代碼是Assets目錄下的Plugins和XLua這兩個文件夾中的內容,將其複製到本身的工程文件中便可。工具
將這兩個文件夾複製到本身的工程中後稍等一下子,就會發如今菜單欄中Windows菜單選項左側出現了XLua菜單選項,沒有報錯的話說明成功導入。post
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#中已經調用過的值類型。