Summary of xLua hot update solution knowledge points

Summary of knowledge points of XLua hot update solution

foreword

​ Hot update is a commonly used solution to update the client online without repackaging and installing. It is generally divided into hot update of resources and hot update of code. There are many solutions for hot update of code. Among them, Tencent provides a xLuasolution commonly used in Lua hot update. This article only focuses on xLuaknowledge points. Readers who are interested in various hot updates can refer to my blog dedicated to explaining the basic functions and principles of various hot updates.

The nature of hot updates

  • Provide Unity with the ability to program Lua
  • Allow C# and Lua to call and access each other

Unity's native language is based on related APIs implemented by C#, but C# code needs to be compiled before it can be used, and there are many restrictions on using C# directly. Therefore, the solution of using the scripting language Lua that does not need to be compiled has become a more common solution for hot update. Lua itself does not support calling related APIs of C# and U3D. xLuaThe essence of the solution is to enable Lua to call related APIs of C# provided by U3D , so that when performing code hot update, it can be realized by replacing and updating Lua code.

C# Call Lua related knowledge points

Lua parser: LuaEnv

  • xLuaThe provided encapsulated C# object can be used to call Lua in C# by using the related API provided by it.

  • Essence: A virtual environment capable of running Lua is implemented in the C# operating environment, and various types of Lua are converted to C# types in the virtual environment, thereby providing C# with the ability to call and access Lua.

  • Provided related API and usage explanation

    //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);
    

    Points to note when using the API:

    LuaThe parser is loaded from Resources by default. The Resources directory under u3d cannot recognize the Lua script suffix , so you must add a suffix such as .txt to.lua the script to access and call . There is also this special case in the AB package, but it can be loaded directly under the custom folder without adding an additional suffix. At the same time, the redirection provided by the parser supports multiple

    Due to these circumstances, in order to facilitate development, under the daily compiler, choose to load from the custom folder without adding a suffix every time. After packaging and launching, it must be loaded by the AB package to achieve hot update. Then you can realize the small functions under the editor, and one-click all the Lua scripts under the custom folder into AB packages and add suffixes, and the author will implement them later.

  • LuaManager.csEncapsulate the API and implement the Lua manager (the use of the singleton mode and the AB package manager has been explained in detail in the blog of the AB package, and you can check it if you need to know)

    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# Gets the basic type of Lua

Use LuaEnvthe provided Global attribute to obtain and set global variables, and note that it cannot obtain Local local variables

Global related APIs and usage methods (Note: Use the Global attribute provided by the Lua manager above to call)

//设置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# obtains Lua's global function function

Two main ways to get Lua functions

  1. Acquisition by delegation
  2. Use xLuathe provided ones LuaTableto obtain (low efficiency is recommended to use the first one)
  • Get a function with no parameters and no return value

    //无参无返回值的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();
    
  • Get a function with parameters and a return value

    [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]); //返回的是数组取第一个
    
  • Acquisition of multi-return value functions ( note that custom delegates may not be predefined, you need to add features and regenerate code)

    //多返回值的委托定义 利用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]));
    
  • Acquisition of variable-length parameter functions

    //可变参数委托的定义  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# gets Lua's table Table

The table in Lua is very powerful, it can store any data type, and you can customize the index or automatically fill the index. Generally, C# obtains tables in Lua and can be converted into the following forms.

  • Use the List list to get the 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]);
    
  • Use Dictionary dictionary to get 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);
    }
    
  • Use the Class class to get Lua's table

    When the table acts as a class, the points that need to be paid attention to when the class is defined

    1. The name of the member variable must be consistent with the name of the element in the table in Lua
    2. The type must correspond, the basic type, the function type uses delegate or LuaFunction, and must be a public variable
    3. When the number of parameters does not match, the redundant variables in Lua will be discarded, and the redundant variables in C# will not be assigned.
    4. Table variables are allowed in the table, and class definitions can also be nested
    --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");
    
  • Use the interface Interface to get Lua's 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);
            }
        }
    }
    

    Note: the interface is a deep copy! It's a deep copy! It's a deep copy!

  • Use the xLuaprovided LuaTabletable to get Lua

     //不建议使用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(); //用完一定要销毁。
    

    Note: LuaTableit is a deep copy!

In C# Call Lua, only the interface sum LuaTableis a deep copy, and the rest are all shallow copies

Lua Call C# related knowledge points

​Usually when doing related logic development that requires hot updates, Lua is usually used to call C# and U3D related classes for development. Compared with C# calling Lua, Lua calling C# is more important.

Program main entry

​ Lua scripts cannot be started directly in Unity, and Lua needs to be called through C# to start Lua. Generally, a main entry to start Lua will be set. Main.luaIt is generally used to define some aliases and start other scripts.

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

Lua calls C# class

