XLua在Unity中的热更用法摘要

1.安装xLua与快速入门

1.1 下载xLua

在下载页面中下载所需资源,其中xlua_vx.x.x.zip和xlua_vx.x.x_luajit.zip是XLua框架的两个不同版本,二者互斥,必须二选一。
xlua_vx.x.x.zip - 【必须/二选一】用于Unity的xLua的Lua版本,其中x.x.x为版本号
xlua_vx.x.x_luajit.zip - 【必须/二选一】用于Unity的xLua的LuaJit版本,性能更好
xlua_vx.x.x_example.zip - 【非必须】用法示例
xlua_vx.x.x_tutorial.zip - 【非必须】官方教程的配套代码
xlua_vx.x.x_general.zip - 【非必须】xLua的通用版本,不局限于Unity

1.2 安装xLua

以xlua_vx.x.x.zip为例,解压xlua_vx.x.x.zip,将其中的Assets文件夹与希望使用xLua的Unity工程的Assets文件夹合并,不要更改Assets文件夹的目录结构。合并完成后,即可在代码中使用xLua。
如果要将xLua安装到其他目录,请参考FAQ

1.3实例化与释放LuaEnv对象

LuaEnv luavm = new LuaEnv();//实例化
luavm.Dispose();//释放

1.4在C#中执行Lua代码

通过LuaEnv.DoString(string)方法来在C#中执行Lua代码,代码的执行方式有两种。
第一种方式是直接通过参数传入Lua代码文本,但不建议使用这种方式。下面的示例中传入了一行Lua代码 print('hello world'),将会在Unity控制台打印hello world。

luavm.DoString("print('hello world')");

第二种方式是通过参数传递Lua代码文件名称(或位置),建议使用这种方式。下面的示例中,将会查找名为 hello_world.lua 的Lua脚本文件并执行该文件中的Lua代码。

luavm.DoString("require 'lua_script_file'");

建议的脚本加载方式是:整个程序中只有一处 DoString("require 'main'") ,然后在main.lua中加载其他的Lua脚本(类似于在Lua命令行执行 $ lua main.lua )。
指令 require 会依次调用不同的加载器去加载Lua文件,当某个加载器成功加载Lua文件后就不再调用其他加载器,如果所有加载器都没能加载到参数中指定地文件,则报告错误。xLua对Lua脚本文件的存放位置有要求,如果要加载自定义位置的、来自网络的、经过压缩或加密的Lua文件,则需要实现自定义加载器。下文中会介绍Lua文件存放位置和自定义加载器的相关内容。

1.5C#与Lua的相互调用

在C#中,使用 LuaEnv.Global.Get<T>("obj_name") 方法来获取名为obj_name的Lua全局对象,该对象可以是任意能够映射到Lua的C#类型;在Lua中,所有C#类都位于CS模块中,可以直接使用 CS.命名空间.类名 或 CS.命名空间.类名.字段/属性/方法 C#的类、字段、属性和方法。例如,在Lua中调用Unity的Debug.Log()方法打印hello world:luavm.DoString("CS.UnityEngine.Debug.Log('hello world')");。

1.6生成代码

通过Unity编辑器窗口的 XLua - Generate Code 选项可以生成用于实现C#和Lua交互的适配代码。生成代码后程序的性能更好,建议使用。如果没有生成代码,则xLua会使用反射进行交互,这种方式性能不高,但是能够减小安装包大小。
在Unity编辑器中,不生成代码也能够正常运行程序,建议在开发阶段不要生成代码。在打包手机版应用和做性能测试、调优前必须生成代码

实例代码:

public class Example : MonoBehaviour{ 
   private LuaEnv luavm;
    private void Start()    { 
       luavm = new LuaEnv(); 
       // 直接执行Lua代码       
 luavm.DoString("print('hello world')"); 
       // 查找Lua脚本文件并执行其中的代码 
       //luavm.DoString("require 'lua_script_file'");
    }
    private void OnDestroy()    { 
       // 记得要释放 
       if(luavm != null)  
      {            luavm.Dispose();        } 
   }}

