Xlua初识
Xlua简介xLua是由腾讯维护的一个开源项目,xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在,腾讯已经将xLua开源到GitHub。其git地址是:https://github.com/Tencent/xLua。
lua文件加载
void Start()
{
//new 一个lua解释器
LuaEnv luaenv = new LuaEnv();
//1.调用dostring方法,执行字符串
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
//2.加载lua脚本
luaenv.DoString("require('Main')");
//3.通过AddLoader添加回调,可以自定义加载lua文件路径,返回byte数组
luaenv.AddLoader(CustomLoader);
luaenv.Dispose();
}
/// <summary>
/// 自定义加载lua脚本
/// </summary>
/// <param name="filepath"></param>
/// <returns></returns>
publicbyte[] CustomLoader(ref string filepath)
{
//自定义加载路径
//加载lua文件
//读取字节返回
}C#访问Lua
1.获取全局数据结构类型。访问LuaEnv.Global即可。 Global定义了一个lua全局环境_G
luaenv.Global.Get<int>("a")
luaenv.Global.Get<string>("b")
luaenv.Global.Get<bool>("c") 2.访问一个全局Table
方式一:映射到普通class或struct
string script = @"
d = {
f1 = 12, f2 = 34,
1, 2, 3,
add = function(self, a, b)
print('d.add called')
return a + b
end
}
";
public class DClass {
public int f1;
public int f2;
}
DClass d = luaenv.Global.Get<DClass>("d");//映射到有对应字段的class,by value
Debug.Log("_G.d = {f1=" + d.f1 + ", f2=" + d.f2 + "}");
//定义一个Class,有对应的table的字段public属性,而且有无参数构造函数即可。这种方式下xlua会帮你new一个实例,并把对应的字段赋值过去
//table的属性可以多于或少于class属性。可以嵌套其他复杂类型。要注意的是,这个过程是值拷贝,如果class比较复代价会比较大,而且修改class字段的值不会同步到table,反过来也不会 方式二:映射到interface
//需要加特性
public interface ItfD
{
int f1 { get; set; }
int f2 { get; set; }
int add(int a, int b);
}
ItfD d3 = luaenv.Global.Get<ItfD>("d"); //映射到interface实例,by ref,这个要求interface加到生成列表,否则会返回null,建议用法
d3.f2 = 1000;
Debug.Log("_G.d = {f1=" + d3.f1 + ", f2=" + d3.f2 + "}");
Debug.Log("_G.d:add(1, 2)=" + d3.add(1, 2));
//这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。 方式三:更轻量级的by value方式:映射到Dictionary<> , list<> 不想定义class或者interface的话,可以考虑这个,前提是table下key和value类型都是一致的。
string script = @"
d = {
f1 = 12, f2 = 34,
1, 2, 3,
add = function(self, a, b)
print('d.add called')
return a + b
end
}
";
Dictionary<string, double> d1 = luaenv.Global.Get<Dictionary<string, double>>("d");//映射到Dictionary<string, double>,by value
Debug.Log("_G.d = {f1=" + d1["f1"] + ", f2=" + d1["f2"] + "}, d.Count=" + d1.Count);
List<double> d2 = luaenv.Global.Get<List<double>>("d"); //映射到List<double>,by value
Debug.Log("_G.d.len = " + d2.Count); 2.4 另外一种by ref方式:映射到LuaTable类。这种方式好处是不需要生成代码,但也有一下问题,比如慢,没有类型检查
LuaTable d4 = luaenv.Global.Get<LuaTable>("d");//映射到LuaTable,by ref
Debug.Log("_G.d = {f1=" + d4.Get<int>("f1") + ", f2=" + d4.Get<int>("f2") + "}");3.访问一个全局function
任然使用Get方法,不同的是类型映射
方式一:映射到delegate。这种事建议的方法,性能好很多。而且类型安全。缺点是要生成代码。
delegate要怎样声明呢? 对于function的每个参数就声明一个输入类型的参数。 多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。
delegate的使用就更简单了,直接像个函数那样用就可以了。
string script = @"
function e()
print('i am e')
end
function f(a, b)
print('a', a, 'b', b)
return 1, {f1 = 1024}
end
function ret_e()
print('ret_e called')
return e
end";
public class DClass
{
public int f1;
public int f2;
}
public delegate int FDelegate(int a, string b, out DClass c);
public delegate Action GetE();
Action e = luaenv.Global.Get<Action>("e");//映射到一个delgate,要求delegate加到生成列表,否则返回null,建议用法
e();
FDelegate f = luaenv.Global.Get<FDelegate>("f");
DClass d_ret;
int f_ret = f(100, "John", out d_ret);//lua的多返回值映射:从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数
Debug.Log("ret.d = {f1=" + d_ret.f1 + ", f2=" + d_ret.f2 + "}, ret=" + f_ret);
GetE ret_e = luaenv.Global.Get<GetE>("ret_e");//delegate可以返回更复杂的类型,甚至是另外一个delegate
e = ret_e();
e(); 方式二:映射到LuaFunction。这种方式优缺点和方式一相反。使用简单,LuaFunction上有个变参Call函数,可以传入任意类型,任意个数的参数,返回值是object数组,对应于lua的多返回值
LuaFunction d_e = luaenv.Global.Get<LuaFunction>("e");
d_e.Call();官方使用建议:
访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。
Lua调用C#
new C# 对象
--lua脚本代码实例
local newGameObj = CS.UnityEngine.GameObject()
local newGameObj2 = CS.UnityEngine.GameObject('helloworld')
print(newGameObj, newGameObj2)
---------------
基本类似,除了:
1. lua里头没有new关键字;
2. 所有C#相关的都放到CS下,包括构造函数,静态成员属性、方法;
如果有多个构造函数呢?放心,xlua支持重载,比如你要调用GameObject的带一个string参数的构造函数,这么写:
local newGameObj2 = CS.UnityEngine.GameObject('helloworld')访问C#静态属性,方法
--读静态属性
Cs.UnityEngine.Time.deltaTime
--写静态属性
Cs.UnityEngine.Time.timeScale = 0.5
--调用静态方法
CS.UnityEngine.GameObject.Find('helloworld')
小技巧:如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能:
local GameObject = CS.UnityEngine.GameObject
GameObject.Find('helloworld')
--建议_G内设置全局变量访问C#成员属性, 方法
--读成员属性
testobj.DMF
--写成员属性
testobj.DMF = 1024
--调用成员方法
--注意:调用成员方法,第一个参数需要传该对象,建议用冒号语法糖,如下
testobj:DMFunc()父类属性,方法。
xlua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类实例)访问基类的成员属性,成员方法参数的输入输出属性(out,ref)
Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,然后从左往右对应lua 调用侧的实参列表;
Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。重载方法
--直接通过不同的参数类型进行重载函数的访问,例如:
testobj:TestFunc(100)
testobj:TestFunc('hello')
--注意:xlua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#丰富,存在一对多的情况,比如C#的int,float,double都对应于lua的number,上面的例子中TestFunc如果有这些重载参数,第一行将无法区分开来,只能调用到其中一个(生成代码中排前面的那个)操作符
支持的操作符有:+,-,*,/,==,一元-,<,<=, %,[]参数带默认值的方法
和C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会用默认值补上。可变参数方法: 实例: params string[] str
//对于C#的如下方法:
void VariableParamsFunc(int a, params string[] strs)
--可以在lua里头这样调用:
testobj:VariableParamsFunc(5, 'hello', 'john')使用Extension methods (扩展方法),在C#里定义了,lua里就能直接使用。
Extension Methods - C# Programming Guide | Microsoft Docs
枚举类型
枚举值就像枚举类型下的静态属性一样。
//C#
public enum TestEnum
{
E1,
E2
}
-- lua
local test = CS.TestEnum.E1
枚举类支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换,例如:
CS.TestEnum.__CastFrom(1)
CS.TestEnum.__CastFrom('E1') delegate使用(调用,+,-)
C#的delegate调用:和调用普通lua函数一样
+操作符:对应C#的+操作符,把两个调用串成一个调用链,右操作数可以是同类型的C# delegate或者是lua函数。
-操作符:和+相反,把一个delegate从调用链中移除。
Ps:delegate属性可以用一个luafunction来赋值。
event
比如testobj里头有个事件定义是这样:public event Action TestEvent;
增加事件回调
testobj:TestEvent('+', lua_event_callback)移除事件回调
testobj:TestEvent('-', lua_event_callback)
页:
[1]