【Unity】xLua and hot updates

1 Introduction

        This article mainly talks about the basic use of xLua. After reading it, you can have a basic understanding. Simple hot-changing operations can still be done, but more details still need to be understood and learned from the official documents.

2x Lua

2.1 What is xLua

        xLua is an open source project maintained by Tencent. We can also think of it as a plug-in. xLua adds Lua script programming capabilities to C# environments such as Unity, .Net, Mono, etc. With xLua, these Lua codes can easily call each other with C#. Usually used as a hot update solution for Unity.

2.2 xLua installation

        You need to download it from github first, address: https://github.com/Tencent/xLua . After entering the website, follow the steps below to download (don’t ask, just ask to download the latest one).

After the download is complete, open the downloaded compressed package and copy the contents of the Assets folder to Unity's Assets. Contents in the compressed package Assets:

Copy to the directory under Unity Assets (as long as it is under Assets, I have two layers of folders here):

PS: The downloaded compressed package also contains tutorial documents, cases, etc. You can follow that document to learn. This chapter is basically based on that document. xLua-master.zip\xLua-master\Assets\XLua\Doc.

2.3 Simple case

        In a C# script, we can execute lua code through a lua virtual machine or C# code.

Code:

using UnityEngine;
using XLua;//xLua头文件

public class xLuaTest01 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("print('lua代码内容。')");
        //调用C#代码(注意C#代码需要前加CS.)
        luaenv.DoString("CS.UnityEngine.Debug.Log('C#代码内容。')");

        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

2.4 Reading external lua files

2.4.1 The first method

        Store the lua file as a text file, then read the content and execute it. First create a .lua file in the Resources folder and write the code, but finally change the suffix to .txt. The code and file are as follows:

print("I belong to lua script.");

Changed to .txt for subsequent reading of text content. Read and execute the code as follows:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();
        
        //读取lua文本文件
        TextAsset ta = Resources.Load<TextAsset>("luaContent01.lua");
        //调用Lua代码
        luaenv.DoString(ta.text);
        
        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

2.4.2 The second method

        Use the require function that comes with LuaEnv. For example: DoString("require 'byfile'").

Code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'luaContent01'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

The result is the same:

        require actually adjusts loaders one by one to load. If one succeeds, it will not try further. If all fail, it will report that the file cannot be found.
        At present, in addition to the native loader, xLua also adds a loader loaded from Resource. It should be noted that because Resource only supports limited suffixes, Lua files placed under Resources must be added with the txt suffix (as mentioned in 2.4.1).
        The recommended way to load a Lua script is: the entire program has only one DoString("require 'main'"), and then load other scripts in main.lua (similar to the command line execution of lua scripts: lua main.lua). In other words, we only call the content of one .lua file, and all subsequent content has been arranged in this .lua file.
        But what should I do if the Lua file needs to be downloaded (needs to be downloaded), or decompressed in a custom file format (needs to be decompressed), or needs to be decrypted (needs to be decrypted), etc.? Can I still use require? The answer is: Yes, just call require directly and leave these tasks to the Loader. At this time, you need to understand xLua's custom Loader.
        (These processing tasks must not be run away. The question is where to process them. Without using require, we can also write a panacea method to read the file, obtain the text content, and then DoString.)

2.5 Custom Loader

        The essence of a custom loader is to add a delegate. The delegates and interfaces involved are as follows:

public delegate byte[] CustomLoader(ref string filepath);//委托
public void LuaEnv.AddLoader(CustomLoader loader)//添加委托的方法

        You can register a callback function through AddLoader. The callback function has a string parameter. When require is called in the Lua code, the parameters will be transparently passed to the callback. The callback can load the specified file based on this parameter. If you need to support debugging, Filepath needs to be modified to the real path and sent out. The return value of this callback is a byte array. If it is empty, it means that the loader cannot be found. Otherwise, it is the content of the Lua file, that is, the content to be executed.

Case code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //添加自定义loader
        luaenv.AddLoader(MyLoader);

        //调用Lua代码
        luaenv.DoString("require 'luaContent01'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //销毁LuaEnv对象
        luaenv.Dispose();
    }

    //自定义loader
    private byte[] MyLoader(ref string filePath)
    {
        //定义一段Lua代码(也可以根据上面的filePath读取)
        string content = "print('myLoader')";
        //返回此代码
        return System.Text.Encoding.UTF8.GetBytes(content);
    }
}

result:

It can be seen that we did not execute the lua code in luaContent01.lua.txt in the end, but executed the lua code we defined. Because after we register the custom loader, our custom loader is first used when requiring. After successfully obtaining the returned content, other loaders will not be executed, so the Lua code we defined is ultimately executed. Of course, if we return null in the custom loader and the acquisition fails, then the built-in loader will be executed to read the lua code in luaContent01.lua.txt and finally executed. I will not try it here.