1.7Lua脚本文件的加载位置

从上表可以看到xLua会在哪些文件夹中查找Lua脚本文件,需要将Lua脚本文件放在这些位置系统才能正确加载它们。除了在Unity安装文件夹中查找Lua脚本文件外,xLua还会在项目的下列位置查找Lua脚本文件:

  • 内置Lua库
  • 自定义加载器
  • Resources文件夹
  • 项目根目录(与Assets文件夹同级,打包后则与可执行exe文件同级,下同)
  • 项目根目录中同名文件夹中的init.lua
  • 项目根目录中的同名DLL文件
  • Assets/StreamingAssets文件夹

需要注意的地方是,Unity系统在打包应用时无法识别扩展名为lua的文件,所以当需要将Lua脚本文件作为TextAsset打包时(例如放到Resources文件夹中),应该将Lua脚本文件的扩展名改为txt,例如 my_script.lua.txt 。

如果需要将Lua脚本文件放置到自定义位置,或者加载网络文件、压缩文件或者加密文件,则需要实现自定义加载器。其中自定义Lua文件加载位置的功能也可以通过直接在Lua脚本中向 package.path 中添加路径名称来实现。例如,添加 /Assets/myluafiles/ 文件夹到加载路径

luavm.DoString (@"package.path = './Assets/myluafiles/?.lua;' .. package.path");

注意要对Lua文件名使用半角问号(?)通配符,多个路径之间使用半角分号(;)分隔,并且文件夹层级使用斜杠(/)而不是反斜杠(\)表示。

2.1 自定义加载器

实现自定义加载器只需要创建 CustomLoader 委托实例并通过 LuaEnv.AddLoader() 方法将其添加到LuaEnv实例中即可。

CustomLoader委托的签名为:

public delegate byte[] CustomLoader(ref string filepath);

示例代码:

public class CustomLoaderExample : MonoBehaviour{
    private void Start()    { 
       LuaEnv luavm = new LuaEnv();
        // 添加自定义加载器  
      luavm.AddLoader(MyCustomLoader); 
       string filepath = Application.dataPath + "/myluafiles/test.lua";  
      luavm.DoString(string.Format("require '{0}'", filepath)); 
       luavm.Dispose();    }  
  // 自定义的Lua文件加载器。
    // 参数filepath:【require 'filepath'】中的【filepath】    // 返回值:文件内容   
 private byte[] MyCustomLoader(ref string filepath)    {  
      // 通过自定义filepath的解析方式来实现特殊加载功能 
       // 1\. 从指定的路径加载Lua文件  
      if (filepath.Contains("/"))        {
            if (File.Exists(filepath)) 
           {                return File.ReadAllBytes(filepath);  
              //string script = File.ReadAllText(filepath);  
              //return System.Text.Encoding.UTF8.GetBytes(script); 
           }        
}        // 2\. 从自定义的默认位置加载Lua文件     
   else        {        
    string defaultFolder = Application.dataPath + "/myluafiles/";  
          string file = defaultFolder + filepath + ".lua"; 
           if (File.Exists(file))     
      {                return File.ReadAllBytes(file);            }  
      }       
 // 其他加载方式:        // 3\. 加载网络文件        // 4\. 加载压缩文件并解压        // 5\. 加载加密文件并解密       
 return null;    }}

3. 在C#中访问Lua数据结构

本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/CSharpCallLua/CSCallLua.cs中找到使用示例。

在C#中访问Lua数据结构的主要方法是 LuaEnv.Global.Get<T>(string name) ,该方法具有多个重载,各个重载的具体区别请查看xLua API文档。

3.1 访问全局的基本数据类型

luavm.Global.Get<int>("a");  // 访问名为a的整型变量luavm.Global.Get<bool>("b");  // 访问名为b的布尔变量luavm.Global.Get<string>("c");  // 访问名为c的字符串变量

