xLua热更新解决方案知识点汇总

XLua热更新解决方案知识点汇总

前言

​ 热更新就是能实现无需重新打包安装,在线更新客户端的一种常用的方案。一般分为资源的热更新和代码的热更新,针对代码的热更新又有很多的方案,其中由腾讯提供的xLua即是目前Lua热更新中较为常用的一种解决方案,本文只针对xLua进行知识点的汇总,有对各种热更新感兴趣的读者可以参考我专门讲解各种热更新的基本作用和原理的博客。

热更新的本质

  • 为Unity提供Lua编程的能力
  • 让C#和Lua可以相互调用访问

unity原生的语言都是基于C#实现的相关API,但C#代码需要编译才能使用,直接使用C#有诸多限制。所以,使用无需编译的脚本语言Lua的解决方案成为了热更新的一种较为常用的解决方案。Lua本身并不支持调用C#和U3D的相关API,xLua方案的本质其实就是使得Lua能够调用U3D提供的C#的相关API,进而使得进行代码热更新时,进行Lua代码的替换更新即可实现。

C# Call Lua相关知识点

Lua解析器:LuaEnv
  • xLua提供的封装好的C#对象,能够用其提供的相关API实现C#中调用Lua

  • 本质:在C#运行环境中实现了一个能够运行Lua的虚拟环境,在虚拟环境中实现Lua各种类型到C#类型的转换,进而为C#提供调用访问Lua的能力。

  • 提供的相关API 及用法解释

    //xLua命名空间引用
    using XLua;
    //Lua解析器LuaEnv的获取
    LuaEnv env = new LuaEnv();
    
    //执行Lua命令
    env.DoString("Lua命令"); //一般用来加载Lua主入口脚本
    
    //清除Lua中未手动释放的对象进行垃圾回收
    env.Tick(); //一般用于帧更新中定时执行 或者 切换场景时执行
    
    //销毁释放Lua解析器
    env.Dispose();
    
    //========重难点:重定向 Loader=======
    //xLua默认在Resources下加载脚本 其它位置的Lua无法加载到,需要自定义加载
    //xLua提供的路径重定向方法,当执行Lua语言中的require时,会自动回调API中作为参数的委托方法
    env.AddLoader(LuaEnv.CustomLoader func);
    
    //委托形式 传入require参数的引用,返回需要加载的Lua脚本文件的字节数组
    public delegate byte[] CustomLoader(ref string filepath);
    

    API的使用注意点:

    Lua解析器默认从Resources下加载,u3d下的Resources目录无法识别Lua脚本后缀,所以必须为.lua脚本再添加诸如.txt的后缀才能访问和调用。 AB包中也有这个特殊情况,但自定义文件夹下可以直接进行加载无需额外添加后缀。同时解析器提供的重定向支持多次

    由于这些情况,为了方便开发,在日常编译器下均选择从自定义文件夹下加载无需每次都添加后缀,打包上线后必须由AB包加载才能实现热更。那么可以实现编辑器下的小功能,一键将所有自定义文件夹下的Lua脚本打成AB包并加上后缀即可,后面笔者会进行实现。

  • LuaManager.cs 封装API,实现Lua管理器 (用到了单例模式和AB包管理器 在AB包的那篇博客中有过详细解释,有需要了解的可以去查看)

    using System.IO;
    using UnityEngine;
    using XLua;
    
    namespace Common
    {
        /// <summary>
        /// Lua 管理器
        /// 对外提供 Lua解析器的简单调用 保证唯一性
        /// </summary>
        public class LuaManager : SingletonLazy<LuaManager>
        {
            //执行Lua语言的函数 释放垃圾 销毁 重定向
            private LuaEnv luaEnv;
    
            //返回Lua中的大G表_G  提供查找Lua全局变量的方法
            //T obj = Global.Get<T>("变量名");
            public LuaTable Global
            {
                get
                {
                    //LuaEnv的属性
                    return luaEnv.Global;
                }
            }
            protected override void Init()
            {
                base.Init();
                luaEnv = new LuaEnv();
                //自定义路径下加载 Assets/Lua下 日常开发使用方便
                luaEnv.AddLoader(MyCustomLoader);
                //Lua脚本重定向 在AB包中进行加载  打包上线时使用
                //luaEnv.AddLoader(CustomABLoader);
            }
    
    
    
            //Lua脚本会放在AB包中,再加载其中的Lua脚本资源来执行它
            //AB包中也要通过加载文本的方式(后缀为.lua不会识别还需加.txt)
            private byte[] CustomABLoader(ref string filepath)
            {
                //从AB包中加载Lua文件
                //加载AB包下的Lua文件--通过AB包管理器
                TextAsset text = ABManager.Instance.LoadResource<TextAsset>("lua", filepath +".lua");
                if(text == null)
                {
                    Debug.Log("重定向失败 文件名为" + filepath);
                    return null;
                }
                    
                //加载byte数组
                return text.bytes;
            }
    
            //重定向:自定义路径委托方法
            private byte[] MyCustomLoader(ref string filepath)
            {
                //通过函数中的逻辑 去 加载Lua文件
                //filepath就是require中的文件名
                //拼接一个Lua文件所在的路径
                string path = Application.dataPath + "/Lua/" + filepath + ".lua";
    
                //利用File 读取文件
                //判断文件是否存在
                if (File.Exists(path))
                {
                    return File.ReadAllBytes(path);
                }
                else
                {
                    Debug.Log("重定向失败 文件名为:" + filepath);
                }
                return null;
            }
    
            /// <summary>
            /// 执行Lua语言
            /// </summary>
            /// <param name="str">Lua语句</param>
            public void DoString(string str)
            {
                if(luaEnv == null)
                {
                    Debug.Log("解析器为空,请检查");
                    return;
                }
                luaEnv.DoString(str);
            }
    
            public void DoLuaFile(string fileName)
            {
                if (luaEnv == null)
                {
                    Debug.Log("解析器为空,请检查");
                    return;
                }
                string path = string.Format("require('{0}')", fileName);
                luaEnv.DoString(path);
            }
            /// <summary>
            /// 释放垃圾
            /// </summary>
            public void Tick()
            {
                if (luaEnv == null)
                {
                    Debug.Log("解析器为空,请检查");
                    return;
                }
                luaEnv.Tick();
            }
            /// <summary>
            /// 销毁Lua解析器
            /// </summary>
            public void Dispose()
            {
                if (luaEnv == null)
                {
                    Debug.Log("解析器为空,请检查");
                    return;
                }
                luaEnv.Dispose();
                luaEnv = null;
            }
    
        }
    }
    
