新博客:https://yinl.fun 欢迎关注,同步更新
toLua踩坑篇
最近工作得用Lua实现逻辑,桥梁用的toLua,踩了很多坑,在这里记录一下。
坑~toLua解析Lua属性
首先我们给出Lua文件的内容,基于toLua Examples 04修改:
1print('Objs2Spawn is: '..Objs2Spawn) 2var2read = 42 3varTable = {1,2,3,4,5} 4varTable.default = 1 5varTable.map = {} 6varTable.map.name = "map" 7 8meta = {name = "meta"} 9setmetatable(varTable, meta) 10 11function TestFunc() 12 print('get func by variable') 13end 14 15function varTable.func() 16 print("获取table中的函数") 17end
在展示坑之前先看看使用toLua,下面是解析全局变量的代码:
1LuaState lua = new LuaState(); 2Debug.Log(lua["全局变量名"]);
缓存成函数类LuaFunction:
LuaFunction func = lua.GetFunction("函数名");
缓存成表类LuaTable:
LuaTable table = lua.GetTable("表名");
那么下面开始踩坑,上代码:
1// 声明lua解析器对象 2LuaState lua = new LuaState(); 3lua.Start(); 4// 添加lua执行路径 5lua.AddSearchPath(Application.dataPath + "/Lua"); 6// 将Objs2Spawn存到lua的全局变量中并赋值 7lua["Objs2Spawn"] = 5; 8// 寻找文件并执行,如找到则返回一个对象。 9lua.DoFile("CallLuaFunction.lua"); 10// 寻找文件并执行,第一次生成对象返回,之后返回之前生成过的对象。 11lua.Require("AccessingLuaVariables"); 12 13// 通过LuaState访问 14Debug.Log(lua["var2read"]); // 输出-> 42 15Debug.Log(lua["varTable.default"]); // 输出-> 1 16// 直接利用LuaState获取name 17Debug.Log(lua["varTable.map.name"]); // 输出-> map 18 19LuaFunction func = lua["TestFunc"] as LuaFunction; 20func.Call(); 21 22func = lua["varTable.func"] as LuaFunction; 23func.Call(); 24func.Dispose(); 25 26//cache成LuaTable访问 27LuaTable table = lua.GetTable("varTable"); 28Debug.Log(table["default"]); // 输出-> 1 29// 利用Table获取name,有Bug 30Debug.Log(table["map.name"]); // 输出-> Null 31LuaTable table1 = table.GetTable<LuaTable>("map"); 32// 利用Table的Table获取name 33Debug.Log(table1["name"]); // 输出-> map
最后的结果为: 
图中显示代码table["map.name"]的结果为Null,这里是toLua的一个bug,在缓存成table的时候处理table中的table有些问题。解决方式上面的代码也给了,就是从table中再获取table,实在是非常的麻烦,还有个简单的方式为直接利用LuaState获取。
坑~Lua函数解析
这里利用的是toLua Examples 03的内容作了一些修改,首先还是看Lua代码:
1CallLuaFunction = {} 2 3function CallLuaFunction.func1() 4 print("Call Lua Function") 5end 6 7function CallLuaFunction.func2(num1, num2) 8 print("执行func2") 9 return num1 + num2 10end 11 12function CallLuaFunction.func3(num) 13 return num + 1 14end 15 16function CallLuaFunction.func4(num) 17 print("执行func4 " .. num) 18end 19 20function CallLuaFunction:func5() 21 self.a = 1 22 self.b = 2 23 print(self.a .. " " .. self.b) 24end
这里一共有5个函数,我们分别针对这五个函数的解析做一些展示。首先是func1,这个函数没有任何的参数也没有返回值,所以调用它很简单(这里假设已经做完lua的建立和释放工作):
1// 获取Lua函数 2LuaFunction func = lua.GetFunction("CallLuaFunction.func1"); 3// 执行函数,无返回值,最多支持9个参数 4func.Call();
对于func2的函数,有两个参数和返回值,这里给出了示例中的4种执行方式,这里坑就来了
1// 注:必须进行委托初始化才能执行方式1,3,4 2DelegateFactory.Init(); 3 4func = lua.GetFunction("CallLuaFunction.func2"); 5if (func != null) 6{ 7 // 第一种方式 8 // 执行函数,有返回值,最多支持9个参数1个返回值 9 int num = func.Invoke<int, int, int>(10, 20); 10 Debug.Log(num); 11 12 // 第二种方式 13 num = CallFunc(func); 14 Debug.Log(num); 15 16 // 第三种方式 17 // 向LuaFunction中添加委托,利用委托实现函数返回 18 // 注:此委托ToLua作者并没有给,所以得自己补充 19 // 作者只给了Func<int, int>的委托 20 Func<int, int, int> luaFunc = func.ToDelegate<Func<int, int, int>>(); 21 num = luaFunc(10, 20); 22 Debug.Log(num); 23 24 // 第四种方式 25 // 直接利用LuaState执行函数,最多6个参数1个返回值 26 num = lua.Invoke<int, int, int>("CallLuaFunction.func2", 10, 20, true); 27 Debug.Log(num); 28} 29 30private int CallFunc(LuaFunction func) 31{ 32 // 函数开始 33 func.BeginPCall(); 34 // 传第一个参 35 func.Push(10); 36 // 传第二个参 37 func.Push(20); 38 // 执行函数 39 func.PCall(); 40 // 检查返回值 41 int num = (int)func.CheckNumber(); 42 // 结束函数 43 func.EndPCall(); 44 return num; 45}
再执行到第三种方式的时候会报错,提示no register(未注册),这是为什么呢?因为代码中会注册委托,再进行调用,而Func<int, int, int>并没有被注册,所以报错了。这里我们知道原因了,那就自己加一个注册呗,说干就干:
DelegateFactory脚本的Register方法控制的委托注册在里面加上一行:
DelegateTraits<System.Func<int,int,int>>.Init(factory.System_Func_int_int_int);
同是在DelegateFactory脚本中我们添加System_Func_int_int_int函数:
1public System.Func<int, int, int> System_Func_int_int_int(LuaFunction func, LuaTable self, bool flag) 2{ 3 if (func == null) 4 { 5 System.Func<int, int, int> fn = delegate (int param0, int param1) { return 0; }; 6 return fn; 7 } 8 9 if (!flag) 10 { 11 System_Func_int_int_int_Event target = new System_Func_int_int_int_Event(func); 12 System.Func<int, int, int> d = target.Call; 13 target.method = d.Method; 14 return d; 15 } 16 else 17 { 18 System_Func_int_int_int_Event target = new System_Func_int_int_int_Event(func, self); 19 System.Func<int, int, int> d = target.CallWithSelf; 20 target.method = d.Method; 21 return d; 22 } 23}
到这里我们还缺少System_Func_int_int_int_Event类,也同是在DelegateFactory脚本中添加:
1class System_Func_int_int_int_Event : LuaDelegate 2{ 3 public System_Func_int_int_int_Event(LuaFunction func) : base(func) { } 4 public System_Func_int_int_int_Event(LuaFunction func, LuaTable self) : base(func, self) { } 5 6 public int Call(int param0, int param1) 7 { 8 func.BeginPCall(); 9 func.Push(param0); 10 func.Push(param1); 11 func.PCall(); 12 int ret = (int)func.CheckNumber(); 13 func.EndPCall(); 14 return ret; 15 } 16 17 public int CallWithSelf(int param0, int param1) 18 { 19 func.BeginPCall(); 20 func.Push(self); 21 func.Push(param0); 22 func.Push(param1); 23 func.PCall(); 24 int ret = (int)func.CheckNumber(); 25 func.EndPCall(); 26 return ret; 27 } 28}
坑就这样踩过去了,运行以下,你会发现不会报错并且执行也是正确的了。
接下来是func3函数,一个参数,这个就不用多说了与func2一样的。
之后是func4函数,这里就展示一下func2中的最后一种执行方式,不过没有返回值:
lua.Call<int>("CallLuaFunction.func4", 10, true);
最后的func5就很神奇了,哪里神奇呢?你注意到表名和函数名的连接处用的是":"了么,这里与"."的区别就是会传入self,相当于C#中的this,那又会说了func5不是没有参数么,因为self是隐式的传入,如果你从C#调用它,必须得传这个参数。坑,这里用GetTable解决一下,感觉很麻烦,不过toLua应该是内置了":"函数的形式。
1LuaTable table = lua.GetTable("CallLuaFunction"); 2lua.Call<LuaTable>("CallLuaFunction.func5", table, true); 3Debug.Log(table["a"]);