赞
踩
整理自官方教程:
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();
- 1
每个LuaEnv实例对应一个Lua虚拟机。出于开销考虑,建议创建一个全局唯一的LuaEnv实例,之后Lua方法调用,都通过该实例来完成。
当不再使用LuaEnv对象后,要将其释放:
luavm.Dispose();
- 1
通过LuaEnv.DoString(string)方法来在C#中执行Lua代码,代码的执行方式有两种。
第一种方式是直接通过参数传入Lua代码文本,但不建议使用这种方式。下面的示例中传入了一行Lua代码 print('hello world')
,将会在Unity控制台打印hello world。
luavm.DoString("print('hello world')");
- 1
第二种方式是通过参数传递Lua代码文件名称(或位置),建议使用这种方式。下面的示例中,将会查找名为 hello_world.lua
的Lua脚本文件并执行该文件中的Lua代码。
luavm.DoString("require 'lua_script_file'");
- 1
建议的脚本加载方式是:整个程序中只有一处 DoString("require 'main'")
,然后在main.lua中加载其他的Lua脚本(类似于在Lua命令行执行 $ lua main.lua
)。
指令 require
会依次调用不同的加载器去加载Lua文件,当某个加载器成功加载Lua文件后就不再调用其他加载器,如果所有加载器都没能加载到参数中指定地文件,则报告错误。xLua对Lua脚本文件的存放位置有要求,如果要加载自定义位置的、来自网络的、经过压缩或加密的Lua文件,则需要实现自定义加载器。下文中会介绍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类型的映射方式以及更多方法调用细节将在下文进行说明。
通过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(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
假设当前系统中没有名为 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");
- 1
注意要对Lua文件名使用半角问号(?
)通配符,多个路径之间使用半角分号(;
)分隔,并且文件夹层级使用斜杠(/
)而不是反斜杠(\
)表示。
实现自定义加载器只需要创建 CustomLoader 委托实例并通过 LuaEnv.AddLoader()
方法将其添加到LuaEnv实例中即可。
CustomLoader委托的签名为:
public delegate byte[] CustomLoader(ref string filepath);
- 1
示例代码:
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; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
本章中提到的所有方法都可以在xLua/Assets/XLua/Tutorial/CSharpCallLua/CSCallLua.cs中找到使用示例。
在C#中访问Lua数据结构的主要方法是 LuaEnv.Global.Get<T>(string name)
,该方法具有多个重载,各个重载的具体区别请查看xLua API文档。
luavm.Global.Get<int>("a"); // 访问名为a的整型变量
luavm.Global.Get<bool>("b"); // 访问名为b的布尔变量
luavm.Global.Get<string>("c"); // 访问名为c的字符串变量
- 1
- 2
- 3
假设现在有如下的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;
}
- 1
- 2
- 3
- 4
- 5
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);
- 1
- 2
将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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
示例代码:
ITable table = luavm.Global.Get<ITable>("my_table");
Debug.Log(table.add(table.f1, table.f2));
- 1
- 2
如果不想定义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]);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
将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"));
- 1
- 2
将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
- 1
- 2
- 3
- 4
- 5
- 6
则可以将其映射到下面的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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果在释放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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
在上面的示例代码中,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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
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
}
- 1
- 2
- 3
- 4
- 5
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
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); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
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>) }; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
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<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>),
};
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
与静态列表类似,动态列表需要在一个静态类中声明一个静态属性,并为其添加相应的特性标签。在静态属性的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();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
xLua特性标签的详细介绍请查看xLua配置文档。
特性标签 | 用途简述 |
---|---|
XLua.LuaCallCSharp | 生成C#类型的适配代码 |
XLua.CSharpCallLua | 生成C# delegate或interface的适配代码 |
XLua.ReflectionUse | 阻止IL2CPP进行代码裁剪 |
XLua.DoNotGen | 不生成某个方法、字段或属性的适配代码,通过反射访问 |
XLua.GCOptimize | 优化C#纯值类型的转换性能 |
XLua.AdditionalProperties | 通过属性访问私有字段 |
XLua.BlackList | 不生成某些类成员的适配代码 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。