C# 获取Lua的基础类型

利用LuaEnv提供的Global属性进行全局变量的获取和设置,注意其获取不到Local本地变量

Global的相关API和使用方法 (注:利用上方的Lua管理器提供的Global属性进行调用)

//设置Lua中基本变量的值 利用Set函数
//Set<Tkey,Tvalue>("变量名",值) 一般第一个泛型为string类型代表变量名 第二个为value,可用于创建/修改Lua中变量的值
LuaManager.Instance.Global.Set<string,int>("testNumber", 100);
//获取Lua中基本变量的值 Get<T>("变量名"); 泛型和返回值类型一致
int testNumber = LuaManager.Instance.Global.Get<int>("testNumber");
bool testBool = LuaManager.Instance.Global.Get<bool>("testBool");
float testFloat = LuaManager.Instance.Global.Get<float>("testFloat");
string testString = LuaManager.Instance.Global.Get<string>("testString");
//注意获取Lua的数字类型时,要根据Lua中具体的值来确定是用int float 还是double进行存储
C#获取Lua的全局函数function

获取Lua函数的两种主要方式

  1. 利用委托进行获取
  2. 利用xLua提供的LuaTable进行获取(效率低 建议使用第一种)
  • 获取无参无返回值的函数

    //无参无返回值的Lua函数的获取
    //自定义无参无返回委托获取Lua函数
    public delegate void CustomCall(); 
    CustomCall call = LuaManager.Instance.Global.Get<CustomCall>("testFun"); //获取Lua中无参无返回的函数testFun
    call(); //调用委托
    //利用C#提供的 委托Action进行获取 --Action无参无返回
    Action action = LuaManager.Instance.Global.Get<Action>("testFun");
    action();
    //Unity 提供的委托
    UnityAction unityAction = LuaManager.Instance.Global.Get<UnityAction>("testFun");
    unityAction();
    //Xlua提供的一种获取函数的方式 ---少用,效率低
    LuaFunction luaF = LuaManager.Instance.Global.Get<LuaFunction>("testFun");
    luaF.Call();
    
  • 获取有参有返回值的函数

    [CSharpCallLua]
    //有参有返回值委托 -- 注意当自定义委托识别不出时 一定要添加特性和重新生成代码
    public delegate int CustomCall2(int a);
    //有参有返回的函数获取 ======
    CustomCall2 call2 = LuaManager.Instance.Global.Get<CustomCall2>("testFun2");
    print("有参有返回值函数的返回值为 : " + call2(10));
    //C# 提供的有返回值委托
    Func<int, int> func = LuaManager.Instance.Global.Get<Func<int, int>>("testFun2");
    print("有参有返回值函数的返回值为 : " + func(10));
    //XLua 提供的LuaFunction 由于Lua函数多返回值的特点 返回的是数组
    LuaFunction luaF2 = LuaManager.Instance.Global.Get<LuaFunction>("testFun2");
    print("有参有返回值函数的返回值为 : " + luaF2.Call(10)[0]); //返回的是数组取第一个
    
  • 多返回值函数的获取(注意自定义委托可能未预定义,需要添加特性并重新生成代码)

    //多返回值的委托定义 利用ref out构造返回值参数
    [CSharpCallLua] //若XLua不识别委托类型 需要加此特性 并且编辑器重新生成XLua代码
    //多返回值 -- 第一个返回值为return的 后续返回值用out/ref接收 先定义参数
    public delegate int CustomCall3(int a,out int var2,out bool var3,ref string var4,out int var5);
    
    //多返回值函数获取 委托用out ref  LuaFunction调用返回值为数组 遍历即可
    //C# 中 使用 out ref获取
    CustomCall3 call3 = LuaManager.Instance.Global.Get<CustomCall3>("testFun3");
    //out 外面无需初始化 out只出不进 初始化也不会传入进去
    int var2; bool var3; string var4 = ""; int var5; //ref需要初始化 因为它有进有出 不能进去未初始化的值
    int var1 = call3(10,out var2,out var3,ref var4,out var5);
    print(string.Format("多返回值函数的返回值{0} {1} {2} {3} {4}",var1,var2,var3,var4,var5));
    //XLua提供 LuaFunction
    LuaFunction luaF3 = LuaManager.Instance.Global.Get<LuaFunction>("testFun3");
    //参数类型不一致时用obejct否则根据参数类型决定
    object[] objs = luaF3.Call(1000);
    for (int i = 0; i < objs.Length; i++) 
         print(string.Format("第{0}个返回值为{1}", i, objs[i]));
    
  • 变长参数函数的获取

    //可变参数委托的定义  Lua函数原型 function(a,...)
    public delegate void CustomCall4(string a, params int[] args); //变长参数的类型 是根据实际情况决定的 类型不一致就用Object
    //变长参数函数的获取
    CustomCall4 call4 = LuaManager.Instance.Global.Get<CustomCall4>("testFun4");
    //Lua中遍历时需要注意数组的使用
    call4("123", 1, 3, 11, 566);
    
    //XLua提供
    LuaFunction luaF4 = LuaManager.Instance.Global.Get<LuaFunction>("testFun4");
                luaF4.Call("123", 1, 3, 11, 566);
    
