Lua与C#的相互调用(xLua)

目录

  • 一、xLua简介
  • 二、C#调用Lua
    • 1、基本操作与自定义Loader
      • 1.1、基本操作
      • 1.2、自定义Loader
    • 2、Lua解析器的初始化
      • 2.1、代码示例
      • 2.2、LuaMgr的初始化
    • 3、获取全局变量
    • 4、获取全局函数
      • 4.1、映射到委托
      • 4.2、映射到LuaFunction
    • 5、获取全局table
      • 5.1、映射到List和Dictionary
      • 5.2、映射到class或者struct
      • 5.3、映射到Interface
      • 5.4、映射到LuaTable
  • 三、Lua调用C#
    • 1、使用C#中的类
    • 2、使用C#中的委托和事件
      • 2.1、使用delegate
      • 2.2、使用event
    • 3、使用C#中泛型函数
    • 4、使用C#中的协程
    • 5、null和nil对比
  • 四、给系统类添加标签
    • 1、静态列表
    • 2、动态列表

一、xLua简介

xLua是由腾讯维护的一个开源项目,xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在,腾讯已经将xLua开源到GitHub。其git地址是:https://github.com/Tencent/xLua。

二、C#调用Lua

C#调用Lua的原理:在C#代码中创建一个Lua解释器,用以解释执行Lua代码
获取Lua中的变量:Lua中的全局变量都保存在G表中,通过变量名可以获取Lua中的变量值
打标签:[CSharpCallLua],获取lua中部分类型时,需要给C#中用来接收的类型打上该标签

1、基本操作与自定义Loader

1.1、基本操作

执行字符串:执行字符串中的lua代码

luaEnv.DoString("print('hello world')")

加载Lua文件:用lua的require函数加载

luaEnv.DoString(“require 'byfile'")

1.2、自定义Loader

当我们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;
    }
}

2、Lua解析器的初始化

在使用LuaEnv之前,需要先进行一些列的初始化操作(生成LuaEnv,添加自定义Loader,加载Main函数),编写一个LuaMgr类以集成这些操作,方便使用

2.1、代码示例

/// 
/// 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;
    }
}

2.2、LuaMgr的初始化

LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");

3、获取全局变量

注意:用自定义有参有返回值委托接收lua函数时,需要在委托前加上[CSharpCallLua]特性,并重新生成代码
直接读取G表中的变量名即可

LuaMgr.GetInstance().Global.Get<int>("a");
LuaMgr.GetInstance().Global.Get<string>("b");
LuaMgr.GetInstance().Global.Get<bool>("c");

4、获取全局函数

4.1、映射到委托

用对应参数和返回值的委托接收即可

声明委托

//无参无返回值委托
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);

4.2、映射到LuaFunction

使用相较于第一种方式更简单,但是性能消耗更大,不建议使用

LuaFunction lf2=LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun2");
Debug.Log( lf2.Call(30)[0]);

5、获取全局table

5.1、映射到List和Dictionary

用List和Dictionary接收,注意lua中的table可以有不同类型的key和value,用object类型的容器接收即可

5.2、映射到class或者struct

用对应变量类型的类或结构体接收即可

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);

5.3、映射到Interface

自定义一个接口,接口中的变量都用属性声明即可
注意事项:需要添加[CSharpCallLua]特性并重新生成代码,且该拷贝为引用拷贝,修改接口中的值,Lua表中的值也会改变

5.4、映射到LuaTable

使用起来比较简单,但是性能消耗较大,不建议使用

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);

三、Lua调用C#

打标签:建议给所有C#中需要使用的类型添加[XLua.LuaCallCSharp],xLua会为该类型生成适配代码,能节约性能,不加也可以使用,但是会以性能较低的反射方式来实现

1、使用C#中的类

CS.命名空间.类名

GameObject=CS.UnityEngine.GameObject
local obj1 = GameObject("新建对象")

使用对象中的成员方法需要用“:”,使用静态变量和方法直接“.”即可

ob1.transform:Translate(Vector3.right)

注意,lua中不支持泛型函数,需要用重载的方法

ob1.AddComponent(typeof(CS.PlayerController))

2、使用C#中的委托和事件

2.1、使用delegate

直接用+,-操作符即可

2.2、使用event

与委托的使用方法不同,不能直接通过+,-操作,需要用类似函数的方式
注意:C#中不允许在类外部调用事件或给事件赋值,所以lua中调用事件和清空事件只能通过在C#类中封装方法的形式
增加事件回调

obj:testEvent('+', fun1)

移除事件回调

obj:testEvent('-', fun1)

3、使用C#中泛型函数

默认情况下只支持有参数的泛型函数,在lua中传入对应类型的参数即可

public void TestFun<T>(T a, T b) where T : TestClass
    { 
    }

4、使用C#中的协程

--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))

5、null和nil对比

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#的系统类添加标签,但是源码是不能修改的,可以通过标签列表的形式,批量打标签

1、静态列表

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;
    }

}

2、动态列表

[Hotfix]
public static List<Type> by_property {
    get {
        return (from type in Assembly.Load("Assembly-CSharp").GetTypes()
                where type.Namespace == "XXXX"
                select type).ToList();
    }
}

你可能感兴趣的:(lua,c#,junit)