3.2 访问全局的table

3.2.1将table映射到class或struct【值拷贝】

假设现在有如下的Lua数据结构:

my_table = {    f1 = 1,    f2 = 2,    f3 = 'string',    add = function(self, a, b)        return a + b    end}

要将上面的Lua数据结构映射到C#,需要在C#中定义一个class或struct,其中含有同名的public字段,并且具有无参构造方法。以class为例:

public class TableClass{    public int f1;    public int f2;}

table和class的成员个数不必完全相同,在映射过程中,table中多出的成员会被忽略,class中多出的成员会被初始化成默认值。在此示例中,忽略了字符串f3和函数add()。

在使用这种方式时,可以为C#类型添加 [GCOptimize] 特性来降低生成开销,具体说明请查看xLua配置文档。

需要注意的是,这一映射过程是值拷贝过程,对class对象的修改不会同步到table对象,反之亦然。

示例代码:

TableClass table = luavm.Global.Get<TableClass>("my_table");Debug.Log(table.f1 + table.f2);

3.2.2 将table映射到interface【引用形式】【建议用法】

将table映射到interface依赖代码生成,如果没有生成代码会抛出 InvalidCastException 异常。接口方式实现的是引用形式的映射,对class对象的修改会同步到table对象,反之亦然。建议使用该方式进行映射。仍然以上一节中的Lua数据结构为例,现在需要定义一个与其相匹配的C#接口,并为这个接口添加用于指明需要生成代码的特性标签 [CSharpCallLua] :

[CSharpCallLua]public interface ITable{    int f1 { get; set; }    int f2 { get; set; }    int add(int a, int b);}

示例代码:

ITable table = luavm.Global.Get<ITable>("my_table");Debug.Log(table.add(table.f1, table.f2));

3.2.3 将table映射到Dictionary<TKey, TValue>和List【值拷贝】

如果不想定义class/struct或interface,可以选择将table映射到Dictionary<TKey, TValue>或List这种更轻量级的方式。这种方式会选择table中能够匹配上的成员进行映射,并且采用了值拷贝形式。

仍然以第一节中的Lua数据结构为例,将其映射到Dictionary<TKey, TValue>和List的示例代码为:

// 映射到Dictionary<TKey, TValue>// 因类型不匹配,字符串f3和函数add()会被忽略Dictionary<string, int> tableDict = luavm.Global.Get<Dictionary<string, int>>("my_table");Debug.Log(tableDict["f1"] + tableDict["f2"]);// 映射到List<T>// 因类型不匹配,字符串f3和函数add()会被忽略List<int> tableList = luavm.Global.Get<List<double>>("my_table");for(int i = 0; i < tableList.Count; i++){    Debug.Log(tableList[i]);}

3.2.4 将table映射到LuaTable类【引用形式】

将table映射到LuaTable类的好处是不需要生成代码即可实现引用形式的映射,但其执行速度慢(比第2种方式要慢一个数量级),而且没有类型检查。

仍然以第一节中的Lua数据结构为例,将其映射到LuaTable的示例代码为:

LuaTable luaTable = luavm.Global.Get<LuaTable>("my_table");Debug.Log(luaTable.Get<int>("f1") + luaTable.Get<int>("f2") + luaTable.Get<string>("f3"));

3.3 访问全局的function

3.3.1 将function映射到delegate【建议用法】

将function映射到delegate是官方建议使用的方式。这种方式的好处是性能好,绑定一次即可重复使用,而且类型安全;其缺点是需要生成代码,如果没有生成代码,则会抛出 InvalidCastException 异常。

在声明delegate时,其访问权限应该是 public 的,delegate的每个普通参数和使用 ref 修饰的参数从左到右依次对应目标function的参数,out 修饰的参数不会被映射到目标function的参数中;delegate的返回值和使用 out 、 ref 修饰的参数从左到右依次对应function的(多个)返回值。参数和返回值支持各种基础类型和复杂类型。