C#获取Lua的表Table

Lua中的表十分强大,可以存任何数据类型,可以自定义索引或自动填充索引。一般C#获取Lua中的表可以转化成以下几种形式。

  • 利用List列表获取Lua的table

    //Lua中table的获取是浅拷贝(值拷贝)
    //Lua中table元素类型统一
    List<int> list = LuaManager.Instance.Global.Get<List<int>>("testList");
    for (int i = 0; i < list.Count; i++)
    	Debug.Log(list[i]);
    //Lua中表的类型不确定用Object存
    List<object> list2 = LuaManager.Instance.Global.Get<List<object>>("testList2");
    for (int i = 0; i < list2.Count; i++)
    	Debug.Log(list2[i]);
    
  • 利用Dictionary字典获取Lua的table

    Dictionary<string, int> dic = LuaManager.Instance.Global.Get<Dictionary<string,int>>("testDic");
    foreach(var item in dic)
    {
    	Debug.Log(item.Key+"_"+item.Value);
    }
    //值拷贝
    Dictionary<object, object> dic2 = LuaManager.Instance.Global.Get<Dictionary<object, object>>("testDic2");
    foreach(var item in dic2)
    {
    	Debug.Log(item.Key + "_" + item.Value);
    }
    
  • 利用Class类获取Lua的table

    table充当类时 类在定义时需要注意的地方

    1. 成员变量的名字和Lua中表中元素的名字必须一致
    2. 类型一定对应,基本类型,函数类型用委托或LuaFunction,且必须是public变量
    3. 当参数个数不匹配时,Lua多余变量会舍弃,C#多余变量不会进行赋值。
    4. table中允许存table变量,类定义也可以进行嵌套
    --lua中提供的table表
    testClass = {
    	testInt = 2,
    	testBool = true,
    	testFloat = 1.2,
    	testString = "123",
    	testFunc = function()
    		print("123123")
    	end,
    	testInClass ={
    		testInInt = 10
    	}
    }
    
    [CSharpCallLua] //自定义类型无法识别,必须添加特性并重新生成代码
    public class CallLuaClass
    {
        //在这个类中去声明成员变量
        //名字一定要和Lua那边一样
        //一定是公共的 私有和保护的没有办法赋值
        public int testInt;
        public bool testBool;
        public float testFloat;
        public float testString;
        public UnityAction testFunc;
        //自定义中的变量可以更多或更少 , 如果变量比Lua中的少则忽略丢弃它
        //若变量比Lua中的多,不会去赋值
        public void Test()
        {
            Debug.Log("测试");
        }
    
        public CallLuaInClass testInClass;
    }
    
    //嵌套类
    public class CallLuaInClass
    {
        public int testInInt;
    }
    
    //获取仍然通过Get函数,依然是值拷贝
    CallLuaClass obj = LuaManager.Instance.Global.Get<CallLuaClass>("testClass");
    
  • 利用接口Interface获取Lua的table

    namespace ns
    {
        //接口必须加特性
        [CSharpCallLua] //接口每次更改都需要删除后重建代码
        public interface ICharpCallInterface
        {
            //接口中不允许有字段 委托等,用属性接收Lua中的变量
            int testInt { get; set; }
            bool testBool { get; set; }
            float testFloat { get; set; }
            string testString { get; set; }
            UnityAction testFunc { get; set; }
        }
        public class CallInterface : MonoBehaviour
        {
            private void Start()
            {
                LuaManager.Instance.DoLuaFile("Main");
                ICharpCallInterface obj = LuaManager.Instance.Global.Get<ICharpCallInterface>("testInterface");
                print(obj.testInt);
                obj.testInt = 100000;
                obj.testFunc();
                //================接口拷贝是深拷贝 引用拷贝 改了值 lua中值也相应更改==================
                ICharpCallInterface obj2 = LuaManager.Instance.Global.Get<ICharpCallInterface>("testInterface");
                print(obj2.testInt);
            }
        }
    }
    

    注意点:接口是深拷贝!是深拷贝!是深拷贝!

  • 利用xLua提供的LuaTable获取Lua的table

     //不建议使用LuaTable和LuaFunction 效率低 还有可能造成内存泄漏
    //引用对象  深拷贝(引用拷贝)
    LuaTable table = LuaManager.Instance.Global.Get<LuaTable>("testLuaTable");
    Debug.Log(table.Get<int>("testInt"));
    Debug.Log(table.Get<bool>("testBool"));
    Debug.Log(table.Get<float>("testFloat"));
    Debug.Log(table.Get<string>("testString"));
    table.Get<LuaFunction>("testFunc").Call();
    //修改时会改变Lua中的值
    table.Set("testInt", 55);
    Debug.Log(table.Get<int>("testInt"));
    table.Dispose(); //用完一定要销毁。
    

    注意点:LuaTable是深拷贝!