2.6 C# accesses Lua’s data structure

        It mainly involves obtaining global variables, tables, and global functions. The three can be obtained by accessing LuaEnv.Global, which has a generic Get method that can specify the return type.

2.6.1 Get global variables

        First create a CSharpCallLua.lua.txt file in the Resources folder. The file content is as follows:

a = 149
b = "shqz"
c = true

Then the C# code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'CSharpCallLua'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //“要先执行上面的DoString执行Lua代码,后续才能获取lua代码中的数据结构”

        //获取全局变量
        int aa = luaenv.Global.Get<int>("a");
        string bb = luaenv.Global.Get<string>("b");
        bool cc = luaenv.Global.Get<bool>("c");
        Debug.Log("aa:" + aa + "   bb:" + bb + "   cc:" + cc);

        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

2.6.2 Get Table

        There are many ways to obtain the table, there are mainly four types:

  1. Map to ordinary class or struct (by value: value copy mapping)
  2. Map to interface (by ref: reference mapping)
  3. Map to Dictionary<>, List<> (more lightweight by value method)
  4. Map to LuaTable class (another by ref method)

        First create a .lua file, don’t forget to add the .txt suffix. The file created here is CSharpCallLua.lua.txt. The file content is as follows:

--全局变量
a = 149
b = "shqz"
c = true

--Table
Map = {
--table的变量
areaName = "mdsl",
areaNumber = 2,
"test1",
"test2",
123,
231,
--table的方法
ff = function(self,a,b)--这里需要加第一个参数,指表本身,用.的形式也要加,用:的形式可以省略,这是lua语法的内容。例子在下面。
print(a+b)
end
}

--其他的“table方法定义方式”
--[[
function Map:ff(a,b)--默认自带一个self参数,表示当前表
print(a+b)
end
--]]

--[[
function Map.ff(self,a,b)
print(a+b)
end
--]]

--既然说的第一参数问题,那也说说在lua中调用函数时也要面临的这个问题
--在调用table中方法时:
--若是 = 创建,则需要以table.形式调用,且需加第一个参数
--若是 . 创建,则需要以table.形式调用,且需加第一个参数
--若是 : 创建,则需要以table:形式调用,且可不加第一个参数
--在调用C#中的成员方法时:
--以 . 形式,需加第一个参数。以 : 形式,可不加第一个参数。(这个后面讲到lua调用C#也会再提)
2.6.2.1 Mapping to class or struct

        Define a class that has public attributes corresponding to the fields of the table and a parameterless constructor. For example, for {f1 = 100, f2 = 100}, you can define a class containing public int f1; public int f2;.
        In this way, xLua will create a new instance for you and assign the corresponding fields to it.
        The attributes of a table can be more or less than those of a class. Other complex types can be nested.
        It should be noted that this process is value copying, and if the class is more complex, the cost will be higher. Moreover, modifying the field values ​​of the class will not be synchronized to the table, nor vice versa.
        This feature can reduce overhead by adding types to GCOptimize generation. For details, see the configuration introduction document (together with the tutorial document).

Code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'CSharpCallLua'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //“要先执行上面的DoString执行Lua代码,后续才能获取lua代码中的数据结构”

        //获取表格,映射为class
        Map map = luaenv.Global.Get<Map>("Map");
        Debug.Log("class:   " + "name = " + map.areaName + "   number = " + map.areaNumber);

        //销毁LuaEnv对象
        luaenv.Dispose();
    }

    //映射用类
    class Map
    {
        public string areaName;
        public int areaNumber;
    }
}

result:

2.6.2.2 Mapping to interface

        This method relies on generating code (if no code is generated, an InvalidCastException will be thrown) (the interface is marked with [CSharpCallLua], which will be shown in the following code examples). The code generator will generate an instance of this interface. If you get an attribute, the code will be generated. The corresponding table field will be obtained. If the set attribute is set, the corresponding field will also be set. That is, the data of the interface and the table are in a reference relationship, and modifications will affect each other. In addition, you can access functions in Lua table through the interface method, that is, the mapping between functions.

Code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'CSharpCallLua'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //“要先执行上面的DoString执行Lua代码,后续才能获取lua代码中的数据结构”

        //获取表格,inerface
        IMap imap = luaenv.Global.Get<IMap>("Map");
        Debug.Log("inerface:   " + "name = " + imap.areaName + "   number = " + imap.areaNumber);
        imap.ff(1, 2);//方法调用

        //销毁LuaEnv对象
        luaenv.Dispose();
    }

    //映射用接口(我这里接口还需要加public)
    [CSharpCallLua]//需加此标签
    public interface IMap
    {
        string areaName { get; set; }
        int areaNumber { get; set; }
        void ff(int a, int b);
    }
}

result:

2.6.2.3 Mapping to Dictionary<>, List<>

        If you don't want to define a class or interface, you can consider using this, provided that the key types and value types in the table are consistent.
        Note that Dictionary<> and List<> are generic. The type parameters passed will determine the data stored in dic and list. Contents with mismatched types will not be obtained.

Code:

using System.Collections.Generic;
using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'CSharpCallLua'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //“要先执行上面的DoString执行Lua代码,后续才能获取lua代码中的数据结构”

        //获取表格,Dictionary<>,List<>
        Dictionary<string, int> dic = luaenv.Global.Get<Dictionary<string, int>>("Map");
        foreach (var key in dic.Keys)
        {
            Debug.Log("key: " + key + ", value: " + dic[key]);//只输出可key为string类型,value为int类型的内容
        }
        List<int> list = luaenv.Global.Get<List<int>>("Map");
        foreach (var data in list)
        {
            Debug.Log("list value: " + data);//只输出了无key的int类型的内容
        }

        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

2.6.2.4 Mapping to LuaTable class

        The advantage of this method is that it does not require code generation, but it also has some problems, such as slowness. It is an order of magnitude slower than "mapping to interface", such as no type checking.

Code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'CSharpCallLua'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //“要先执行上面的DoString执行Lua代码,后续才能获取lua代码中的数据结构”

        //获取表格,LuaTable
        LuaTable tab = luaenv.Global.Get<LuaTable>("Map");
        Debug.Log("LuaTable.areaName = " + tab.Get<string>("areaName"));

        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

2.6.3 Get global function

        There are two ways to obtain it:

  1. map to delegate
  2. Map to LuaFunction

        Modify the contents of the CSharpCallLua.lua.txt file and add a global function. Here I delete the previous one directly, leaving only one global function:

--全局函数
function FGlobal(p)
print("global function!")
return p,p*p
end
2.6.3.1 Mapping to delegate

        This is the recommended method, which has much better performance and is type safe. The disadvantage is that code needs to be generated (if no code is generated, an InvalidCastException will be thrown) (the delegate is marked with [CSharpCallLua]).
         For each parameter of the function, declare an input type parameter.
        How to deal with multiple return values? Mapping from left to right to the output parameters of C#, the output parameters include return values, out parameters, and ref parameters (there are examples in the code).
        What parameter and return value types are supported? All are supported, various complex types, modified by out and ref, and can even return another delegate.
        The use of delegate is even simpler, just use it like a function.
        The code comments also mention some other things to pay attention to.

Code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    private LuaEnv luaenv;

    //创建委托类型
    //注意此委托类型参数,有一个out参数,这是为了接受Lua函数的多值返回。
    //若Lua函数返回多个值,比如3个,函数本身返回一个,那另外两个呢?就需要在委托中定义额外的两个out参数来接收。
    //接收顺序:函数返回、out参数顺序。
    //实际除了out,也可以使用ref。
    //具体的使用例子看下面代码。
    [CSharpCallLua]//加标识!
    private delegate int myAction(int p,out int extraReturnParam);
    //创建委托对象
    myAction act;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'CSharpCallLua'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //“要先执行上面的DoString执行Lua代码,后续才能获取lua代码中的数据结构”

        //获取全局函数,使用委托类型
        act = luaenv.Global.Get<myAction>("FGlobal");
        //执行函数
        int extraReturnP;
        int num = act(10, out extraReturnP);
        Debug.Log("获取的返回值是:" + num + ",额外返回值:" + extraReturnP);

        //act是全局变量,所以这类需要置为null,释放对函数的索引,这样luaenv虚拟机才能在OnDestroy中正确销毁(Dispose)
        //若act是局部变量则不用置为null,因为离开此“代码块”自己就会被销毁了。
        act = null;
    }

    //这里销毁虚拟机的代码移动到这里是因为不能与调用全局函数的索引在一个“代码块”内,否则会销毁失败,报出异常。
    //这里实际上就是报对函数的引用没释放的异常,即是我们将对函数的引用变量置为null,但只要在一个“代码块”内,就还会报此错误。
    private void OnDestroy()
    {
        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

2.6.3.2 Mapping to LuaFunction

        The advantages and disadvantages of this method are exactly opposite to the first one.
        It is also simple to use. There is a variable-parameter Call function on LuaFunction, which can pass any type and any number of parameters. The return value is an array of objects, corresponding to Lua's multiple return values.

Code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    private LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'CSharpCallLua'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //“要先执行上面的DoString执行Lua代码,后续才能获取lua代码中的数据结构”

        //获取全局函数,LuaFunction
        LuaFunction act = luaenv.Global.Get<LuaFunction>("FGlobal");
        //执行函数
        object[] objs = act.Call(10);//数组接收多返回值
        foreach(object obj in objs)
        {
            Debug.Log("返回值:" + obj);
        }

        //销毁LuaEnv对象(没有索引烦恼,直接销毁!)
        luaenv.Dispose();
    }
}