假设现在有如下的Lua function:

function luafunc(a, b, c, d)    v3 = {x = a, y = b, z = c}    sum = a + b + c + d    pro = a * b * c * d    return v3, sum, proend

则可以将其映射到下面的C# delegate中。在下面的示例代码中,C# delegate的输入参数a、b、c分别对应Lua function的参数a、b、c,C# delegate的返回值和输出参数sum、pro分别对应Lua function 的3个返回值。C# delegate和Lua function的参数名称不必完全相同,也不限定输入输出参数的顺序,只要类型匹配即可。建议绑定一次重复使用,生成代码后,通过C# delegate调用Lua function不会产生gc alloc。

// 声明委托,输出参数不是必须排在最后

[CSharpCallLua]public delegate Vector3 LuaFuncDelegate(int a, int b, int c, out int sum, ref int pro);// 绑定LuaFuncDelegate luaFunc = luavm.Global.Get<LuaFuncDelegate>("luafunc");// 调用Vector3 v3;int sum, pro = 4;v3 = luaFunc(1, 2, 3, out sum, ref pro);Debug.Log(v3 + " " + sum + " " + pro);

如果在释放LuaEnv实例时报出

InvalidOperationException: try to dispose a LuaEnv with C# callback! ,说明代码中有绑定了Lua function的委托实例没有释放,找到这个委托实例并将其释放即可,具体信息可以查看官方的常见问题解答页面。

3.3.2 将function映射到LuaFunction类

将function映射到LuaFunction类比较简单,不需要生成代码,但这种方式性能较差,而且没有类型检查。在LuaFunction类中有一个变参的 Call() 方法,可以传递任意类型的参数,这些参数对应Lua function的参数,这一方法的返回值是一个object数组,其中的元素分别对应Lua function的多个返回值。

仍然以上一节中给出的Lua function为例,相应的C#部分代码是:

// 映射LuaFunction luaFunc = luavm.Global.Get<LuaFunction>("luafunc");// 调用object[] results = luaFunc.Call(1, 2, 3, 4);// 取值// 注意,Lua function中的v3在这里变成了LuaTable,其中含有x、y、z三个keyLuaTable table = results[0] as LuaTable;Vector3 v3 = new Vector3(table.Get<int>("x"), table.Get<int>("y"), table.Get<int>("z"));long sum = (long)results[1];long pro = (long)results[2];Debug.Log(v3 + " " + sum + " " + pro);

在上面的示例代码中,sum和pro的类型由int变成了long,这是因为,在C#中参数(或字段)类型是object时,默认以long类型传递整数。如果要指明整数的类型,比如int,可以在Lua中使用XLua提供的 CS.XLua.Cast.Int32() 方法,例如:

function luafunc(a, b, c)    v3 = {x = a, y = b, z = c}    sum = CS.XLua.Cast.Int32(a + b + c + d)    pro = CS.XLua.Cast.Int32(a * b * c * d)    return v3, sum, proend

3.4 使用建议

在C#中访问Lua全局数据,尤其是访问table和function时,代价比较大,建议尽量减少访问次数。可以在程序初始化阶段把要调用的Lua function绑定到C# delegate并缓存下来,以后直接调用这个delegate即可,table与之类似。

如果Lua方面的实现部分都以delegate和interface的方式提供,那么使用方可以完全与xLua解耦 —— 由一个专门的模块负责xLua的初始化以及delegate和interface的映射,然后把这些delegate和interface实例设置到要用到它们的地方。

4. 在Lua中调用C#

本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/LuaCallCSharp/LuaCallCs.cs中找到使用示例。

在Lua中调用C#时,首先要注意以下几点:

xLua中所有的C#类都被放到了 CS 模块中。

Lua语言中没有new关键字;

Lua语言运算符:+ , - , * , / , % , ^ , == , ~= , < , > , <= , >= , and , or , not , .. , #

Lua语言不支持泛型

Lua语言不支持类型转换