C# Call Lua中只有接口和LuaTable是深拷贝,其余全是浅拷贝

Lua Call C#相关知识点

​ 通常做需要热更新的相关逻辑开发时,一般都是用Lua去调用C#和U3D的相关类进行开发,相对C#调用Lua, Lua调用C#更为重要。

程序主入口

​ Unity中无法直接启动Lua脚本,需要通过C#调用Lua从而才能启动Lua。一般会设置一个启动Lua的主入口Main.lua 一般用来定义一些别名,启动其它脚本

--Main.lua Lua程序主入口 由此开始调用其它脚本
--注意 从Lua脚本里的require仍会进行重定向,一般所有Lua脚本都放在自定义文件夹下
require("Test") --启动Test.lua脚本
Lua调用C#的类

xLua提供的Lua中使用C#的类的固定套路 : CS.命名空间.类名

实例化C#的类(非mono脚本),Lua中无new方法,可以直接使用 CS.命名空间.类名()

脚本一般不能直接new来实例化,需要使用GameObject对象提供的AddComponent方法

一般采用全局变量存储需要使用的C#中的类型,再次调用时直接调用变量即可(相当于取了别名)

--Lua调用C#固定套路 CS.命名空间.类名
local obj1 = CS.UnityEngine.GameObject()  --实例化一个GameObejct对象
GameObject = CS.UnityEngine.GameObject    --定义全局变量存储类,别名
local obj2 = GameObject("mrLiu")          --利用别名实例化并进行传参