result:

2.6.4 Usage recommendations

        Accessing Lua global data, especially tables and functions, is relatively expensive. It is recommended to do it as little as possible. For example, during initialization, obtain the Lua function to be called once (mapped to the delegate), save it, and then directly call the delegate. Table is similar.

        If the implementation parts on the Lua side are provided in the form of delegates and interfaces, the user can be completely decoupled from xLua: a special module is responsible for the initialization of xlua and the mapping of delegates and interfaces, and then sets these delegates and interfaces to the desired where they are used. (That is, the Lua code content is completely realized by the idea of ​​​​delegation and interface mapping. Then C# has a dedicated module to initialize, map, and maintain these delegations and interfaces. We then call the content we need through this module. Implementation structure: Use --module--xLua. Complete decoupling.)

2.7 Lua calls C#

        First create a LuaCallCSharp.lua.txt file, and you will write Lua code in it later.

2.7.1 new C# object

//在C#中new一个对象
var newGameObj = new UnityEngine.GameObject();
--在lua中new一个对象
local newGameObj = CS.UnityEngine.GameObject()

They are basically the same, but there are some differences:

  1. There is no new keyword in Lua;
  2. All C# related things are placed under CS, including constructors, static member properties, and methods;

What if there are multiple constructors? Don’t worry, xlua supports overloading. For example, if you want to call GameObject’s constructor with a string parameter, write this:

--调用重载
local newGameObj2 = CS.UnityEngine.GameObject('helloworld')

        Write in LuaCallCSharp.lua.txt:

--创建一个游戏对象,并命名为 GameObject, new in lua
CS.UnityEngine.GameObject("GameObject, new in lua")

C# code (execute the lua code. It is the previous code for reading the lua file, but the lua file name has been modified):

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    private LuaEnv luaenv;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'LuaCallCSharp'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

2.7.2 Access C# static properties and methods

--读静态属性
CS.UnityEngine.Time.deltaTime
--写静态属性
CS.UnityEngine.Time.timeScale = 0.5
--调用静态方法
CS.UnityEngine.GameObject.Find('helloworld')

Tip: If you need to access a class frequently, you can use local variables to reference it first and then access it. In addition to reducing the time of typing code, it can also improve performance. like:

--用一个变量GameObject引用到UnityEngine.GameObject类
local GameObject = CS.UnityEngine.GameObject
--用变量使用类
GameObject.Find('helloworld')

The case will not be demonstrated.

2.7.3 Access C# member attributes and methods

--testobj是我们假设创建的一个类对象

--读成员属性
testobj.DMF
--写成员属性
testobj.DMF = 1024
--调用成员方法(注意:调用成员方法,第一个参数需要传当前对象,这里对象指testobj,建议用冒号语法糖,如下)
testobj:DMFunc()--冒号语法糖,不需要传当前对象

The case will not be demonstrated.

2.7.4 Access C# parent class properties and methods

        xlua supports accessing static properties and static methods of base classes (through derived classes). Supports accessing member properties and member methods of the base class (through derived class instances).

2.7.5 Parameter input and output attributes (out, ref)

        Parameter processing rules on the Lua call side: The ordinary parameters of C# correspond to a Lua input parameter, the ref modified parameter corresponds to a Lua input parameter, out is not counted, and then the parameter lists of C# and Lua are matched one by one from left to right.

        Return value processing rules on the Lua call side: The return value of the C# function (if any) counts as a return value, out counts as a return value, ref counts as a return value, and then corresponds to Lua's multiple return values ​​from left to right (this As mentioned before).

        Let’s take a simple case to illustrate. Write in LuaCallCSharp.lua.txt:

--lua中的函数
function LuaFunc(l1,l2,l3)
print(l1,l2,l3)
return l1*l1,l2*l2,l3*l3
end

C# code:

using UnityEngine;
using XLua;

public class xLuaTest02 : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    private LuaEnv luaenv;

    //C#中定义一个委托来获取
    [CSharpCallLua]
    private delegate int myAction(int c1, ref int c2, out int c3, int c4);
    //创建委托对象
    myAction act;

    private void Start()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //调用Lua代码
        luaenv.DoString("require 'LuaCallCSharp'");//使用Loader加载(通常有多个Loader,加载时一个不成功会换下一个)

        //获取全局函数,使用委托类型
        act = luaenv.Global.Get<myAction>("LuaFunc");

        //调用方法
        int p = 2;
        int re1 = 0;
        int re2 = 0;
        re1 = act(1, ref p, out re2, 3);
        Debug.Log("返回值1:" + re1 + ",返回值2:" + p + ",返回值3:" + re2);

        //置空,为luaenv销毁做准备
        act = null;    
    }

    private void OnDestroy()
    {
        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

result:

First of all, what is the corresponding relationship mentioned in "Parameter processing rules on the Lua call side"? That is, the corresponding relationship between the parameter lists of Lua and C# functions in the code is reflected in the case:

  • l1 → c1
  • l2 → c2
  • l2 → c4

Did you see that there is no c3 here, because c3 is modified by out and does not count the parameter list correspondence. Therefore, the values ​​we pass in should be 1, p(2), 3, not 1, p(2), re2(0). The parameter values ​​printed by the Lua function in the figure are 1, 2, and 3. Okay, so what is the return value of the Lua function? It is the square of each parameter, which is 1, 4, 9. So let's start discussing "Return Value Processing Rules on the Lua Call Side", that is, the correspondence between return values ​​and parameters, as shown in the case:

  • l1*l1 → act return
  • l2*l2 → p
  • l3*l3 → re2

The first one is the function return value, followed by the parameters modified by ref and out. The modified parameters correspond to the return value one by one from left to right. This is their correspondence. In the figure, we output these parameters, and the results are 1, 4, and 9, which confirms this relationship. In fact, this return value relationship has been mentioned once before.

2.7.6 Overloaded methods

        Access overloaded functions directly through different parameter types, for example:

--TestFunc重载1调用
testobj:TestFunc(100)
--TestFunc重载2调用
testobj:TestFunc('hello')

TestFunc for integer parameters and TestFunc for string parameters will be accessed separately.

        Note: xlua only supports the calling of overloaded functions to a certain extent, because the types of Lua are far less rich than C#, and there are one-to-many situations. For example, C#'s int, float, and double all correspond to Lua's number. In the above example If TestFunc has these overloaded parameters, the first line will be indistinguishable and can only call one of them (the first one in the generated code).

2.7.7 Operators

        Supported operators are: +, -, *, /, ==, unary -, <, <=, %, [].

2.7.8 Methods with default values ​​for parameters

        Just like C# calling a function with default value parameters, if the actual parameters given are less than the formal parameters, the default values ​​will be used.

2.7.9 Variadic methods

//对于C#的如下方法
void VariableParamsFunc(int a, params string[] strs)
--可以在lua里头这样调用
testobj:VariableParamsFunc(5, 'hello', 'john')

2.7.10 Using Extension methods

        It is defined in C# and can be used directly in Lua.

2.7.11 Generalization (template) method

        It is not directly supported and can be encapsulated and called through the Extension methods function. (That is to encapsulate according to the required type, combine a type with an encapsulated function, and then call it as needed. At this layer used after encapsulation, we have lost the convenience of generics.) (General function encapsulation is also acceptable, Extension methods are not necessarily required.)

        How to call GetComponent method?

--没有泛型,改为这样:
GetComponent('组件名')

2.7.12 Enumerated types

        Enumeration values ​​are like static properties under the enumeration type.

--调用EnumTestFunc函数,参数是Tutorial.TestEnum枚举类型
testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1)

