熱更新解決方案--xlua學習筆記

一.熱更新方案簡介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#中已經調用過的值類型。

相關文章
相關標籤/搜索