--对于类中的静态对象,可以直接使用.来调用
local obj3 = GameObject.Find("mrLiu");
--获取实例对象中的成员变量 直接.调用
print(obj3.transform.position)

Vector3 = CS.UnityEngine.Vector3          --存储类,起别名
--使用实例对象中的成员方法 一定要用冒号:调用!  否则报错
obj3.tranform:Translate(Vector3(5,2,1))

--使用自定义的C#类 注意命名空间要匹配(已定义了名为Test的C#类 无命名空间)
local test = CS.Test()
--特殊情况,继承了MonoBehaviour的脚本类
local obj4 = GameObject("加脚本测试")
--Lua不支持泛型,利用AddComponent的重载,在参数中提供类型 typeof是xLua提供的获取C#类型的方法
obj4:AddComponent(typeof(CS.Test)) --注意成员方法用冒号调用

注意点:成员方法的调用必须用冒号!

Lua调用C#枚举

枚举的调用和类相似:CS.命名空间.枚举名

枚举无需实例化,不存在枚举名()的用法 仍存在取别名的用法

--枚举调用

--调用Unity当中的枚举
--枚举的调用规则和类的调用规则是一样的
--CS.命名空间.枚举名   不存在实例化的小区别

--U3D自带的一些枚举类型 PrimitiveType 几何体的一些类型
--创建特定形状的几何体,GameObject.CreatePrimitive(PrimitiveType type)
--也支持取别名
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject
--静态成员函数可用.调用
local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)
--自定义枚举 使用方法一样注意命名空间
MyEnum = CS.MyEnum
--枚举的转换

--数值转枚举
local a = MyEnum.__CastFrom(1)
print(a)
--字符串转枚举
local b = MyEnum.__CastFrom("Attack")
print(b)
Lua调用C#的数组

重要性质:Lua中调用C#的相关数据结构时,仍使用其原有的数据结构性质方法等

原因:xLua调用C#时,本质是使用自定义数据类型userdata和元表等以达到存储C#中各种对象的实例成员,静态成员以及类型对象中包含的各种类型信息的目的

--Lua使用C#数组的相关知识点
--Lua中调用其并不改变原有语言中的结构 仍用原来语言方法进行使用
local obj = CS.LuaCallArray() --测试用C#类,里面已声明了相关数组

--长度 不能使用# 应该用原数组对象提供的Length属性
print(obj.arr.Length)

--访问元素 索引仍从0开始
print(obj.arr[0])
--遍历需要注意从0开始,Lua中遍历变量范围双闭合,所以长度需要减一
for i = 0,obj.arr.Length-1 do
	print(obj.arr[i])
end

--Lua中无法通过C#的关键字定义相关数据结构,但可以使用更通用的相关方法实现
--Lua中创建一个C#的数组 利用Array类
--Array.CreateInstance(Type elementType,int length) 静态方法
local arr2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32),10)
print(arr2.Length)
print(arr2[0])
print(arr2)

注意循环遍历时和Lua表的区别

Lua调用二维数组问题(坑)

Lua不支持使用[x,y][x][y]的方式访问二维数组

必须使用Array下实现的相关方法进行访问

  • arr.GetValue(x,y); 访问元素
  • arr.GetLength(0); 获取行数
  • arr.GetLength(1); 获取列数

其余遍历等相关操作根据这些API即可正常实现

Lua调用C#的List

获取规则性质和数组基本一致 提供的相关方法有所区别

创建的区别较大,老版本和新版本相关方法都会介绍

--规则遵循C#
obj.list:Add(1)
obj.list:Add(2)
print(obj.list.Count) --利用Count获取长度
--遍历
for i=0,obj.list.Count-1 do
	print("list",obj.list[i])