In addition, if the enumeration class is added to the generated code, the enumeration class will support the __CastFrom method, which can convert from an integer or string to an enumeration value, for example:

--将整型转换为枚举类型
CS.Tutorial.TestEnum.__CastFrom(1)
--将字符串转换为枚举类型
CS.Tutorial.TestEnum.__CastFrom('E1')

How to add enum types to generated code? Just add the [LuaCallCSharp] tag to the enumeration type. like:

    [LuaCallCSharp]
    enum MyEnum
    {
        one,
        two,
        three
    }

2.7.13 delegate usage (call, +, -)

        C#'s delegate call: the same as calling ordinary lua functions

  • + Operator: Corresponds to C#'s + operator, which strings two calls into a call chain. The right operand can be the same type of C# delegate or lua function.
  • -Operator: The opposite of +, removes a delegate from the call chain.

Ps: The delegate attribute can be assigned a value using a luafunction.

2.7.14 event

        For example, there is an event definition in testobj as follows: public event Action TestEvent; So how to add and remove callbacks in Lua?

--增加事件回调
testobj:TestEvent('+', lua_event_callback)--回调函数:lua_event_callback
--移除事件回调
testobj:TestEvent('-', lua_event_callback)

2.7.15 64-bit integer support

        The Lua53 version 64-bit integers (long, ulong) are mapped to native 64-bit integers, while the luajit version is equivalent to the lua5.1 standard and does not support 64-bit itself. xlua has made an extension library for 64-bit support, C#'s long and ulong will both be mapped to userdata:

  1. Supports 64-bit operations, comparison, and printing in Lua
  2. Supports operations and comparisons with Lua numbers
  3. It should be noted that in the 64 extension library, there is actually only int64, and ulong will be converted to long first and then passed to Lua. For some operations and comparisons of ulong, we adopt the same support method as Java and provide a set of API, please see the API documentation for details.

