Unity中 Xlua使用整理(一)
1.安装:
从GitHub上下载Xlua源码
Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. (github.com)
下载Xlua压缩包,并解压将Aseet文件夹中的Xlua和Plugins文件夹复制到Unity工程中
复制到工程之后,菜单栏就会出现Xlua项
新建脚本运行第一个程序:
public class Test : MonoBehaviour{ void Start() { LuaEnv luaenv = new LuaEnv(); luaenv.DoString(\"CS.UnityEngine.Debug.Log(\'hello world\')\"); luaenv.Dispose(); }}
结果:
2.加载Lua脚本
1.读取字符串:
使用DoString函数,输入lua的字符串代码执行
LuaEnv luaenv = new LuaEnv();luaenv.DoString(\"CS.UnityEngine.Debug.Log(\'hello world\')\");luaenv.Dispose();
2.默认loader加载:
使用DoString函数,执行require函数加载Lua脚本
LuaEnv luaenv = new LuaEnv(); luaenv.DoString(\"require \'main\'\"); luaenv.Dispose();
require加载的路径:Resources文件夹、StreamingAssets文件夹、CustomLoader自定义Loader加载。下图中用红色框选的都是require的加载路径
如果放在Resources文件夹,因为Resource只支持有限的后缀,放Resources下的lua文件得加上txt后缀
如果放在StreamingAssets文件夹或者放在与Asset同级目录以及编辑器安装目录,则可以使用.lua作为后缀。
3.自定义loader加载:
通过AddLoader可以注册个回调,该回调参数是字符串,lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,否则则为lua文件的内容。
新建Lua文件夹,存放lua文件
加载代码:
void Start(){ LuaEnv luaenv = new LuaEnv(); luaenv.AddLoader(customLoader); luaenv.DoString(\"require \'main\'\"); luaenv.Dispose();} public byte[] customLoader(ref string filepath) { /*加载Lua代码*/#if UNITY_EDITOR return AssetDatabase.LoadAssetAtPath($\"Assets/Lua/{filepath}.lua.txt\").bytes;#endif return null; }
3.C#调用Lua:
1.使用LuaEnv.Global获取一个全局基本数据类型:
使用Get(string name)方法获取全局变量
C#代码:
int hp = luaenv.Global.Get(\"hp\"); bool isPlay = luaenv.Global.Get(\"isPlay\"); string heroName = luaenv.Global.Get(\"heroName\"); Debug.Log($\"HP:{hp},isPlay:{isPlay},heroName:{heroName}\");
Lua代码:
hp = 100isPlay = falseheroName = \"Owen\"
结果:
2.访问一个全局的table
1.映射到普通class或struct
注:这种方式下xLua会new一个实例,并把对应的字段赋值过去。
table的属性可以多于或者少于class的属性,可以嵌套其它复杂类型, 这个过程是值拷贝,如果class比较复杂代价会比较大,而且修改class的字段值不会同步到table,反过来也不会。这个功能可以通过把类型加到GCOptimize生成降低开销。
使用Get(string name)方法获取全局变量
C#代码:class和struct中的字段访问都是公开的(publish)
public class PlayerInfo { public int id; public string name; public int level; } public struct EventMsg { public int eventId; public string param; } //映射到有对应字段的class,by value PlayerInfo info = luaenv.Global.Get(\"playerInfo\"); EventMsg msg = luaenv.Global.Get(\"eventMsg\"); Debug.Log($\"PlayerInfo:{info.id},{info.name},{info.level}\"); Debug.Log($\"EventMsg:{msg.eventId},{msg.param}\");
Lua代码:
playerInfo = { id = 1001, name = \"Owen\", level = 100} eventMsg = { eventId = 101, param = \"aaaaaaaaaaa\"}
结果:
2.映射到一个interface
需要先生成代码(如果没生成代码会抛InvalidCastException异常),
代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数
C#代码:
[CSharpCallLua] public interface IPlayerPosition { int x { get; set; } int y { get; set; } int z { get; set; } void add(int x, int y, int z); void sub(int x,int y ,int z); } public void GetInterface() { //映射到interface实例,by ref,这个要求interface加到生成列表,否则会返回null,建议用法 IPlayerPosition pPos = luaenv.Global.Get(\"playerPosition\"); Debug.Log($\"{pPos.x},{pPos.y},{pPos.z}\"); pPos.add(10,1,23); Debug.Log($\"{pPos.x},{pPos.y},{pPos.z}\"); pPos.sub(1,-1,11); Debug.Log($\"{pPos.x},{pPos.y},{pPos.z}\"); }
Lua代码:
playerPosition = { x = 10, y = 0, z = 10}function playerPosition:add(x0,y0,z0) self.x = self.x + x0 self.y = self.y + y0 self.z = self.z + z0endfunction playerPosition:sub(x0,y0,z0) self.x = self.x - x0 self.y = self.y - y0 self.z = self.z - z0end
结果:
注意:
C#侧和Lua侧的属性、方法、字段必须名字一样大小写一致。如果将C#侧的add方法写成Add,就会报错,如下:
3.映射到Dictionary,List
lua侧table和C#侧的key和value类型必须一致,List只会映射table的数组部分,Dictionary只会映射非数组部分。
C#代码:
//映射到Dictionary,by value Dictionary d = luaenv.Global.Get<Dictionary>(\"Item\"); Debug.Log(d.Count); //映射到List,by value List l = luaenv.Global.Get<List>(\"Item\"); Debug.Log(l.Count);
Lua代码:
Item = { 10001,1002,content = 10000,20001,3300,}
结果:
4.映射到LuaTable类
这种方式好处是不需要生成代码,但也有一些问题,慢,比2要慢一个数量级,没有类型检查。
访问字段属性时需要用Get(string name)方法访问。
C#侧代码:
//映射到LuaTable,by refLuaTable info= luaenv.Global.Get(\"playerInfo\");Debug.Log($\"PlayerInfo:{info.Get(\"id\")},{info.Get(\"name\")},{info.Get(\"level\")}\");
[CSharpCallLua] public delegate void AddMethod(LuaTable self,int x,int y, int z); [CSharpCallLua] public delegate Action addAc(LuaTable t, int x, int y, int z); //映射到LuaTable,by ref LuaTable info= luaenv.Global.Get(\"playerPosition\"); AddMethod LD = info.Get(\"add\"); LD(info,2, 3, 4); Debug.Log($\"playerPosition :{info.Get(\"x\")},{info.Get(\"y\")},{info.Get(\"z\")}\"); var ac = info.Get<Action>(\"add\"); ac(info, 2, 3, 4); Debug.Log($\"playerPosition :{info.Get(\"x\")},{info.Get(\"y\")},{info.Get(\"z\")}\"); var aac = info.Get(\"add\"); aac(info, 2, 3, 4); Debug.Log($\"playerPosition :{info.Get(\"x\")},{info.Get(\"y\")},{info.Get(\"z\")}\");
//映射到LuaTable,by ref LuaTable info= luaenv.Global.Get(\"playerPosition\"); var LF = info.Get(\"add\"); LF.Call(info,1,2,3); Debug.Log($\"{info.Get(\"x\")},{info.Get(\"y\")},{info.Get(\"z\")}\");
结果:
注意:在LuaTable中获取对象中方法需要用.Get(\"\"),或者.Get<Action>(\"\")或者使用.Get(\"\")。
3.访问一个全局的function
注意:用该类访问Lua函数会有boxing,unboxing的开销,为了性能考虑,需要频繁调用的地方不要用该类。建议通过 table.Get 获取一个 delegate 再调用(假设 ABCDelegate 是 C# 的一个 delegate)。在使用使用 table.Get 之前,请先把ABCDelegate加到代码生成列表。
1.映射到delegate:
使用delegate获取lua方法时需要先生成代码
[CSharpCallLua] public delegate void test1(int x); [CSharpCallLua] public delegate Action test2(int x); test1 LD = luaenv.Global.Get(\"test\"); LD(100); var ac = luaenv.Global.Get<Action>(\"test\"); ac(0); var aac = luaenv.Global.Get(\"test\"); aac(19);
Lua代码:
function test(a) print(a)end
结果:
2.映射到LuaFunction
var lf = luaenv.Global.Get(\"test\");lf.Call(10);
结果:
4.使用建议(官方手册)
-
访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
-
如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。
4.Lua调用C#:
1.new C#对象
lua里头没有new关键字;所有C#相关的都放到CS下,包括构造函数,静态成员属性、方法;
使用lua new一个C#侧的GameObject对象
Lua代码:
local newGameObj = CS.UnityEngine.GameObject()
结果:
xlua支持重载,使用重载new一个构造函数带参的GameObject
Lua代码:
local newGameObj = CS.UnityEngine.GameObject(\"test\")
结果:
2.访问C#静态属性,方法
c#代码:使用打标签(LuaCallCSharp)需要先生成代码再使用,如果不生成代码会使用性能较低的反射方式来访问。
//此类是否是static都可以 [LuaCallCSharp] public static class GameCfg { public static int times = 0; public static string url = \"www.baidu.com\"; public static void CalcValue() { Debug.Log(\"cccccccc\"); } }
1.获取静态属性:
Lua代码:
local url = CS.GameCfg.urlprint(url)
结果:
2.写入静态属性:
Lua代码:
CS.GameCfg.url = \"zzzzzz\"print(CS.GameCfg.url)
结果:
3.调用静态方法:
Lua代码:
CS.Test.GameCfg.CalcValue()
结果:
3.访问C#成员属性,方法
C#侧代码:
[LuaCallCSharp] public class Actor { public int id; public float hp; public string name; public float baseAtk; public float baseDef; public virtual void callAtk() { Debug.Log(\"atk\"); } public virtual void callWalk() { Debug.Log(\"walk\"); } public void PrintActorInfo() { Debug.Log($\"ActorInfo:{id},{hp},{name},{baseAtk},{baseDef}\"); } public void Test2(int v, ref int c, out int d) { d = v + c; c++; } public void Test2(int v, ref int c) { c += v; } public void Test(int v , Action action,ref int c,out int d,out Action testFunc) { d = v + c; c++; action(); testFunc = (c,d) => { Debug.Log($\"{c},{d}\"); }; } } [LuaCallCSharp] public class Player : Actor { public float Atk; public float Def; public delegate void testDelegate(); public override void callAtk() { Debug.Log(\"Player Atk\"); } public override void callWalk() { Debug.Log(\"Player walk\"); } public bool testPass; public static Player operator +(Player a, Player b) { Player player = new Player(); player.Atk = a.Atk + b.Atk; return player; } public void TestParam(int a, params int[] p) { Debug.Log($\"a:{a},param len:{p.Length}\"); } public void TestDefualtValue(int a, string b ,bool c,Player p) { Debug.Log($\"a:{a},b:{b},c:{c},p:{p}\"); } } [LuaCallCSharp] public class Monster : Actor { public event Action testEvent; public override void callAtk() { Debug.Log(\"Monster atk\"); } public override void callWalk() { Debug.Log(\"Monster walk\"); } public MonsterType getMonsterType(int value) { value = Mathf.Min(++value, 3); return (MonsterType)value; } }
1.调用成员方法:
调用成员方法,第一个参数需要传该对象,建议用冒号语法糖。
Lua代码:
local actorObj = CS.Test.Actor()actorObj:callAtk()actorObj:callWalk()--或者使用如下方式执行成员方法--actorObj.callAtk(actorObj)--actorObj.callWalk(actorObj)
结果:
2.写入成员属性:
Lua代码:
local actorObj = CS.Test.Actor()actorObj:PrintActorInfo()actorObj.id = 1001actorObj.hp = 100actorObj.name = \"wukong\"actorObj.baseAtk = 500.6actorObj.baseDef = 800actorObj:PrintActorInfo()
结果:
3.获取成员属性:
Lua代码:
local actorObj = CS.Test.Actor()actorObj.id = 1001actorObj.hp = 100actorObj.name = \"wukong\"actorObj.baseAtk = 500.6actorObj.baseDef = 800print(actorObj.id)print(actorObj.hp)print(actorObj.name)print(actorObj.baseAtk)print(actorObj.baseDef)actorObj:PrintActorInfo()
结果:
4.父类属性,方法
Xlua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类实例)访问基类的成员属性,成员方法。
Lua代码:
local playerObj = CS.Test.Player()playerObj.id = 1001playerObj.hp = 100playerObj.name = \"wukong\"playerObj.baseAtk = 500.6playerObj.baseDef = 800playerObj:PrintActorInfo()
结果:
5.参数的输入输出属性(out,ref)
Lua调用侧的参数处理规则:C#的普通参数和带有ref参数算输入形参,带有out的参数不算形参,然后从左往右对应lua 调用侧的实参列表;
Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)、带有ref的参数和带有out参数算返回值,然后从左往右对应lua的多返回值。
Lua代码:
local actorObj = CS.Test.Actor()local c,d = actorObj:Test2(10,1)print(c,d)
结果:
6.重载方法
直接通过不同的参数类型进行重载函数的访问,Xlua只一定程度上支持重载函数的调用,调用顺序是生成代码中排前面的那个最先调用,也就是同样参数数量的方法,在生成代码中最先生成的符合参数数量的函数被执行。
Lua代码:
local actorObj = CS.Test.Actor()local c,d = actorObj:Test2(10,1)print(c,d)
结果:
因为C#侧的两个参数的Test2的重载方法最先生成代码,所以执行的是两个参数的Test2
7.操作符
支持的操作符有:+,-,*,/,==,unary-(++,--,+,-,!,~,(T)x,await,&x *x),<,<=, %,[]
C#代码:
public static Player operator +(Player a,Player b) { Player player = new Player(); player.Atk = a.Atk + b.Atk; return player; }
Lua代码:
--操作符local player1 = CS.Test.Player()player1.Atk = 100local player2 = CS.Test.Player()player2.Atk = 210print((player1+player2).Atk)
结果:
8.参数带默认值的方法
和C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会用默认值补上。
C#代码:
public void TestDefualtValue(int a, string b ,bool c,Player p) { Debug.Log($\"a:{a},b:{b},c:{c},p:{p}\"); }
Lua代码:
local p = CS.Test.Player()p:TestDefualtValue(2,\"aaaaaaaa\",false)p:TestDefualtValue(2,\"aaaaaaaa\")p:TestDefualtValue(2)
结果:
9.可变参数方法
C#代码:
public void TestParam(int a, params int[] p) { Debug.Log($\"a:{a},param len:{p.Length}\"); }
Lua代码:
local p = CS.Test.Player()p:TestParam(10,\"hp\",false,{\"ccc\",1,2})p:TestParam(10,\"hp\",false)
结果:
10.使用Extension methods,泛型(模版)方法(本身不支持,使用扩展方法实现)
扩展方法:扩展方法 - C# | Microsoft Learn
C#代码:
public static class ActorExtendMethod{ public static void TestExtend(this Player p) { Debug.Log(\"这是Player的扩展方法\"); } public static void TestExtend(this Actor a) { Debug.Log(\"这是Actor的扩展方法\"); } public static void TestExtend(this Monster m) { Debug.Log(\"这是Monster的扩展方法\"); }}
Lua代码:
local c1 = CS.Test.Actor()local c2 = CS.Test.Player()local c3 = CS.Test.Monster()c1.TestExtend()c2.TestExtend()c3.TestExtend()
结果:
11.枚举类型
枚举类支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换
C#代码:
[LuaCallCSharp]public enum MonsterType{ None, Normal, melee, remote}
Lua代码:
local m = CS.Test.Monster()print(m:getMonsterType(2))print(CS.MonsterType.__CastFrom(1))
结果:
12.delegate使用(调用,+,-)
C#的delegate调用:和调用普通lua函数一样,+操作符:对应C#的+操作符,把两个调用串成一个调用链,右操作数可以是同类型的C# delegate或者是lua函数。-操作符:和+相反,把一个delegate从调用链中移除。
C#代码:
public delegate void testDelegate2(string content); public testDelegate2 test2 = (string content) => { Debug.Log(content); };
Lua代码:
local p = CS.Test.Player()p.test2 = p.test2 + testD p.test2(\"add\")p.test2 = p.test2 - testDp.test2(\"remove\")
结果:
注:由于使用的是多播委托,所以初始时需要给多播委托一个值,后续增加委托才能用”+“/”-“,如果直接使用”+“,
public delegate void testDelegate2(string content); public testDelegate2 test2;
会报如下的错误
13.event
C#代码:
public event Action testEvent;public void testCALL(string content){ testEvent?.Invoke(content);}
Lua代码:
local function testD(content) print(\"LUA TESTD....\"..content)endlocal m = CS.Test.Monster()m:testEvent(\'+\', testD)m:testCALL(\"add\");m:testCALL(\"remove\")m:testEvent(\'-\', testD)
结果:
注:
event使用 对象:事件名(+,方法),事件名(-,方法)来注册事件,所以在调用的时候不能使用对象:事件名()来执行事件,如果使用就会报下面的错,
因为使用对象:事件名()相当于参数只传了一个对象自身,\"+\"/\"-\"和方法并没有传入,得到的gen_param_count就是1,然后gen_delegate就是null,然后就会出现上边的报错。
由上,要执行事件可以再写一个函数来执行事件函数。
Lua代码中不要使用中文,
会报以下错:
14.64位整数支持
Lua53版本64位整数(long,ulong)映射到原生的64位整数,而luajit版本,相当于lua5.1的标准,本身不支持64位,xlua做了个64位支持的扩展库,C#的long和ulong都将映射到userdata,支持在lua里头进行64位的运算、比较、打印,支持和lua number的运算、比较,在64扩展库中,实际上只有int64,ulong也会先强转成long再传递到lua,而对ulong的一些运算,比较,采取和java一样的支持方式。
15.C#复杂类型和table的自动转换
对于一个有无参构造函数的C#复杂类型,在lua侧可以直接用一个table来代替,该table对应复杂类型的public字段有相应字段即可,支持函数参数传递,属性赋值等。
C#代码:
[LuaCallCSharp]public class TestObj{ public void test(testStruct t) { Debug.Log($\"{t.a},{t.c}\"); }}public struct testStruct{ public int a; public string c;}
Lua代码:
local m = CS.TestObj()m:test({a=10,c=\"ccccccc\"})
结果:
16.获取类型
Lua代码:
print(typeof(CS.Test.Monster))
结果:
17.强制类型转换
lua没类型,所以不会有强类型语言的“强转”。
Lua代码:
cast(calc, typeof(CS.Tutorial.Calc))
代码:
public class RawObjectTest : MonoBehaviour { public static void PrintType(object o) { Debug.Log(\"type:\" + o.GetType() + \", value:\" + o); } void Start() { LuaEnv luaenv = new LuaEnv(); //直接传1234到一个object参数,xLua将选择能保留最大精度的long来传递 luaenv.DoString(\"CS.XLuaTest.RawObjectTest.PrintType(1234)\"); //通过一个继承RawObject的类,能实现指明以一个int来传递 luaenv.DoString(\"CS.XLuaTest.RawObjectTest.PrintType(CS.XLua.Cast.Int32(1234))\"); luaenv.Dispose(); } }
结果:
什么时候用到?
有的时候第三方库对外暴露的是一个interface或者抽象类,实现类是隐藏的,这样我们无法对实现类进行代码生成。该实现类将会被xlua识别为未生成代码而用反射来访问,如果这个调用是很频繁的话还是很影响性能的,这时我们就可以把这个interface或者抽象类加到生成代码,然后指定用该生成代码来访问。
注意:
1.如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能。
local GameObject = CS.UnityEngine.GameObjectGameObject.Find(\'helloworld\')
2. 像上边这种Lua调用C#中某个类中的属性、字段、方法,调用方式是CS.命名空间.类名,如果没有命名空间则就是CS.类名,要注意的是,如果这个类包含在一个类中,就需要CS.类名.类名,比如
GameCfg包含在Test类中,如果用如下Lua代码,就会出现这个类的获取错误的问题
print(CS.GameCfg.url)CS.GameCfg.url = \"zzzzzz\"print(CS.GameCfg.url)CS.GameCfg.CalcValue()
结果:
查看GameCfg生成的代码如下:
是通过获取Test.GameCfg的类型来注册的,所以Lua代码需要修改为:
print(CS.Test.GameCfg.url)CS.Test.GameCfg.url = \"zzzzzz\"print(CS.Test.GameCfg.url)CS.Test.GameCfg.CalcValue()
结果:
5.HotFix:
使用xLua 的代码逻辑替换掉原有的 C# 程序逻辑, 以实现热补丁。不支持静态构造函数。目前只支持 Assets 下代码的热补丁,不支持引擎,C# 系统库的热补丁。
1.使用
1.开启HotFix
按照官方文档的步骤:
执行HotFix Inject In Editor时,
会出现以下错误:
解决方案:将Xlua源工程中Tools文件夹复制到现工程于Asset文件同级目录即可
----->
如果
2.开始使用
C#代码:
public class HotFixTest : MonoBehaviour{ LuaEnv luaenv = null; Action onDestroy = null; void Start() { TestHotFix testHotFix = new TestHotFix(); luaenv = new LuaEnv(); luaenv.AddLoader(customLoader); testHotFix.TestPrint(); luaenv.DoString(\"require \'main\'\"); testHotFix.TestPrint(); luaenv.Dispose(); } public byte[] customLoader(ref string filepath) { /*加载Lua代码*/#if UNITY_EDITOR return AssetDatabase.LoadAssetAtPath($\"Assets/Lua/{filepath}.lua.txt\").bytes;#endif return null; }}[Hotfix]public class TestHotFix{ public void TestPrint() { Debug.Log(\"Before Hotfix\"); }}
Lua代码:
xlua.hotfix(CS.TestHotFix, \'TestPrint\', function(self) print(\'After HotFix\')end)
结果:
在释放LuaEnv时出现了一个错误,官方文档FQA解答如下:
使用Xlua提供的工具,并在lua侧增加一个函数,并映射到c#端
C#侧:
Lua侧:
可以查看到
也就是HotFix打补丁的函数和lua增加的函数OnDestroy函数占用,所以将二者释放即可。
注: 下图中使用的util文件就是在文件夹下的文件,为为了方便我将它移动到这里
2.建议
对所有较大可能变动的类型加上 Hotfix 标识;
建议用反射找出所有函数参数、字段、属性、事件涉及的 delegate 类型,标注 CSharpCallLua;
业务代码、引擎 API、系统 API,需要在 Lua 补丁里头高性能访问的类型,加上 LuaCallCSharp;
引擎 API、系统 API 可能被代码剪裁调(C#无引用的地方都会被剪裁),如果觉得可能会新增 C# 代码之外的 API 调用,这些 API 所在的类型要么加 LuaCallCSharp,要么加 ReflectionUse
6.类型映射
1.基本数据类型
sbyte,byte,short,ushort,int,uint,double,char,float
2.复杂数据类型
LuaTable
C#侧指明从Lua侧输入(包括C#方法的输入参数或者Lua方法的返回值)LuaTable类型,则要求Lua侧为table。或者Lua侧的table,在C#侧未指明类型的情况下转换成LuaTable。
LuaFunction
C#侧指明从Lua侧输入(包括C#方法的输入参数或者Lua方法的返回值)LuaFunction类型,则要求Lua侧为function。或者Lua侧的function,在C#侧未指明类型的情况下转换成LuaFunction。LuaUserData
对应非 C# Managered 对象的lua userdata。
class 或者 struct 的实例
从C#传一个class或者struct的实例,将映射到Lua的userdata,并通过__index访问该userdata的成员 C#侧指明从Lua侧输入指定类型对象,Lua侧为该类型实例的userdata可以直接使用;如果该指明类型有默认构造函数,Lua侧是table则会自动转换,转换规则是:调用构造函数构造实例,并用table对应字段转换到c#对应值后赋值各成员。
method, delegate
成员方法以及delegate都是对应lua侧的函数。 C#侧的普通参数以及引用参数,对应lua侧函数参数;C#侧的返回值对应于Lua的第一个返回值;引用参数和out参数则按序对应于Lua的第2到第N个参数。
参考链接:
介绍 — XLua (tencent.github.io)