end
print(obj.list)
--在Lua中创建List对象
--老版本xLua
local list2 = CS.System.Collections.Generic["List`1[System.String]"]()
list2:Add("123")
print(list2[0])
--新版本xLua > v2.1.12
--相当于一个List泛型类 还需要实例化
local List_String = CS.System.Collections.Generic.List(CS.System.String)
local list3 = List_String() --创建对象
list3:Add("55555")
print(list3[0])
Lua调用C#的Dictionary

字典的调用和列表数组有所区别 无法通过[]中括号直接调用,需要用字典对象提供的相关方法

obj.dic:Add(1,"1234")
print(obj.dic[1])

--遍历
for k,v in pairs(obj.dic) do
	print(k,v)
end
--在Lua中创建一个字典对象
--获取别名 还需实例化
local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dic2 = Dic_String_Vector3()
dic2:Add("123",CS.UnityEngine.Vector3.right)
--字典的遍历必须用pairs 不然无法识别字符串索引
for k,v in pairs(dic2) do
	print(k,v)
end
--在Lua中创建的字典直接通过[]获得无法获取
print(dic2["123"]) --不支持
--C#函数原型TryGetValue利用out接收value,返回bool
--Lua中调用TryGetValue时没有out关键字,使用多返回值即可
--两个返回值 bool键是否存在  value键对应的值
print(dic2:TryGetValue("123"))
--xLua为Dictionary提供的拓展方法 get_Item和set_Item 根据键查找和修改值
--利用get_Item 要通过这个固定方法
print(dic2:get_Item("123")) --查找字典元素
--更改用set_Item
dic2:set_Item("123",nil)
print(dic2:get_Item("123")) --vector3不能空 则默认(0,0,0)

注意字典特殊的调用方法 和 遍历时必须使用pairs

Lua调用C#的拓展方法

使用拓展方法时,一定要在工具类前面加LuaCallCSharp特性再重新生成代码

建议Lua中使用的自定义类都加此特性,会显著提高性能

不加此特性采用的是反射机制调用,效率低下

//想要在Lua中使用拓展方法时 一定要在工具类前面加上特性
//建议 Lua中要使用的类 都加上该特性 可以提升性能
//不加该特性 可能不会报错 xLua采用的是反射机制 效率较低
[LuaCallCSharp]
public static class Tools
{
    //当指定对象调用此方法 相当于将自身传入
    public static void Move(this Test obj)
    {
        Debug.Log(obj.name + "移动");
    }
}
public class Test
{
    public void Speak(string str)
    {
        print(str);
    }
}
--拓展类无需在lua中加载 只需添加特性即可
obj = Test()
obj:Speak("mrLiu")
--使用拓展方法和使用成员方法一致 将自身传入
--调用C#中某个类的拓展方法,一定要在拓展方法的类上加特性
obj:Move()
Lua调用含ref和out参数的函数

针对函数多返回值的用法:Lua函数直接返回多个值 而 C#则只能返回一个,其余均通过ref和out传递引用,在函数外面接收值,注意顺序要一一对应。

public class Test
{
    public int RefFunc(int a, int b ,ref int c, ref int d )
    {
        c = a + b;
        d = a - b;
        return 100;
    }

    public int OutFunc(int a, int b, out int c, out int d)
    {
        c = a;
        d = b;
        return 200;
    }

    public int RefOutFunc(int a , out int b, ref int c)
    {
        b = a * 10;
        c = a * 20;
        return 300;
    }
}
print("*******Lua调用C# ref方法相关知识点**********")
Lesson5 = CS.Test

local obj = Test()

--ref参数 会以多返回值的形式返回给lua
--如果函数存在返回值则为第一个 其余ref从左到右一一对应
local raw,ref1,ref2 = obj:RefFunc(1,1,0,0)
--RefFunc(1,1) 不给ref传值会传入当前类型的默认值数字即为0
--注意: 此时ref参数位于最后可省略,当位于中间时需要进行占位不得省略
print(raw,ref1,ref2)


print("*******Lua调用C#out方法相关知识点**********")
--out参数 会以多返回值的形式返回给lua
--out参数不需要传值 out可以省略 且无需占位
local raw,out1,out2 = obj:OutFunc(1,1)
print(raw , out1 , out2)