2.7.16 Automatic conversion of C# complex types and tables

        For a C# complex type with a parameterless constructor, it can be directly replaced by a table on the Lua side. The table can have corresponding fields corresponding to the public fields in the complex type. The table used for replacement supports passing as function parameters, attribute assignment, etc., for example:

//C#下B结构体(class也支持)定义如下
public struct A
{
    public int a;
}

public struct B
{
    public A b;
    public double c;
}

//某个类有成员函数如下
void Foo(B b)
--在lua可以这么调用
obj:Foo({b = {a = 100}, c = 200})

It can be seen that the formal parameter b of complex type B is directly replaced by a table. There is also a table in the table, corresponding to the public field b of type A in type B.

2.7.17 Get type (equivalent to typeof in C#)

--比如要获取UnityEngine.ParticleSystem类的Type信息,可以这样
typeof(CS.UnityEngine.ParticleSystem)

2.7.18 “Strong” transfer

        Lua has no types, so there will be no "force conversion" of strongly typed languages, but there is something similar: telling xlua to use the specified generated code to call an object. Under what circumstances can this be used? Sometimes a third-party library exposes an interface or abstract class, and the implementation class is hidden, so we cannot generate code for the implementation class. This implementation class will be recognized by xlua as ungenerated code and accessed using reflection. If this call is very frequent, it will still affect the performance. At this time, we can add this interface or abstract class to the generated code, and then specify the use This generates code to access:

--也可以简单理解为将calc转换为后面的类型
cast(calc, typeof(CS.Tutorial.Calc))

The above specifies using the generated code of CS.Tutorial.Calc to access the calc object.

3 hot updates

3.1 Configuration

3.1.1 xLua configuration

        The first is the configuration of xLua, which is the xLua installation mentioned above. Copy a few folders.

        Then you need to copy some new things here, namely the toolkit. There is a Tools file in the xLua compressed package, as shown in the figure:

We need to copy it to the project and put it in the same directory as Assets.

3.1.2 Macro configuration

        Configure macros in Unity settings. As shown in the picture:

After configuration, you can see the new function button in the menu bar (inject the hot update code into the editor). as follows:

3.1.3 dll file configuration

        You need to copy some .dll files in the Unity installation directory to the xLua file directory.
        The directory where the .dll file is located: "2022.3.0f1c1\Editor\Data\Managed" (2022.3.0f1c1 is the folder where the Unity I installed is located, and it was automatically generated during installation). The file is as shown:

Copy these three files to "XLua\Src\Editor".

3.2 Issues to note

3.2.1 Generate code

        Required before running! We need to generate the code first and then inject the code into the editor before it can be executed normally. When the C# code is modified, don't forget to regenerate the code. The operation is as shown in the figure:

PS: When generating code, I encountered some problems with unrecognized types. I went to github and took a look. "Fix: Fixed the problem that Span cannot be used as a generic parameter when generating code under Unity 2022. 2 days ago." It was just fixed two days ago. .....So many problems may be solved by changing to another version, or waiting for the big guys to fix them. However, the official also provides a lot of problem explanations, which are available on github. You can refer to the official explanations to correct errors.

PS: When generating, wait for the progress bar in the lower right corner to complete before operating.

3.2.2 Clear code

        That option under Generate Code Options. Before regenerating the code, you can clear it first (usually it is not cleared, but sometimes if something goes wrong and an error is reported, you can clear it and try it).

PS: When clearing, wait for the progress bar in the lower right corner to complete before operating.

3.2.3 Chinese path

        Don’t use Chinese paths. No matter what kind of development you do, you must develop the habit of not using Chinese paths.

3.2.4 Packaging projects

        When packaging the project, first delete all cases in xLua, otherwise an error will be reported and the package cannot be packaged.

3.2.5 Close macros during development phase

        It is recommended not to turn on HOTFIX_ENABLE when developing business code, but only turn on HOTFIX_ENABLE when building the mobile version or developing patches under the compiler.
        PS: My understanding is that it is not turned on for pure C# development, but it needs to be turned on if it involves Lua patch development. After all, if it is not turned on, the Lua code using hotfix will report an error. Then if there is no Lua patch, it must be opened during build. This should be to prepare for the development of subsequent Lua patches. After opening, subsequent updates can use Lua code to adjust the code logic with the help of hotfix.

3.3 Small case of function replacement

3.3.1 Basic

        The official case is a case of replacing functions, and it is re-examined here. First create a script and mount it into the scene. The code is as follows:

using UnityEngine;
using XLua;

[Hotfix]//热更新标签
public class xluaTest_Hot : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Awake()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();       
    }

    [LuaCallCSharp]//加此标签,因为lua代码调用了它
    private void Update()
    {
        Debug.Log("旧函数");
    }

    private void OnGUI()
    {
        //按钮
        if (GUI.Button(new Rect(10, 10, 300, 80), "Hotfix"))
        {
            //替换xluaTest_Hot脚本的Update方法替换为自定义方法,准确说是内容替换。
            luaenv.DoString(@"
                --xlua.hotfix来执行函数替换,参数:类名、被替换函数名、替换的新函数。self是类本身(这里指xluaTest_Hot),如this。如果函数有参数的话,就继续在self后面写就行。
                xlua.hotfix(CS.xluaTest_Hot,'Update',function(self)
                    local a = CS.UnityEngine.GameObject.Find('Main Camera')
                    CS.UnityEngine.Debug.Log(a.name)
                end)
            ");
        }
    }

    private void OnDestroy()
    {
        //销毁LuaEnv对象
        luaenv.Dispose();
    }
}

