|
XLua在Unity中的用法摘要
整理自官方教程:
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
提示: 搜索关键字“建议”查看官方建议用法,也可以搜索其他关键字快速查找相关内容。
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 简单说明与示例
1.3.1 实例化与释放LuaEnv对象
使用xLua时,首先要实例化一个LuaEnv对象:- LuaEnv luavm =newLuaEnv();
复制代码 每个LuaEnv实例对应一个Lua虚拟机。出于开销考虑,建议创建一个全局唯一的LuaEnv实例,之后Lua方法调用,都通过该实例来完成。
当不再使用LuaEnv对象后,要将其释放:1.3.2 在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.3.3 C#与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')");。
C#类型与Lua类型的映射方式以及更多方法调用细节将在下文进行说明。
1.3.4 生成代码
通过Unity编辑器窗口的 XLua - Generate Code 选项可以生成用于实现C#和Lua交互的适配代码。生成代码后程序的性能更好,建议使用。如果没有生成代码,则xLua会使用反射进行交互,这种方式性能不高,但是能够减小安装包大小。
在Unity编辑器中,不生成代码也能够正常运行程序,建议在开发阶段不要生成代码。在打包手机版应用和做性能测试、调优前必须生成代码。
用于生成代码的C#特性标签是 [CSharpCallLua] 和 [LuaCallCSharp] ,在下文中会有它们的使用示例。xLua中还有一些其他的用于控制代码生成的特性标签,可以在官方的xLua配置文档中查看它们的详细信息。
1.3.5 示例代码
- publicclassExample:MonoBehaviour{privateLuaEnv luavm;privatevoidStart(){
- luavm =newLuaEnv();// 直接执行Lua代码
- luavm.DoString("print('hello world')");// 查找Lua脚本文件并执行其中的代码//luavm.DoString("require 'lua_script_file'");}privatevoidOnDestroy(){// 记得要释放if(luavm !=null){
- luavm.Dispose();}}}
复制代码 2. Lua脚本文件的加载位置与自定义加载器
2.1 Lua脚本文件的加载位置
假设当前系统中没有名为 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脚本文件:
内置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.2 自定义加载器
实现自定义加载器只需要创建 CustomLoader 委托实例并通过 LuaEnv.AddLoader() 方法将其添加到LuaEnv实例中即可。
CustomLoader委托的签名为:- publicdelegatebyte[]CustomLoader(refstring filepath);
复制代码 示例代码:- publicclassCustomLoaderExample:MonoBehaviour{privatevoidStart(){LuaEnv luavm =newLuaEnv();// 添加自定义加载器
- luavm.AddLoader(MyCustomLoader);string filepath = Application.dataPath +"/myluafiles/test.lua";
- luavm.DoString(string.Format("require '{0}'", filepath));
- luavm.Dispose();}// 自定义的Lua文件加载器。// 参数filepath:【require 'filepath'】中的【filepath】// 返回值:文件内容privatebyte[]MyCustomLoader(refstring 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. 加载加密文件并解密returnnull;}}
复制代码 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为例:- publicclassTableClass{publicint f1;publicint 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]publicinterfaceITable{int f1 {get;set;}int f2 {get;set;}intadd(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, 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]publicdelegateVector3LuaFuncDelegate(int a,int b,int c,outint sum,refint 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 =newVector3(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, proend3.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 tableC#的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]publicclassMyClass{publicvoidComplexStructTest(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]publicclassMyClass{publicintRefOutTest(int a,outint b,refint c){
- b =32;return a + b + c;}}
复制代码 Lua示例代码:
local myObj = CS.MyClass()-- ret、1、2分别映射到C#方法的返回值、参数a、参数clocal ret = myObj:RefOutTest(1,2)4.4 枚举类型
在xLua中可以像使用C#类的静态属性一样使用枚举成员。枚举的 __CastFrom() 方法可以将一个整数或字符串转换到枚举值。示例代码:
C#代码:- [LuaCallCSharp]publicenum 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]publicclassMyClass{// 委托,需要有默认实现public Action<string> MyDelegate =(arg)=>{};// 事件publicevent Action<string> MyEvent;// 在Lua中调用此方法间接触发事件publicvoidTriggerEvent(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]publicclassMyClass{publicvoidGenericTest<T>(T t){
- Debug.Log("GenericTest: "+typeof(T)+"-"+ t.ToString());}}[LuaCallCSharp]publicstaticclassMyExtensions{publicstaticvoidGenericTestOfString(thisMyClass 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]publicclassMyClass{publicstring id;// delegate需要有默认值,否则Lua中会报错public Action<string> MyDelegate =(arg)=>{};publicevent Action<string> MyEvent;publicMyClass(){ id ="id_default";}publicMyClass(string id){this.id = id;}// 带有默认参数的方法publicvoidDefaultParamsTest(int a,int b =1){
- Debug.Log("DefaultParamsTest: "+(a + b));}// 带有可变参数的方法publicvoidVariableParamsTest(int a,paramsint[] args){int sum = a;foreach(var arg in args) sum += arg;
- Debug.Log("VariableParamsTest: "+ sum);}// 带有ref、out参数的方法publicintRefOutTest(int a,outint b,refint c){
- b =32;return a + b + c;}// 带有复杂类型(非基本类型)参数的方法publicvoidComplexStructTest(Vector3 v3){
- Debug.Log("ComplexStructTest: "+ v3);}// 枚举publicvoidEnumTest(MyEnum e){
- Debug.Log("EnumTest: "+ e.ToString());}// 触发事件,不能再Lua中直接使用MyEvent触发事件,添加一层转接publicvoidTriggerEvent(string arg){if(MyEvent !=null){MyEvent(arg);}}// 操作符重载publicstaticMyClassoperator+(MyClass a,MyClass b){MyClass sum =newMyClass(a.id +"&"+ b.id);return sum;}// 泛型方法publicvoidGenericTest<T>(T t){
- Debug.Log("GenericTest: "+typeof(T)+"-"+ t.ToString());}}[LuaCallCSharp]publicenum MyEnum
- {
- A, B, C
- }[LuaCallCSharp]publicstaticclassMyExtensions{// 扩展方法publicstaticvoidMyExtensionMethod(thisMyClass obj,string msg){
- Debug.Log("MyExtensionMethod - "+ msg);}// xLua不支持泛型方法假装支持string泛型publicstaticvoidGenericTestOfString(thisMyClass obj,string arg){
- obj.GenericTest<string>(arg);}}}// 这里请参考第5.2节(静态列表)[LuaCallCSharp]publicstaticclassCsLuaCaster{// 静态列表publicstatic List<Type> LuaCallCsCastList =newList<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和eventfunction 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))endlua_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]publicstaticclassStaticListClass{// 静态列表publicstatic List<Type> LuaCallCsStaticList =newList<Type>(){typeof(GameObject),typeof(Action<string>),typeof(Dictionary<string, GameObject>),};}
复制代码 5.3 动态列表
与静态列表类似,动态列表需要在一个静态类中声明一个静态属性,并为其添加相应的特性标签。在静态属性的Getter代码块中,可以实现很多效果,例如按命名空间配置、按程序及配置等。建议将动态列表放到Editor目录中。示例代码:- publicstaticclassDynamicListClass{[Hotfix]publicstatic 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 | 不生成某些类成员的适配代码 |
|
|