print("*******Lua调用C#ref out方法相关知识点**********")
--当混合使用时 需综合上述两个规则 ref需占位 out无需占位
local raw,out1,out2 = obj:RefOutFunc(20,30) --out省略 30直接传给ref
print(raw , out1 , out2)
Lua调用重载函数
public class Test
{
    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;
    }
}
local obj = CS.Test()
--Lua自身不支持定义重载函数
--Lua支持调用C#中的重载函数
print(obj:Calc())
print(obj:Calc(10,5))

--Lua中数值类型只有Number
--C#中有各种精度的重载函数 无法分清楚
--对数据类型的重载 使用时可能出现问题
print(obj:Calc(10))
print(obj:Calc(10.2))

--解决重载函数含糊问题
--XLua提供反射机制解决
--只做了解 尽量不用 效率低下
--Type是反射的关键类
--得到指定函数的相关信息
local fun_int = typeof(CS.Lesson6):GetMethod("Calc",{typeof(CS.System.Int32)})
local fun_float = typeof(CS.Lesson6):GetMethod("Calc",{typeof(CS.System.Single)})
--通过xlua提供的一个方法 转成lua函数使用
--一般只转一次 然后重复使用 声明函数
local f1 = xlua.tofunction(fun_int)
local f2 = xlua.tofunction(fun_float)

--成员方法 第一个参数传对象
--静态方法 无需传第一个参数
print(f1(obj,10))
print(f2(obj,10.2))
Lua调用委托和事件
public class Test
{
    //声明委托和事件
    public UnityAction del; //delagate
    //事件
    public event UnityAction eventAction;
    //触发事件
    public void DoEvent()
    {
        if (eventAction != null)
            eventAction();
    }
    //事件不允许外部直接操作,需要包装方法
    public void ClearEvent()
    {
        eventAction = null;
    }
}
local obj = CS.Test()
--委托是用来封装函数的
--使用C#中的委托 可以用来装Lua函数
local fun = function()
	print("Lua函数fun")
end
--lua中没有符合运算符 不能+=添加委托
--如果第一次往委托中加函数 应该用 = 后续+=
obj.del = fun
obj.del = obj.del + fun
obj.del()
obj.del = obj.del - fun
print("开始减函数")
obj.del()
--清空所有存储的函数 委托支持 =
obj.del = nil


print("*******Lua调用C#事件相关知识点**********")
local fun2 = function()
	print("事件加的函数")
end
--事件加减函数 和 委托非常不一样
--lua中使用事件时 用lua提供的固定方法
--对象:事件名("+",函数变量)
obj:eventAction("+",fun2)
obj:eventAction("+",fun2)
obj:DoEvent()
--注销事件
obj:eventAction("-",fun2)
obj:DoEvent()
--清楚事件 不允许直接在事件外部进行=等操作 只能+= -=
--需要在C#中封装一个函数进行这个操作
obj:ClearEvent()
obj:DoEvent()

注意事件的注册和注销与C#中事件极大的不同

Lua调用特殊的系统类型

​ 当使用系统自带的一些特殊类型 例如Unity提供的UnityAction<float>委托等等,xLua已经将常用的都预先准备好了,但如果泛型参数等很复杂,没有预先准备,则必须在xLua中进行注册,传统类型直接加特性即可,但系统提供的委托无法修改源码直接加特性,则需要其它方法。

  • C# Call Lua时必须注册的地方
    1. 没有预定义的委托(自定义的可以加特性,系统的委托只能注册)
    2. 接口必须加特性(自定义接口加特性,系统接口只能注册)
//必须是静态类  类名和类中变量名不加以要求
public static class Test
{
    //为C#调用Lua时的系统类型进行注册(委托,接口)
    [CSharpCallLua]
    public static List<Type> cSharpCallLuaList = new List<Type>()
    {
      typeof(UnityAction<float>),
    };
    //为Lua调用C#的系统类型进行注册(拓展方法) 为提高性能建议使用到的都进行注册或加特性
    [LuaCallCSharp]
    public static List<Type> luaCallCSharpList = new List<Type>()
    {
        typeof(GameObject),
        typeof(Rigibody),
    }
}

注册的优势:不会受到系统类型无法加特性的约束,将所有特性汇总在一处,不重不漏,比单独为类添加特性,结构更清晰。

Lua的nil和C#的null

