整理自官方教程:
https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/XLua教程.md
https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/configure.md其他官方文档链接:
常见问题解答:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/faq.md
XLua API:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/XLua_API.md
热补丁操作指南:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/hotfix.md
XLua增加删除第三方Lua库:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/XLua增加删除第三方lua库.md
提示: 搜索关键字“建议”查看官方建议用法,也可以搜索其他关键字快速查找相关内容。
在下载页面中下载所需资源,其中xlua_vx.x.x.zip和xlua_vx.x.x_luajit.zip是XLua框架的两个不同版本,二者互斥,必须二选一。
以xlua_vx.x.x.zip为例,解压xlua_vx.x.x.zip,将其中的Assets文件夹与希望使用xLua的Unity工程的Assets文件夹合并,不要更改Assets文件夹的目录结构。合并完成后,即可在代码中使用xLua。
如果要将xLua安装到其他目录,请参考FAQ。
使用xLua时,首先要实例化一个LuaEnv对象:
LuaEnv luavm = new LuaEnv();
每个LuaEnv实例对应一个Lua虚拟机。出于开销考虑,建议创建一个全局唯一的LuaEnv实例,之后Lua方法调用,都通过该实例来完成。
当不再使用LuaEnv对象后,要将其释放:
luavm.Dispose();
通过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文件存放位置和自定义加载器的相关内容。
在C#中,使用 LuaEnv.Global.Get
方法来获取名为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')");
。
C#类型与Lua类型的映射方式以及更多方法调用细节将在下文进行说明。
通过Unity编辑器窗口的 XLua - Generate Code 选项可以生成用于实现C#和Lua交互的适配代码。生成代码后程序的性能更好,建议使用。如果没有生成代码,则xLua会使用反射进行交互,这种方式性能不高,但是能够减小安装包大小。
在Unity编辑器中,不生成代码也能够正常运行程序,建议在开发阶段不要生成代码。在打包手机版应用和做性能测试、调优前必须生成代码。
用于生成代码的C#特性标签是 [CSharpCallLua]
和 [LuaCallCSharp]
,在下文中会有它们的使用示例。xLua中还有一些其他的用于控制代码生成的特性标签,可以在官方的xLua配置文档中查看它们的详细信息。
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();
}
}
}
假设当前系统中没有名为 not_exist.lua 的Lua脚本文件,那么在执行代码 luavm.DoString("require 'not_exist'");
时,Unity控制台会打印如下信息:
no field package.preload['not_exist']
no such builtin lib 'not_exist'
no such file 'not_exist' in CustomLoaders!
no such resource 'not_exist.lua'
no file 'D:\Unity\Unity2017.4.4\Editor\lua\not_exist.lua'
no file 'D:\Unity\Unity2017.4.4\Editor\lua\not_exist\init.lua'
no file 'D:\Unity\Unity2017.4.4\Editor\not_exist.lua'
no file 'D:\Unity\Unity2017.4.4\Editor\not_exist\init.lua'
no file 'D:\Unity\Unity2017.4.4\Editor\..\share\lua\5.3\not_exist.lua'
no file 'D:\Unity\Unity2017.4.4\Editor\..\share\lua\5.3\not_exist\init.lua'
no file '.\not_exist.lua'
no file '.\not_exist\init.lua'
no file 'D:\Unity\Unity2017.4.4\Editor\not_exist.dll'
no file 'D:\Unity\Unity2017.4.4\Editor\..\lib\lua\5.3\not_exist.dll'
no file 'D:\Unity\Unity2017.4.4\Editor\loadall.dll'
no file '.\not_exist.dll'
no such file 'not_exist.lua' in streamingAssetsPath!
在Lua中使用语句
print(package.path)
可以直接输出Lua文件的加载路径,但是这样输出的是所有路径名称连在一起的字符串,不方便查看。
从上表可以看到xLua会在哪些文件夹中查找Lua脚本文件,需要将Lua脚本文件放在这些位置系统才能正确加载它们。除了在Unity安装文件夹中查找Lua脚本文件外,xLua还会在项目的下列位置查找Lua脚本文件:
需要注意的地方是,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文件名使用半角问号(?
)通配符,多个路径之间使用半角分号(;
)分隔,并且文件夹层级使用斜杠(/
)而不是反斜杠(\
)表示。
实现自定义加载器只需要创建 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;
}
}
本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/CSharpCallLua/CSCallLua.cs中找到使用示例。
在C#中访问Lua数据结构的主要方法是 LuaEnv.Global.Get
,该方法具有多个重载,各个重载的具体区别请查看xLua API文档。
luavm.Global.Get<int>("a"); // 访问名为a的整型变量
luavm.Global.Get<bool>("b"); // 访问名为b的布尔变量
luavm.Global.Get<string>("c"); // 访问名为c的字符串变量
假设现在有如下的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);
将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));
如果不想定义class/struct或interface,可以选择将table映射到Dictionary
仍然以第一节中的Lua数据结构为例,将其映射到Dictionary
// 映射到Dictionary
// 因类型不匹配,字符串f3和函数add()会被忽略
Dictionary<string, int> tableDict = luavm.Global.Get<Dictionary<string, int>>("my_table");
Debug.Log(tableDict["f1"] + tableDict["f2"]);
// 映射到List
// 因类型不匹配,字符串f3和函数add()会被忽略
List<int> tableList = luavm.Global.Get<List<double>>("my_table");
for(int i = 0; i < tableList.Count; i++)
{
Debug.Log(tableList[i]);
}
将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"));
将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, pro
end
则可以将其映射到下面的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的委托实例没有释放,找到这个委托实例并将其释放即可,具体信息可以查看官方的常见问题解答页面。
将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三个key
LuaTable 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, pro
end
在C#中访问Lua全局数据,尤其是访问table和function时,代价比较大,建议尽量减少访问次数。可以在程序初始化阶段把要调用的Lua function绑定到C# delegate并缓存下来,以后直接调用这个delegate即可,table与之类似。
如果Lua方面的实现部分都以delegate和interface的方式提供,那么使用方可以完全与xLua解耦 —— 由一个专门的模块负责xLua的初始化以及delegate和interface的映射,然后把这些delegate和interface实例设置到要用到它们的地方。
本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/LuaCallCSharp/LuaCallCs.cs中找到使用示例。
在Lua中调用C#时,首先要注意以下几点:
CS
模块中。+
, -
, *
, /
, %
, ^
, ==
, ~=
, <
, >
, <=
, >=
, and
, or
, not
, ..
, #
[LuaCallCSharp]
除此之外,在xLua中可以像写普通的C#代码那样调用C#。
xLua支持以下功能:
下面将对在Lua中访问C#时的几点特殊情况加以说明,并在最后给出示例代码。
在Lua中,使用点(.
)语法调用对象的成员方法时方法的第一个参数应该传入对象自身,而使用冒号(:
)语法调用对象的成员方法时可以省略这一参数。建议使用冒号语法。示例代码:
local gameObject = CS.UnityEngine.GameObject()
-- 使用冒号语法不用传入对象自身
gameObject:SetActive(false)
-- 使用点语法需要传入对象自身
gameObject.SetActive(gameObject, true)
在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})
参数处理规则: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)
在xLua中可以像使用C#类的静态属性一样使用枚举成员。枚举的 __CastFrom()
方法可以将一个整数或字符串转换到枚举值。示例代码:
C#代码:
[LuaCallCSharp]
public enum MyEnum
{
A, B, C
}
Lua代码:
-- 访问枚举成员
CS.MyEnum.A
-- 将整数转换到枚举值
CS.MyEnum.__CastFrom(1)
-- 将字符串转换到枚举值
CS.MyEnum.__CastFrom('C')
在xLua中可以像在C#中一样使用 +
和 -
运算符向delegate调用链中添加方法,不过Lua中没有 +=
和 -=
运算符。方法的添加顺序会影响调用顺序。需要注意的两点是:
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)
end
local myObj = CS.MyClass()
-- delegate使用点语法,否则调用委托时参数会变成nil
-- Lua中没有+=操作符,方法的添加顺序会影响调用顺序
myObj.MyDelegate = myObj.MyDelegate + my_lua_callback
myObj.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')
在C#中定义了扩展方法后,为该扩展方法所在的类添加 [LuaCallCSharp]
特性标签,就可以在Lua中直接使用这个扩展方法,
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')
Lua没有类型转换功能,但xLua提供了 cast()
方法实现了类似的功能,该方法让xLua使用指定的生成代码去调用一个对象。有些时候,第三方库对外暴露的接口是一个interface或者抽象类,其实现类是隐藏的,这时就没办法对实现类进行代码生成,xLua会通过反射来访问这个实现类。如果这种访问很频繁,会很影响性能。这时就可以把第三方库暴露出来的interface或抽象类添加到生成代码列表,然后指定用这个interface或抽象类的生成代码来访问对象,类似于将对象转换成了interface或抽象类的类型。例如,下面的Lua示例代码指定了使用CS.MyInterface的生成代码来访问myObj对象:
cast(myObj, typeof(CS.MyInterface))
建议在Lua种使用局部变量缓存需要经常访问的类,这样不仅能够提高开发效率,还能提高性能。例如:
local GameObject = CS.UnityEngine.GameObject
GameObject.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()
xLua的所有配置都支持3种方式:特性标签、静态列表和动态列表。
对于xLua的配置,有两个必须和两个建议:
Hotfix
配置,而且类位于 Assembly-CSharp.dll
之外的其它dll中,必须放Editor目录)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模式下会增加不少的代码量,不建议使用。
有时候无法直接给一个类型添加特性标签,例如系统API、没有源码的DLL等,这时可以在一个静态类中声明一个静态字段,这一字段只要实现了 IEnumerable
并且没有使用 BlackList
和 AdditionalProperties
特性标签即可,例如 List
,然后为这个静态类或静态字段添加 [LuaCallCSharp]
特性标签即可。建议将静态列表放到Editor目录中。示例代码:
[LuaCallCSharp]
public static class StaticListClass
{
// 静态列表
public static List<Type> LuaCallCsStaticList = new List<Type>()
{
typeof(GameObject),
typeof(Action<string>),
typeof(Dictionary<string, GameObject>),
};
}
与静态列表类似,动态列表需要在一个静态类中声明一个静态属性,并为其添加相应的特性标签。在静态属性的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();
}
}
}
xLua特性标签的详细介绍请查看xLua配置文档。
特性标签 | 用途简述 |
---|---|
XLua.LuaCallCSharp | 生成C#类型的适配代码 |
XLua.CSharpCallLua | 生成C# delegate或interface的适配代码 |
XLua.ReflectionUse | 阻止IL2CPP进行代码裁剪 |
XLua.DoNotGen | 不生成某个方法、字段或属性的适配代码,通过反射访问 |
XLua.GCOptimize | 优化C#纯值类型的转换性能 |
XLua.AdditionalProperties | 通过属性访问私有字段 |
XLua.BlackList | 不生成某些类成员的适配代码 |