xLuaProvided fixed routines for classes using C# in Lua: CS.namespace.classname

Instantiate C# classes (non-mono scripts), there is no new method in Lua, you can directly use CS.Namespace.ClassName()

Scripts generally cannot be instantiated directly with new, and need to use the methods GameObjectprovided by the objectAddComponent

Generally, global variables are used to store the types in C# that need to be used, and the variables can be called directly when calling again (equivalent to taking an alias)

--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)) --注意成员方法用冒号调用

Note: the call of the member method must use a colon!

Lua calls C# enumeration

Enumeration calls are similar to classes: CS. Namespace. Enumeration name

Enumeration does not need to be instantiated, there is no usage of enumeration name () and there is still usage of aliasing

--枚举调用

--调用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 calls C#'s array

Important properties: When calling C# related data structures in Lua, the original data structure properties and methods are still used.

Reason: xLuaWhen calling C#, the essence is to use custom data types userdataand metatables to achieve the purpose of storing instance members, static members and various types of information contained in various objects in 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)

Pay attention to the difference between loop traversal and Lua table

Lua calls two-dimensional array problem (pit)

Lua does not support access to two-dimensional arrays using [x,y]or[x][y]

Must use related methods implemented under Array to access

  • arr.GetValue(x,y);access element
  • arr.GetLength(0); Get row count
  • arr.GetLength(1); Get the number of columns

Other related operations such as traversal can be implemented normally according to these APIs

Lua calls C#'s List

Obtaining the nature of the rules is basically the same as the array, and the related methods provided are different

There is a big difference in creation, and the related methods of the old version and the new version will be introduced

--规则遵循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 calls C# Dictionary

The call of the dictionary is different from the list array. It cannot be called directly through the [] square brackets. It needs to use the related methods provided by the dictionary object.

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)

Note that pairs must be used for special call methods and traversal of dictionaries

Lua calls the extension method of C#

When using the extension method, be sure to LuaCallCSharpadd features and then regenerate the code

It is recommended that all custom classes used in Lua add this feature, which will significantly improve performance

If this feature is not added, the reflection mechanism is used to call, which is inefficient

//想要在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 calls functions with ref and out parameters

For the usage of functions with multiple return values: Lua functions directly return multiple values, while C# can only return one value. The rest are passed by reference through ref and out, and the values ​​are received outside the function. Note that the order should be one-to-one.

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 calls overloaded functions

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 calls delegates and events

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()

Note that the registration and cancellation of events is very different from events in C#

Lua calls special system types

​ When using some special types that come with the system, such as the delegate provided by Unity, UnityAction<float>etc., xLuathe commonly used ones have been prepared in advance, but if the generic parameters are complicated and not prepared in advance, they must xLuabe registered in the traditional type You can directly add features, but the commission provided by the system cannot modify the source code and add features directly, so other methods are required.

  • Where C# Call Lua must be registered
    1. There are no predefined delegates (custom ones can add features, system delegates can only be registered)
    2. Interface must add features (custom interface plus features, system interface can only be registered)
//必须是静态类  类名和类中变量名不加以要求
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),
    }
}

Advantages of registration : It will not be constrained by the inability to add features to the system type, and all the features will be gathered in one place, without duplication or omission. Compared with adding features to classes separately, the structure is clearer.

Lua's nil and C#'s null

​ Lua's nil is not equivalent to C#'s null. If you directly judge a C# object obj == niland objreturn false if it is null, it proves that null == nil is wrong. Here are three solutions:

  1. Use the Equals method provided by the C# object obj:Equals(nil)- the most direct, but risky, if it is a Lua data type, this method will not be provided, and an error will be reported

  2. Encapsulate a global method in Lua for empty judgment

    function IsNull(obj)
        if obj == nil or obj:Equals(nil) then
            return true
        end
        return false
    end
    
  3. Extend a method for the Object type

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

Lua calls the coroutine of C#

​ Lua itself provides coroutine-related calls, but xLuawhat I hope to achieve in Lua is to use Lua language to call C#-related APIs, so here we need to open the coroutines of Unity scripts, which cannot be used by ourselves, that is, to pass Lua functions into Go to the coroutine provided by 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 calls C#'s generic function

Lua only supports calling c# functions that are constrained to Class and have parameters

Even so, there are still certain usage restrictions, which are different under different packaging methods. Be careful when using it.

  • Support Mono package use
  • If it is packaged with IL2Cpp, it can only be used if the generic type is a reference type. If it is a value type, it can only be used in Lua unless C# has called the same generic parameter.
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中才能够使用

It can be seen that there are many inconveniences when using generics directly, but xLuathe method of generic functions is provided to realize the call of generic functions

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

Guess you like

Origin blog.csdn.net/Q540670228/article/details/122883366
Recommended