所有的lua代码都是在lua虚拟机上跑的,一个LuaEnv实例对应一个lua虚拟机。
出于开销考虑,官方建议全局唯一:
XLua.LuaEnv luaenv = new XLua.LuaEnv();
一、lua文件的执行和加载
LoadString:加载一个代码块,但不执行,返回类型可以指定为一个delegate或者一个LuaFunction
T LoadString(string chunk, string chunkName = "chunk", LuaTable env = null)
DoString:执行一个代码块
object[] DoString(string chunk, string chunkName = "chuck", LuaTable env = null)
例如:
luaenv.DoString("content")
content可以是lua代码,但更常见的用法是去包含lua文件,使用require函数:
DoString("require 'byfile'")
require实际上是调用loader来加载文件,xlua原生的loader支持加载Resources文件夹下的.txt文件。我们也可以自定义loader来加载特定路径格式的文件。
public delegate byte[] CustomLoader(ref string filepath);
public void LuaEnv.AddLoader(CustomLoader loader)
使用LuaEnv.AddLoader方法添加loader,例:
luaenv.AddLoader((ref string filename) =>
{
if (filename == "InMemory")
{
string script = "return {ccc = 9999}";
return System.Text.Encoding.UTF8.GetBytes(script);
}
return null;
});
这时LuaEnv就会根据我们自定义的loader来加载文件了。
ps:官方推荐加载一个lua文件,然后在这个lua文件中加载其他文件。
二、C#调用Lua
首先官方案例中这样推荐:
为每个MonoBehaviour设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突
LuaEnv luaEnv = new LuaEnv();
LuaTable scriptEnv;
scriptEnv = luaEnv.NewTable();
LuaTable meta = luaEnv.NewTable();
meta.Set("__index", luaEnv.Global);
scriptEnv.SetMetaTable(meta);
meta.Dispose();
由于设置了独立的luaTable,之后的操作都在scriptEnv中进行。
c#访问lua,无论是访问基本类型、function还是table
本质上都是使用xLua提供的一个方法,访问lua中的数据并映射到c#中的指定类型:
public void Get<TKey, TValue>(TKey key, out TValue value)
访问基本类型:
var basicType=scriptEnv.Get<TBasic>("Name")
访问function:
i.映射到delegate
推荐使用的方式,性能好很多,而且类型安全,缺点是要生成代码。
Action luaStart;
scriptEnv.Get("start", out luaStart);
ii.映射到LuaFunction
LuaFunction是xLua提供的一个类,优缺点刚好和第一种相反。 使用也简单,LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。
LuaFunction func = scriptEnv.Get<LuaFunction>("FuncName");
object[] os= func.Call(p1, p2);
访问table:
i.映射到普通class或struct
table的属性可以多于或者少于class的属性。可以嵌套其它复杂类型。 要注意的是,这个过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会。
Test test = scriptEnv.Get<Test>("Name");
ii.映射到一个interface
这种方式为引用方式的映射,依赖于生成代码,代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。
ITest test = scriptEnv.Get<ITest>("Name");
iii.映射到Dictionary<>,List<>
不想定义class或者interface的话,可以考虑用这个,前提table下key和value的类型都是一致的。
Dictionary<T1, T2> dict = scriptEnv.Get<Dictionary<T1, T2>>("Name")
iv.映射到LuaTable类
不需要生成代码,但也有一些问题,比如慢,比方式2要慢一个数量级,比如没有类型检查,只能以k-v的形式使用
LuaTable tab = scriptEnv.Get<LuaTable>("Name");
print(tab.Get<T>("Name"));
使用建议:
-
访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
-
如果lua侧的实现部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。
PS:如果希望把一个lua函数适配到一个C# delegate,或者把一个lua table适配到一个C# interface,该delegate或者interface需要加上CSharpCallLua属性
三、Lua调用C#
调用方法与在c#中类似,但有以下区别:
1.lua中没有new关键字
2.所有c#相关的都要放在CS下,访问任何成员(类、函数、方法等)都要以CS.Namespace.Any的形式
3.调用非静态方法时常常需要传入方法所在类的实例为第一参数
new一个对象:
local newGameObj = CS.UnityEngine.GameObject()
获取类型:
typeof(CS.UnityEngine.GameObject)
访问静态属性、方法:
属性:
CS.UnityEngine.Time.deltaTime
方法:
CS.UnityEngine.GameObject.Find('helloworld')
小技巧:如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能
访问成员属性、方法
属性:
obj.test
方法:
obj.Test(obj,obj.test)
//冒号语法糖
obj:Test(obj.test)
ref、out关键字:
Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,然后从左往右对应lua 调用侧的实参列表;(因为out关键字修饰的变量在方法内部要被初始化,所以省略输入)
Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。
//c#侧
public class Test
{
public int TestFunc(int a,ref int b,out int c)
{
...
}
}
//lua侧调用
local test=Test()
local a,ref0,out0 = test:TestFunc(0,0)
重载:
xlua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#丰富,存在一对多的情况,比如C#的int,float,double都对应于lua的number,如果一个函数有多个int、float、double类型的重载,并且数量相等,那么第一行将无法区分开来,只能调用到其中一个(生成代码中排前面的那个)。还有一种情况是当函数重载中存在params关键字时,也有可能会导致上述问题。
扩展方法:
只要在c#中定义了,lua中可以直接使用。
枚举:
和c#中枚举使用方法一样。
同时提供了一个_CastForm方法可以实现从值类型到枚举类型的转换
CS.NameSpace.TestEnum.__CastFrom(1)
CS.NameSpace.TestEnum.__CastFrom('Test1')
泛型方法:
支持部分情况
- mono下可以
- il2cpp下,如果泛型参数是引用类型可以
- il2cpp下,如果泛型参数是值类型,C#那有用同样的泛型参数调用过
通过提供的xlua.get_generic_method方法调用。
//测试类
public class Test
{
private int a = 100;
//泛型方法
public int GenericMethodTest<T>(T t)
{
Debug.Log(t);
Debug.Log(typeof(T));
return a;
}
}
//实例化Test类
local test=CS.Test.Test()
//取得泛型方法 CS.Test为命名空间
local testGenericFunc=xlua.get_generic_method(CS.Test.Test,"GenericMethodTest")
//为泛型方法指定类型
local realFunc=testGenericFunc(typeof(CS.System.Int32))
//调用 若泛型方法为静态,则无需传第一个实例参数
realFunc(test,5)
协程:
调用unity中的协程
//包含xlua提供的工具
local util = require 'xlua.util'
//使用util.cs_generator来生成IEnumerator
local t_fun = util.cs_generator(function()
for i=1,10 do
print(i)
coroutine.yield(CS.UnityEngine.WaitForSeconds(1))
end
end)
//开启协程 self是一个传入的monobehaviour脚本
self:StartCoroutine(t_fun)
PS:除基本类型外,所有希望lua访问的c#代码都要加上LuaCallCSharp属性,这样xLua才会生成代码。否则会尝试以效率较低的反射方式访问。
xLua相关配置详见:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/configure.md