目录
0.引言
1.访问基本数据类型全局变量
2.访问全局的table
2.1.映射到class或struct
2.2.映射到一个interface
2.3.映射到List、Dictionary
2.4.映射到LuaTable类
3.访问全局函数
3.1映射到delegate
3.2映射到LuaFunction
4.官方使用建议
本文是个人学习xLua中C#访问Lua的一些知识点总结。参考教程的是官方教程:
xLua/Assets/XLua/Doc/XLua教程.md at master · Tencent/xLua
通过Lua解析器的Global表中的Get
示例:
luaenv.Global.Get("a")
luaenv.Global.Get("b")
luaenv.Global.Get("c")
同样也是用上述的Get方法。那么我们table应该映射到c#中的什么类型呢?
定义一个class,有对应于table的字段的public属性,而且有无参数构造函数即可。xLua会帮你new一个对应类实例,并把对应的字段赋值过去。
示例:
student={
name="骰子",
age=20,
id=5
}
class Student
{
public string name;
public int age;
public int id;
}
public class Test : MonoBehaviour
{
void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoString("require('Main')");
LuaTable _G = LuaMgr.Instance._G;
Student student = _G.Get("student");
print(student.name); //"骰子"
print(student.age); //20
print(student.id); //5
}
}
也就是说xLua会new 一个Student实例来,并把table表中字段赋值到实例中对应的字段中去。
这里使用的LuaMgr是自己封装的一个管理lua解析器的类,可以去这篇文章查看:
基于xLua实现一个简单的Lua解析器管理器-CSDN博客
table的属性可以多于或者少于class的属性,并且可以嵌套其它复杂类型,而且table映射到类或结构体是值拷贝,这意味着修改class的字段值不会同步到table,反过来也不会。
实例:
student={
name="骰子",
age=20,
id=5,
testMore="多了"--测试table中有class没有的字段会不会报错
,
score={
math=80,
english=90,
chinese=95
}--测试嵌套table
}
struct Score
{
public int math;
public int chinese;
public int english;
}
class Student
{
public string name;
public int age;
public int id;
public string testLess = "少了";//测试class多了table没有的字段会不会报错
public Score score;//测试嵌套table可不可以接收到
}
public class Test : MonoBehaviour
{
void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoString("require('Main')");
LuaTable _G = LuaMgr.Instance._G;
Student student = _G.Get("student");
print(student.name); //输出骰子
print(student.age); //输出20
print(student.id);//输出5
print(student.testLess);//输出"少了",测试说明Student哪怕有table没有的字段也不会报错
print(student.score.math);//输出80
print(student.score.chinese);//输出95
print(student.score.english);//输出90
//测试说明嵌套table类型也可以直接接收
LuaMgr.Instance.DoString("student.name='Cheer'"); //修改lua中的table数据
LuaMgr.Instance.DoString("print(student.name)");//输出Cheer
print(student.name);//输出骰子
//这说明改变lua中的table数据不会改变student的数据,反过来也是这样的
//这个过程是值拷贝
}
}
根据上述示例测试,证实了上述结论。
注意:因为这个过程是值拷贝,所以如果class比较复杂代价会比较大。
接口不允许有成员变量,所以是用属性来进行table字段的接收。
这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。
声明的interface必须要加[CSharpCallLua]特性,并且一旦改变了interface结构就需要重新生成代码,才能使用。
table的属性可以多于或者少于interface的属性,并且可以嵌套其它复杂类型。
嵌套的接口类型也需要加[CSharpCallLua]特性,也需要重新生成代码。
嵌套的非接口类型,例如类、结构体无需遵循接口映射的规则。
table映射到interface,是引用拷贝。
这里我个人注意到一个点,貌似[CSharpCallLua]特性修饰的delegate或是interface必须是public的,否则生成代码无效。
这种映射是xLua官方比较推荐使用的。
示例:
public struct Score
{
public int math;
public int chinese;
public int english;
}
[CSharpCallLua]
public interface _GameObject
{
int ID
{
get;set;
}
string Name
{
get;
set;
}
string More
{
get;set;
}
Score score { get; set; }
}
public class Test : MonoBehaviour
{
void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoString("require('Main')");
LuaTable _G = LuaMgr.Instance._G;
#region 映射到interface
LuaMgr.Instance.DoString(@"
gameObject={
Name = 'Cheer',
ID= 105,
positionX=1.5,
positionY=1.5,
score={
math=90,
english=0,
chinese=100
}
}
");
_GameObject gameObject = _G.Get<_GameObject>("gameObject");
print(gameObject.ID);//105
print(gameObject.Name);//Cheer
print(gameObject.More);//Null
print(gameObject.score.math);//输出90
print(gameObject.score.chinese);//输出100
print(gameObject.score.english);//输出0
//table映射到interface是值拷贝吗?
//测试一下
LuaMgr.Instance.DoString("gameObject['Name']='骰子'");
print(gameObject.Name);//输出骰子 这说明改变table表,接口实例也跟着发生了改变
gameObject.Name = "_cheer";
LuaMgr.Instance.DoString("print(gameObject.Name)");//输出_cheer 这说明改变接口实例,table表也跟着发生了改变
//这说明table映射到interface是引用拷贝。
#endregion
}
}
对于table模拟的数组,可以用List
如果table存储不定类型,可以用List
table映射到List,是值拷贝过程。
示例:
LuaMgr.Instance.DoString(@"
testList={1,2,3,4,5,6}
testList2={'哈吉米',2,false,4,1.2}
");
List list = _G.Get>("testList");
foreach (int i in list) print(i);//输出1 2 3 4 5 6 映射正常
List
字典也是类似的用法。
如果table的键存储了不定类型,用Dictionary
如果table的值存储了不定类型,用Dictionary
table映射到字典,是值拷贝过程。
示例:
LuaMgr.Instance.DoString(@"
testDic={
['1']=1,
['2']=2,
['3']=3,
}
testDic2={
name = 12,
[false]='哇哇哇',
['1']=false
}
");
Dictionary testDic = _G.Get>("testDic");
foreach(string key in testDic.Keys)
{
print(key + ":" + testDic[key]);//输出 1:1 2:2 3:3 映射正常
}
Dictionary
不想定义class或者interface的话,可以考虑用这个,前提table下key和value的类型都是一致的,否则用object装会引起装箱拆箱问题。
table映射到LuaTable也是引用拷贝,其语法简单且不需要生成代码。
实例:
LuaMgr.Instance.DoString(@"
score={
math=90,
english=0,
chinese=100
}
");
LuaTable luaTable = _G.Get("score");
print(luaTable.Get("math"));//输出90
print(luaTable.Get("english"));//输出0
print(luaTable.Get("chinese"));//输出100
luaTable.Set("math", 55);//设置table值
LuaMgr.Instance.DoString("print(score.math)");//lua中的score.math也发生了改变
//这种映射是引用拷贝.
这种方式虽然有以上好处,但也有一些问题(官方不推荐使用),比如慢——比映射到一个interface要慢一个数量级;比如没有类型检查。
注意:LuaTable需要手动Dispose,或者调用lua解析器的Tick释放垃圾,否则会一直占用内存造成内存泄漏。
仍然是用Get方法,不同的是类型映射。
这种是官方建议的方式,性能好很多,而且类型安全。
缺点是要手动加给委托加[CSharpCallLua]特性并生成代码(如果没生成代码会抛InvalidCastException异常)。
声明规则:delegate的输入参数对应function的参数;多返回值从左往右映射到C#的输出参数(包括返回值、out参数、ref参数)。
支持类型:各种复杂类型,含out、ref修饰的,甚至可返回另一个delegate。
delegate的使用就更简单了,直接像个函数那样用就可以了。
示例:
[CSharpCallLua]
public delegate int luaDelegate(int a, int b, out int Sum);
void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoString(@"
function AddAndReturn(a, b)
local sum = a + b
return sum, sum * 2
end
");
//映射到delegate
luaDelegate _luaDelegate = LuaMgr.Instance._G.Get("AddAndReturn");
int Sum;
int sum =_luaDelegate(2,3, out Sum);
print(sum+"and"+Sum);//输出6and12
}
这种方式的优缺点刚好和第一种相反。 使用也简单,LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。
//映射到LuaFunction
LuaFunction luaFunction = LuaMgr.Instance._G.Get("AddAndReturn");
object[] results = luaFunction.Call(new object[]{2,3 });
for(int i=0; i
访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。