标识生成代码的特性标签:[LuaCallCSharp]

除此之外,在xLua中可以像写普通的C#代码那样调用C#。

xLua支持以下功能:

创建C#对象

通过C#子类访问C#父类的静态属性和方法

通过C#子类对象访问C#父类的成员属性和方法

带有默认参数的C#方法

带有可变参数的C#方法

C#方法重载

C#扩展方法

C#操作符重载

C#枚举

自动转换C#复杂类型和Lua table

C#的delegate和event

下面将对在Lua中访问C#时的几点特殊情况加以说明,并在最后给出示例代码。

4.1 Lua的点语法和冒号语法

在Lua中,使用点(.)语法调用对象的成员方法时方法的第一个参数应该传入对象自身,而使用冒号(:)语法调用对象的成员方法时可以省略这一参数。建议使用冒号语法。示例代码:

local gameObject = CS.UnityEngine.GameObject()-- 使用冒号语法不用传入对象自身gameObject:SetActive(false)-- 使用点语法需要传入对象自身gameObject.SetActive(gameObject, true)

4.2 C#复杂类型和Lua table的自动转换

在Lua中可以直接使用table来代替带有无参构造方法的C#复杂类型(class和struct)。下面示例中展示了C# Vector3和Lua table的自动转换:

C#代码:

[LuaCallCSharp]public class MyClass{    public void ComplexStructTest(Vector3 v3)    {        Debug.Log("ComplexStructTest: " + v3);    }}

Lua代码:

local myObj = CS.MyClass()-- C# Vector3与Lua table的自动转换myObj:ComplexStructTest({x=1.0, y=2.0, z=3.0})

4.3 参数和返回值的处理规则

参数处理规则:C#方法的普通参数和ref 参数会按照从左到右的顺序依次映射到Lua function的形参,out 参数不会被映射到Lua function的形参。

返回值处理规则:C#方法的返回值会映射到Lua function的第一个返回值,然后C#方法的 out 和 ref 参数会按照从左到右的顺序依次映射到Lua function的其他返回值。

C#示例代码:

[LuaCallCSharp]public class MyClass{    public int RefOutTest(int a, out int b, ref int c)    {        b = 32;        return a + b + c;    }}

Lua示例代码:

local myObj = CS.MyClass()

-- ret、1、2分别映射到C#方法的返回值、参数a、参数c

local ret = myObj:RefOutTest(1, 2)

4.4 枚举类型

在xLua中可以像使用C#类的静态属性一样使用枚举成员。枚举的 __CastFrom() 方法可以将一个整数或字符串转换到枚举值。示例代码:

C#代码:

[LuaCallCSharp]public enum MyEnum{    A, B, C}

Lua代码:

-- 访问枚举成员CS.MyEnum.A-- 将整数转换到枚举值CS.MyEnum.__CastFrom(1)-- 将字符串转换到枚举值CS.MyEnum.__CastFrom('C')

4.5 delegate和event

在xLua中可以像在C#中一样使用 + 和 - 运算符向delegate调用链中添加方法,不过Lua中没有 += 和 -= 运算符。方法的添加顺序会影响调用顺序。需要注意的两点是:

在C#中声明delegate时需要为其添加一个默认的实现,否则在Lua中向其添加方法时会抛出异常;

调用delegate时应该使用点语法,如果使用冒号语法传入的参数会变成 nil 。

在xLua中为event添加和移除监听的写法有些不同,不能直接通过加减运算来实现,而是要使用 EventName('+', func_name) 和 EventName('-', func_name) 这种写法来实现添加和移除监听,并且需要使用冒号语法。另外,在xLua中不能直接通过 EventName(params) 这种形式来触发事件,而是要在C#代码中添加一个间接触发方法。

C#示例代码:

[LuaCallCSharp]public class MyClass{    // 委托,需要有默认实现    public Action<string> MyDelegate = (arg) => { };    // 事件    public event Action<string> MyEvent;    // 在Lua中调用此方法间接触发事件    public void TriggerEvent(string arg)    {        if(MyEvent != null) MyEvent(arg);    }}

Lua示例代码:

local function my_lua_callback(arg)    print('my_lua_callback: ' .. arg)endlocal myObj = CS.MyClass()-- delegate使用点语法,否则调用委托时参数会变成nil-- Lua中没有+=操作符,方法的添加顺序会影响调用顺序myObj.MyDelegate = myObj.MyDelegate + my_lua_callbackmyObj.MyDelegate('delegate callback')-- event使用冒号语法,不能直接使用MyEvent来触发事件myObj:MyEvent('+', my_lua_callback)myObj:TriggerEvent('event callback 1')myObj:MyEvent('-', my_lua_callback)myObj:TriggerEvent('event callback 2')

4.6 扩展方法

在C#中定义了扩展方法后,为该扩展方法所在的类添加 [LuaCallCSharp] 特性标签,就可以在Lua中直接使用这个扩展方法,

4.7 泛型方法

xLua不支持泛型方法,但可以使用扩展方法为泛型方法添加针对特定类型的转换方法,实现一个假的泛型。例如,下面的示例为GenericTest()方法实现了针对string类型的转换方法:

C#代码:

[LuaCallCSharp]public class MyClass{   
 public void GenericTest<T>(T t)    {   
     Debug.Log("GenericTest: " + typeof(T) + "-" + t.ToString());
    }}[LuaCallCSharp]public static class MyExtensions{  
  public static void GenericTestOfString(this MyClass obj, string arg)  
  {        obj.GenericTest<string>(arg);    }}

Lua代码:

local myObj = CS.MyClass()myObj:GenericTestOfString('fake')

4.8 类型转换

Lua没有类型转换功能,但xLua提供了 cast() 方法实现了类似的功能,该方法让xLua使用指定的生成代码去调用一个对象。有些时候,第三方库对外暴露的接口是一个interface或者抽象类,其实现类是隐藏的,这时就没办法对实现类进行代码生成,xLua会通过反射来访问这个实现类。如果这种访问很频繁,会很影响性能。这时就可以把第三方库暴露出来的interface或抽象类添加到生成代码列表,然后指定用这个interface或抽象类的生成代码来访问对象,类似于将对象转换成了interface或抽象类的类型。例如,下面的Lua示例代码指定了使用CS.MyInterface的生成代码来访问myObj对象:

cast(myObj, typeof(CS.MyInterface))

4.9 完整示例代码

建议在Lua种使用局部变量缓存需要经常访问的类,这样不仅能够提高开发效率,还能提高性能。例如:

local GameObject = CS.UnityEngine.GameObjectGameObject.Find('obj_name')

C#示例代码:

namespace MyNamespace{    
[LuaCallCSharp] 
   public class MyClass 
   {        
public string id;  
      // delegate需要有默认值,否则Lua中会报错 
       public Action<string> MyDelegate = (arg) => { }; 
       public event Action<string> MyEvent; 
       public MyClass() { id = "id_default"; 
}       
 public MyClass(string id) { this.id = id; }  
      // 带有默认参数的方法  
      public void DefaultParamsTest(int a, int b = 1)     
   {            Debug.Log("DefaultParamsTest: " + (a + b));        } 
       // 带有可变参数的方法    
    public void VariableParamsTest(int a, params int[] args)  
      {            int sum = a;       
     foreach (var arg in args) sum += arg;   
         Debug.Log("VariableParamsTest: " + sum);   
     }      
  // 带有ref、out参数的方法      
  public int RefOutTest(int a, out int b, ref int c)        {            b = 32;            return a + b + c;        }     
   // 带有复杂类型(非基本类型)参数的方法      
  public void ComplexStructTest(Vector3 v3)        {            Debug.Log("ComplexStructTest: " + v3);        }  
      // 枚举     
   public void EnumTest(MyEnum e)        {            Debug.Log("EnumTest: " + e.ToString());        }  
      // 触发事件,不能再Lua中直接使用MyEvent触发事件,添加一层转接      
  public void TriggerEvent(string arg)        {            if (MyEvent != null)            {                MyEvent(arg);            }        } 
       // 操作符重载      
  public static MyClass operator +(MyClass a, MyClass b)        {            MyClass sum = new MyClass(a.id + "&" + b.id);            return sum;        }        // 泛型方法        public void GenericTest<T>(T t)        {            Debug.Log("GenericTest: " + typeof(T) + "-" + t.ToString());        }    }    [LuaCallCSharp]    
public enum MyEnum    {        A, B, C    }    [LuaCallCSharp]    public static class MyExtensions    {      
  // 扩展方法        public static void MyExtensionMethod(this MyClass obj, string msg)  
      {            Debug.Log("MyExtensionMethod - " + msg);        }       
 // xLua不支持泛型方法假装支持string泛型       
 public static void GenericTestOfString(this MyClass obj, string arg)       
 {            obj.GenericTest<string>(arg);        }    }}
// 这里请参考第5.2节(静态列表)
[LuaCallCSharp]
public static class CsLuaCaster{    // 静态列表    public static List<Type> LuaCallCsCastList = new List<Type>()   
 {        typeof(Action),        typeof(Action<string>)    };}

Lua示例代码:

-- 缓存经常访问的类
local Time = CS.UnityEngine.Time
-- 读静态属性
Time.deltaTime
-- 写静态属性
Time.timeScale = 0.5
-- 调用静态方法
local obj = CS.UnityEngine.GameObject.Find('obj_name')
-- 读(父类)成员属性
obj.name
-- 写(父类)成员属性
obj.name = 'new_name'
-- 调用成员方法,注意冒号语法和点语法的参数区别
obj:SetActive(false)
obj.SetActive(obj, true)
-- 用于测试delegate和event
function my_lua_callback(arg)
    print('my_lua_callback: ' .. arg)
end
-- 访问MyClass类
function lua_call_cs()
    local MyClass = CS.MyNamespace.MyClass
    -- 实例化C#对象,方法重载
    local myObj0 = MyClass()
    local myObj1 = MyClass('id_1')
    -- 操作符重载
    local myObj2 = myObj0 + myObj1
    print('Operator Overload: ' .. myObj2.id)
    -- 默认参数
    myObj0:DefaultParamsTest(1)
    -- 可变参数
    myObj0:VariableParamsTest(1, 2, 3)
    -- ref、out参数
    local ret = myObj0:RefOutTest(1, 2)
    print('RefOutTest: ' .. ret)
    
    -- C#复杂类型与Lua table的自动转换
    myObj0:ComplexStructTest({x=1.0, y=2.0, z=3.0})
    
    -- 枚举,像使用静态属性一样使用枚举
    local MyEnum = CS.MyNamespace.MyEnum
    myObj0:EnumTest(MyEnum.A)
    -- 枚举的__CastFrom()方法可以将一个整数或字符串转换到枚举值
    myObj0:EnumTest(MyEnum.__CastFrom(1))
    myObj0:EnumTest(MyEnum.__CastFrom('C'))

    -- delegate使用点语法,否则调用委托时参数会变成nil
    -- Lua中没有+=操作符,方法的添加顺序会影响调用顺序
    myObj0.MyDelegate = myObj0.MyDelegate + my_lua_callback
    myObj0.MyDelegate('delegate callback')
    -- event使用冒号语法,不能直接使用MyEvent来触发事件
    myObj0:MyEvent('+', my_lua_callback)
    myObj0:TriggerEvent('event callback 1')
    myObj0:MyEvent('-', my_lua_callback)
    myObj0:TriggerEvent('event callback 2')
    -- 扩展方法
    myObj0:MyExtensionMethod('hello')
    -- xLua不支持泛型方法,这里是假的泛型方法
    myObj0:GenericTestOfString('fake')
    -- Lua没有类型转换功能,但xLua提供了cast方法实现了类似的功能
    -- 指定使用MyClass类的生成代码访问myObj0,类似于把myObj0转换成MyInterface类型
    -- cast(myObj0, typeof(CS.MyNamespace.MyInterface))
end
lua_call_cs()

5. 代码生成配置

xLua的所有配置都支持3种方式:特性标签、静态列表和动态列表。

对于xLua的配置,有两个必须和两个建议:

列表方式都必须在静态类中进行配置

列表方式都必须使用静态字段/属性

建议不要使用特性标签,这种方式在IL2CPP模式下会增加不少的代码量

建议将列表方式的配置放到Editor目录(如果是 Hotfix 配置,而且类位于 Assembly-CSharp.dll 之外的其它dll中,必须放Editor目录)

5.1 特性标签

xLua通过白名单来指明要为哪些类生成代码,而白名单通过特性标签(Attribute)来配置。为类添加 [CSharpCallLua] 或 [LuaCallCSharp] 特性标签后,通过Unity编辑器菜单栏的 XLua - Generate Code 按钮即可为该类生成适配代码。

如果一个C#类型添加了 [LuaCallCSharp] 特性标签,那么xLua会生成这个类的适配代码,包括构造方法、成员属性和方法、静态属性和方法;如果没有为类型添加这个特性标签,那么xLua会尝试使用性能较差的反射方式访问C#类。xLua只会为添加了该特性标签的类型生成代码,不会自动为该类型的父类生成代码,当子类对象访问父类方法时,如果父类也添加了特性标签,则执行父类的适配代码,否则将尝试使用反射来访问父类。反射方式除了性能不佳外,在IL2CPP模式下还有可能因为代码裁剪而导致无法访问。建议所有要在Lua中访问的C#代码,要么加上 [LuaCallCSharp] 特性标签,要么加上 [ReflectionUse] 特性标签,这样才能够保证程序在各平台都能正常运行。

如果需要把一个Lua function绑定到C# delegate,或者需要把一个Lua table映射到C# interface,那么需要为delegate或者interface添加 [CSharpCallLua] 特性标签。

特性标签方便使用,但在IL2CPP模式下会增加不少的代码量,不建议使用。

5.2 静态列表

有时候无法直接给一个类型添加特性标签,例如系统API、没有源码的DLL等,这时可以在一个静态类中声明一个静态字段,这一字段只要实现了 IEnumerable<Type> 并且没有使用 BlackList 和 AdditionalProperties 特性标签即可,例如 List<Type> ,然后为这个静态类或静态字段添加 [LuaCallCSharp] 特性标签即可。建议将静态列表放到Editor目录中。示例代码:

[LuaCallCSharp]
public static class StaticListClass
{
    // 静态列表
    public static List<Type> LuaCallCsStaticList = new List<Type>()
    {
        typeof(GameObject),
        typeof(Action<string>),
        typeof(Dictionary<string, GameObject>),
    };
}

5.3 动态列表

与静态列表类似,动态列表需要在一个静态类中声明一个静态属性,并为其添加相应的特性标签。在静态属性的Getter代码块中,可以实现很多效果,例如按命名空间配置、按程序及配置等。建议将动态列表放到Editor目录中。示例代码:

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

5.4 xLua特性标签列表

xLua特性标签的详细介绍请查看xLua配置文档

| 特性标签 | 用途简述 |
| XLua.LuaCallCSharp | 生成C#类型的适配代码 |
| XLua.CSharpCallLua | 生成C# delegate或interface的适配代码 |
| XLua.ReflectionUse | 阻止IL2CPP进行代码裁剪 |
| XLua.DoNotGen | 不生成某个方法、字段或属性的适配代码,通过反射访问 |
| XLua.GCOptimize | 优化C#纯值类型的转换性能 |
| XLua.AdditionalProperties | 通过属性访问私有字段 |
| XLua.BlackList | 不生成某些类成员的适配代码 |

个人学习博客感谢关注

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容