Then "Generate Code", "Insert Code", and then run. Click the button to find that the execution content of the Update function has changed.

        The core idea is to use the execution of xlua code to modify our code logic, such as function replacement here, replacing old logic with new logic. But you should pay attention to the execution position of xlua code and the execution order of logic. The xlua code is in a .txt file, so we can update the xlua file by updating the file to update the file content, update the code, and update the logic.

3.3.2 Improvements

        Sort out the code and change it to read the Lua code form in the .txt file:

myLua.lua.txt

--替换xluaTest_Hot脚本的Update方法替换为自定义方法,准确说是内容替换。
--xlua.hotfix来执行函数替换,参数:类名、被替换函数名、替换的新函数。self是类本身(这里指xluaTest_Hot),如this。如果函数有参数的话,就继续在self后面写就行。
xlua.hotfix(CS.xluaTest_Hot,'Update',function(self)
                    local a = CS.UnityEngine.GameObject.Find('Main Camera')
                    CS.UnityEngine.Debug.Log(a.name)
                end)

myLuaDestroy.lua.txt

--将函数替换为nil,解除引用。注意,这里函数只是还原为原函数了。
xlua.hotfix(CS.xluaTest_Hot,'Update',nil)

xluaTest_Hot.cs

using UnityEngine;
using XLua;
using System.IO;

[Hotfix]//热更新标签
public class xluaTest_Hot : MonoBehaviour
{
    //一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一
    LuaEnv luaenv;

    private void Awake()
    {
        //创建LuaEnv对象
        luaenv = new LuaEnv();

        //添加自定义loader
        luaenv.AddLoader(MyLoader);

        //读取myLua.lua.txt文件内容,并执行
        luaenv.DoString("require 'myLua'");
    }
    
    [LuaCallCSharp]//加此标签,因为lua代码调用了它
    private void Update()
    {
        Debug.Log("旧函数");
    }

    private void OnDisable()
    {
        //读取myLua.lua.txt文件内容,并执行。
        //执行的是清除引用操作,这样在OnDestroy里才能正常释放虚拟机,若不清除引用则会报错。至于是否放在OnDisable里还有待讨论,但肯定得在.Dispose前执行,且不能在一个函数体内。
        //替换函数实际是让C#委托指向Lua中的函数,委托上的函数就是我们要替换的新函数,委托不为空就会去执行委托函数,我们清除引用也只是将委托清空,还原为了未替换前的状态,即后续若再执行原函数,则会执行没替换前的内容。
        luaenv.DoString("require 'myLuaDestroy'");
    }

    private void OnDestroy()
    {
        //销毁LuaEnv对象
        luaenv.Dispose();
    }

    //自定义loader
    private byte[] MyLoader(ref string filePath)
    {
        //获取完整路径
        string path = @"E:\_U3D\Note_Projects\009_AssetBundle_xLua\009_AssetBundle_xLua\Assets\XluaScene\Resources\" + filePath + ".lua.txt";
        //读取文件内容,并转为字节数组返回(这里使用的是UTF8,要求.txt文件也是此编码格式,否则会报错,若是此问题则将.txt文件另存一下即可,在另存时设置UTF8格式)
        return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(path));
    }
}

3.4 Hot update development process

  1. The first is to develop business code.
  2. Put a [Hotfix] tag on all classes that may have problems, that is, mark the classes we want to hot update.
  3. Mark [LuaCallCSharp] on all C# methods involved in being called by lua.
  4. Mark [CSharpCallLua] on all C# methods that call lua.
  5. Packaged and released.
  6. In subsequent updates, when modifying the code (such as bugs and the like), you only need to update the lua file. When modifying resources (sounds, models, textures, pictures, UI, etc.), you only need to update the AB package (AssetBundle). Users only need to download the lua file and AB package.

3.5 Various functional syntaxes

3.5.1 Replacement function

        Look at 3.3 small case.

3.5.2 Release the reference to the lua function

        Look at 3.3 small case.

