xLua是由腾讯维护的一个开源项目,xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在,腾讯已经将xLua开源到GitHub。其git地址是:https://github.com/Tencent/xLua。
C#调用Lua的原理:在C#代码中创建一个Lua解释器,用以解释执行Lua代码
获取Lua中的变量:Lua中的全局变量都保存在G表中,通过变量名可以获取Lua中的变量值
打标签:[CSharpCallLua],获取lua中部分类型时,需要给C#中用来接收的类型打上该标签
执行字符串:执行字符串中的lua代码
luaEnv.DoString("print('hello world')")
加载Lua文件:用lua的require函数加载
luaEnv.DoString(“require 'byfile'")
当我们require lua脚本时,默认路径是在Resources中,xlua内部是通过Resources.Load加载,无法读取.lua后缀的文件,只能读取.txt后缀,而且当游戏发布出去时,Resouces文件夹是不可读的,所以我们要自定义lua文件的加载路径。xlua中提供的AddLoader方法,允许我们自定义加载lua文件的规则,当我们用require执行lua脚本时,会首先区自定义的loader中执行(可以添加多个自定义loader),当所有loader都找不到时,xlua会调用默认的loader,也就是去Resources目录下找。
代码示例
public class Test : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//创建lua解释器
LuaEnv env = new LuaEnv();
//添加自定义Loader
env.AddLoader(MyCustomLoader);
env.DoString("require('Main')");
}
//自定义loader方法,以字节数组形式返回读取到的lua代码
private byte[] MyCustomLoader(ref string filePath)
{
//自定义加载路径和后缀
string path = Application.dataPath + "/Lua/" + filePath + ".lua";
Debug.Log(path);
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("重定向失败");
}
return null;
}
}
在使用LuaEnv之前,需要先进行一些列的初始化操作(生成LuaEnv,添加自定义Loader,加载Main函数),编写一个LuaMgr类以集成这些操作,方便使用
///
/// lua解析器管理器,集成对lua的操作
///
public class LuaMgr : BaseManager<LuaMgr>
{
private LuaEnv luaEnv;
public LuaTable Global
{
get
{
return luaEnv.Global;
}
}
public void Init()
{
if (luaEnv != null)
{
return;
}
luaEnv = new LuaEnv();
luaEnv.AddLoader(MyLoader);
luaEnv.AddLoader(MyCustomABLoader);
}
public void DoLuaFile(string fileName)
{
string str = string.Format("require('{0}')", fileName);
DoString(str);
}
//自动执行
private byte[] MyLoader(ref string filePath)
{
string path = Application.dataPath + "/Lua/" + filePath + ".lua";
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("文件重定向失败,文件名:"+filePath);
}
return null;
}
private byte[] MyCustomABLoader(ref string filePath)
{
TextAsset lua= ABMgr.GetInstance().LoadRes<TextAsset>("lua", filePath + ".lua");
if (lua != null)
{
return lua.bytes;
}
else
{
Debug.Log("文件重定向失败,文件名:"+filePath);
return null;
}
}
public void DoString(string str)
{
if (luaEnv == null)
{
Debug.Log("解析器未初始化");
return;
}
luaEnv.DoString(str);
}
public void Tick()
{
luaEnv.Tick();
}
public void Dispose()
{
luaEnv.Dispose();
luaEnv = null;
}
}
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
注意:用自定义有参有返回值委托接收lua函数时,需要在委托前加上[CSharpCallLua]特性,并重新生成代码
直接读取G表中的变量名即可
LuaMgr.GetInstance().Global.Get<int>("a");
LuaMgr.GetInstance().Global.Get<string>("b");
LuaMgr.GetInstance().Global.Get<bool>("c");
用对应参数和返回值的委托接收即可
声明委托
//无参无返回值委托
public delegate void CustomCall();
//有参有返回值委托
[CSharpCallLua]
public delegate int CustomCall2(int a);
//多返回值委托
[CSharpCallLua]
public delegate int CustomCall3(int a, out int b, out bool c, out string d, out int e);
获取全局函数
//无参无返回值函数
CustomCall call = LuaMgr.GetInstance().Global.Get<CustomCall>("testFun");
call();
//有参有返回值函数,用Func接收
Func<int,int> call2=LuaMgr.GetInstance().Global.Get<Func<int,int>>("testFun2");
Debug.Log( call2(10));
//多返回值函数
CustomCall3 call3=LuaMgr.GetInstance().Global.Get<CustomCall3>("testFun3");
int b;
bool c;
string d;
int e;
Debug.Log("第一个返回值:"+ call3(4,out b,out c,out d,out e));
Debug.Log(b+" "+c+" "+d+" "+e);
使用相较于第一种方式更简单,但是性能消耗更大,不建议使用
LuaFunction lf2=LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun2");
Debug.Log( lf2.Call(30)[0]);
用List和Dictionary接收,注意lua中的table可以有不同类型的key和value,用object类型的容器接收即可
用对应变量类型的类或结构体接收即可
public class CallLuaClass
{
public int testInt;
public bool testBool;
public UnityAction testFun;
}
CallLuaClass a= LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClas");
Debug.Log(a.testInt);
Debug.Log(a.testBool);
自定义一个接口,接口中的变量都用属性声明即可
注意事项:需要添加[CSharpCallLua]特性并重新生成代码,且该拷贝为引用拷贝,修改接口中的值,Lua表中的值也会改变
使用起来比较简单,但是性能消耗较大,不建议使用
LuaTable t=LuaMgr.GetInstance().Global.Get<LuaTable>("testTable");
//获取表中变量或函数
t.Get<int>("testInt");
t.Get<bool>("testBool");
t.Get<LuaFunction>("testFun").Call();
//修改
t.Set("testInt",5);
打标签:建议给所有C#中需要使用的类型添加[XLua.LuaCallCSharp],xLua会为该类型生成适配代码,能节约性能,不加也可以使用,但是会以性能较低的反射方式来实现
CS.命名空间.类名
GameObject=CS.UnityEngine.GameObject
local obj1 = GameObject("新建对象")
使用对象中的成员方法需要用“:”,使用静态变量和方法直接“.”即可
ob1.transform:Translate(Vector3.right)
注意,lua中不支持泛型函数,需要用重载的方法
ob1.AddComponent(typeof(CS.PlayerController))
直接用+,-操作符即可
与委托的使用方法不同,不能直接通过+,-操作,需要用类似函数的方式
注意:C#中不允许在类外部调用事件或给事件赋值,所以lua中调用事件和清空事件只能通过在C#类中封装方法的形式
增加事件回调
obj:testEvent('+', fun1)
移除事件回调
obj:testEvent('-', fun1)
默认情况下只支持有参数的泛型函数,在lua中传入对应类型的参数即可
public void TestFun<T>(T a, T b) where T : TestClass
{
}
--xlua提供的一个工具表
--一定是要通过require调用之后 才能用
util = require("xlua.util")
--C#中协程启动都是通过继承了Mono的类 通过里面的启动函数StartCoroutine
GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds
--在场景中新建一个空物体 然后挂一个脚本上去 脚本继承mono使用它来开启协程
local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.LuaCallCSharp))
--希望用来被开启的协程函数
fun = function()
local a = 1
while true do
--lua中 不能直接使用 C#中的 yield return
--就使用lua中的协程返回
coroutine.yield(WaitForSeconds(1))
print(a)
a = a + 1
if a > 10 then
--停止协程和C#当中一样
mono:StopCoroutine(b)
end
end
end
--我们不能直接将 lua函数传入到开启协程中!!!!!
--如果要把lua函数当做协程函数传入
--必须 先调用 xlua.util中的cs_generator(lua函数)
b = mono:StartCoroutine(util.cs_generator(fun))
nil和null 不能进行==比较
第一种方法
--如果rig本身为空,则会报错,不建议用这种方法
if rig:Equals(nil) then
rig=obj:AddComponent(typeof(Rigidbody));
end
第二种方法:添加一个公共判断方法
function IsNull( obj )
if obj==nil or obj:Equals(nil) then
return true;
end
return false;
end
第三种方法:给Object类添加一个扩展方法
//为Object扩展方法
[XLua.LuaCallCSharp]
public static class Test
{
///
/// 扩展一个Object判空的方法,主要是给lua使用,lua没法用nil和null进行比较
///
///
///
public static bool IsNull(this Object obj)
{
return obj == null;
}
}
local rig3 = obj3:GetComponent(typeof(Rigidbody));
if rig3:IsNull() then
rig3=obj3:AddComponent(typeof(Rigidbody));
end
有时我们需要给Unity或C#的系统类添加标签,但是源码是不能修改的,可以通过标签列表的形式,批量打标签
public static class CSCallLuaList
{
[CSharpCallLua]
public static List<Type> csCallLuaList = new List<Type>()
{
typeof(UnityAction<bool>)
};
[LuaCallCSharp]
public static bool IsNull(this Object obj)
{
return obj == null;
}
}
[Hotfix]
public static List<Type> by_property {
get {
return (from type in Assembly.Load("Assembly-CSharp").GetTypes()
where type.Namespace == "XXXX"
select type).ToList();
}
}