​ Lua的nil和C#的null不等价,若直接判断某个C#对象 obj == nilobj为null 返回的却是false,证明null == nil 是错的。以下是三种解决方案:

  1. 利用C#对象提供的Equals方法 obj:Equals(nil) --最直接,但有风险,若是Lua的数据类型则不会提供此方法,会报错

  2. 在Lua中封装一个全局方法进行判空

    function IsNull(obj)
        if obj == nil or obj:Equals(nil) then
            return true
        end
        return false
    end
    
  3. 为Object类型拓展一个方法

    //切记拓展方法要加特性
    [LuaCallCSharp]
    public static class ExtendFunc
    {
        //拓展一个为Object判空的方法 用于给lua使用 lua无法用null和nil比较
        public static bool IsNull(this Object obj)
        {
            return obj == null;
        }
    }
    
Lua调用C#的协程

​ Lua本身就提供协程的相关调用,但在xLua中更希望实现的是用Lua语言调用C#的相关API,所以这里要开启的是Unity脚本的协程,不能用自身的,即将Lua函数传入到Mono提供的协程中去。

--xlua提供的一个工具表
--一定通过require调用之后才能用
util = require("xlua.util")

--C# 中协程启动都是通过继承了Mono的类
--通过里面的启动函数StartCoroutine开启协程
GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds

--在场景中新建一个空物体,然后挂一个脚本,脚本只继承了mono
local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.Test))

--希望用协程开启Lua中函数
fun = function()
	local a = 1
	while true do
		--lua中不能直接使用C#中的yield关键字
		--就是用Lua中的协程返回
		coroutine.yield(WaitForSeconds(1))
		print(a)
		a = a + 1
		if a > 10 then
			mono:StopCoroutine(running)
		end
	end
end
--我们不能直接将 lua函数传入到开启协程中!!
--mono:StartCoroutine(fun)
--利用util工具表中提供的解决方法
--如果要把Lua函数当作C#协程函数使用
--必须要调用xlua.util中的cs_generator(lua函数)
running = mono:StartCoroutine(util.cs_generator(fun))

--关闭协程
Lua调用C#的泛型函数

Lua只支持调用约束为Class的且带有参数的c#函数

即使如此仍有一定的使用限制 在不同打包方式下不同 一定慎用

  • 支持Mono打包使用
  • 若是IL2Cpp打包,如果泛型是引用类型才能使用。如果是值类型,除非C#已经调用过了同泛型参数,Lua中才可使用。
public class Test
{
    public interface ITest
    {

    }
    public class TestFather
    {

    }
    public class TestChild : TestFather,ITest
    {

    }
    public void TestFun1<T>(T a, T b) where T:TestFather
    {
        Debug.Log("有参数有约束的泛型方法");
    }

    public void TestFun2<T>(T a)
    {
        Debug.Log("有参数无约束的泛型方法");
    }

    public void TestFun3<T>() where T:TestFather
    {
        Debug.Log("有约束无参数的泛型方法");
    }

    public void TestFun4<T>(T a)where T:ITest
    {
        Debug.Log("有约束有参数,约束是接口");
    }
}
local obj = CS.Test()
local child = CS.Lesson12.TestChild()
local father = CS.Lesson12.TestFather()

--Lua 支持有约束有参数的泛型函数
obj:TestFun1(child,father)
obj:TestFun1(father,child)
--Lua 不支持没有约束的泛型函数
--obj:TestFun2(child)
--Lua 不支持有约束没参数的泛型函数
--obj:TestFun3()
--Lua 不支持约束为非Class的函数
--obj:TestFun4(child)

--总结,Lua只支持约束为Class且带有参数的函数

--有一定的使用限制
--如果使用Mono打包支持使用
--如果是IL2Cpp 如果泛型是引用类型才可以使用
--如果是值类型,除非C#那边已经调用过了同泛型参数,lua中才能够使用

可以看出直接使用泛型时有诸多的不便,但xLua提供了通用函数的方法实现泛型函数的调用

--得到通用函数
--设置泛型类型再使用
--xlua.get_generic_method(类,"函数名")
local testFun2 = xlua.get_generic_method(CS.Lesson12,"TestFun2")
testFun2_R = testFun2(CS.System.Int32) --设置泛型类型
--函数的调用
--成员方法 第一个参数传调用函数的对象
--静态方法不用传
--第二个参数为泛型的值
testFun2_R(obj,1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值