3.5.3 Access private members

        In Lua scripts, we generally can only access public members of C# class objects. If we want to access private programs, some processing is required.

--加上下面这句,即可访问xluaTest_Hot类的私有成员
xlua.private_accessible(CS.xluaTest_Hot)--私有访问!
xlua.hotfix(CS.xluaTest_Hot,'Update',function(self)
                    --处理内容,如self.私有成员
                end)

3.5.4 Replace only the function part

        It is said that only part of it is replaced. In fact, the original function is executed first, and then some modifications are made later as needed.

        First, we need a .lua file util.lua.txt, which is officially provided. We need to copy it to the same directory as our own .lua file (it is not necessary to put it in the same directory, but it will be more convenient when referencing it. Just You only need to quote the file name, or you can put it in another folder, but in this way, you need to add an additional folder path when referencing. Like in the code below, I created a file in the same directory as my .lua file lib folder, and then copy it into it, you need to add lib when referencing it). The directory where the util file is located: "XLua\Resources\xlua".

--获取util.lua.txt文件对象,这里lib是文件夹,与当前.lua文件同目录
local util = require 'lib/util'
--使用util的hotfix_ex函数来执行替换函数操作。原之前的区别在于,这个可以调用被替换函数本身
util.hotfix_ex(CS.xluaTest_Hot,'Update',function(self)
					--调用被替换函数本身
					self:Update()
					--执行其他操作
					local a = CS.UnityEngine.GameObject.Find('Main Camera')
					CS.UnityEngine.Debug.Log(a.name)
                end)

PS: How to release it when releasing the reference? Use util.hotfix_ex to release? No, continue to use xlua.hotfix.

3.5.5 Random number problem (type conversion in Lua)

--调用C#中的随机数函数
CS.UnityEngine.Random.Range(0,4)

--[[
存在问题:
因为lua中不区分int和float,但Range函数区分,这里会返回float类型。
若我们是使用一个C#中的int类型接收,或想要一个int类型,则需要执行强转操作,否则会给默认0值。
--]]

--使用C#中的向下取值来处理,转换为int类型
CS.UnityEngine.Mathf.Floor(CS.UnityEngine.Random.Range(0,4))

3.6 Download patches

        I won’t talk about the AB package. The previous AB package article has already explained how to download the new AB package.

        Then there is the download of the Lua file. It is very similar to the AB package, except that it is slightly different when using UnityWebRequest, and finally the lua file needs to be saved.

using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;


public class LoadFromWeb : MonoBehaviour
{
    void Start()
    {
        //开启协程,下载file.lua.txt文件
        StartCoroutine(DownloadLuaFile(@"http://ip地址/文件目录/file.lua.txt"));
    }

    IEnumerator DownloadLuaFile(string url)
    {
        //创建文件下载请求
        UnityWebRequest request = UnityWebRequest.Get(url);
        //开始请求下载并等待
        yield return request.SendWebRequest();
        //下载完成后,获取文件内容
        string str = request.downloadHandler.text;
        //保存文件。重写指定路径下file.lua.txt文件的内容(没有则创建),将内容存储进去。
        File.WriteAllText(@"xxx\x\x\xx\x\file.lua.txt", str);
    }
}

3.7 Identify the type to be hot updated (Hotfix tag)

        PS: This is the content of the document in the Xlua package that is pasted directly. It mainly talks about how to use the Hotfix tag. The document demonstration is placed directly in front of the class. This is actually not suitable. The official also provides another way, as follows.

Like other configurations, there are two ways

Method 1: Put the Hotfix tag directly in the class (not recommended, the example is just for convenience of demonstration);

! ! Note that method one is not supported in higher versions of unity.

Method 2: Configure a list in the static field or attribute of a static class. Properties can be used to implement more complex configurations, such as whitelisting based on Namespace.

! ! Note that higher versions of Unity need to put the configuration file in the Editor directory.

//如果涉及到Assembly-CSharp.dll之外的其它dll,如下代码需要放到Editor目录
public static class HotfixCfg
{
    [Hotfix]
    public static List<Type> by_field = new List<Type>()
    {
        typeof(HotFixSubClass),
        typeof(GenericClass<>),
    };

    [Hotfix]
    public static List<Type> by_property
    {
        get
        {
            return (from type in Assembly.Load("Assembly-CSharp").GetTypes()
                    where type.Namespace == "XXXX"
                    select type).ToList();
        }
    }
}

4 Conclusion

        This is almost the content of xLua. The basic content is basically covered. For more details, you have to read the official documentation. The documentation is in the downloaded Xlua package. I also mentioned where the path is before, and then on github There are also some instructions. If you want to understand everything thoroughly, you must read all these documentation and tutorials. Of course, you can also just mess it up and use it directly. If there is any problem, check it again.

Guess you like

Origin blog.csdn.net/Davidorzs/article/details/135167707