XLUA学习笔记之C#和Lua之间的相互调用

感觉学习的笔记还是放在网上查询的时候比较方便,文章初看会感觉很难理清,需要耐心去看,对不起自己对不起翻到我文章的同学,不废话直接上代码。

1.C#调用Lua (耐心!耐心!耐心!)

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;

//这个示例涉及到的Lua脚本,将文本复制过来
/*
a = 100.1
str = "hahaha"
isDie = false

person = {
name = "xumeixi";
age1 = 26,108,18,"haha",true,2.34;

add = function(self,a,b)
print(a+b)
end

--[[  
--这是注释掉的两种方法实现方式

function person:add(a,b)--默认带一个self的参数,代表当前table
print(a+b)
end

function person.add(self,a,b)
print(a+b)
end

--这是注释掉的两种方法实现方式
--]]

}

function add()
    print("调用全局Function:add")
end

function cut(a,b)
    print("相减获得:"..(a-b))
	return a - b
end

function add_cut_mul_div(a,b)
    return a+b,a-b,a*b,a/b
end
 */

/// 
/// C#调用Lua代码示例
/// 
public class HelloXLua_CSharpCallLua : MonoBehaviour {
    //创建Lua虚拟机
    LuaEnv luaenv = new LuaEnv();
	// Use this for initialization
	void Start () {
        //普通加载lua脚本的形式
        luaenv.DoString("require 'Lua/CSharpCallLua'");

        //直接加载文本形式的lua代码
        //luaenv.DoString(script);

        //1.C#访问Lua中的全局基本数据类型
        //GetGlobalData();

        //2.1.1.读取lua脚本中的数据映射到普通class或struct(比较耗费性能)
        //GetTable();

        //2.1.2.读取文本形式的lua代码映射到普通class或struct(比较耗费性能)
        //GetTableScript();

        //2.2.映射到一个interface(接口)
        //GetOrSetData();

        //2.3.更轻量级(代码量较少)的by value方式:映射到Dictionary<>、List<>
        GetDataByDictionatyOrList();

        //2.4.另外一种by ref方式:映射到LuaTable类(不推荐,一般用2.2映射到一个interface的方法)
        //GetDataByLUaTable();

        //3.1使用Get方法,不同的是类型映射
        //GetDataByFunction();

        //3.2.映射到LuaFunction(性能较慢)
        //GetDataByLuaFunction();
    }

    #region 1.C#访问Lua中的全局基本数据类型
    void GetGlobalData()
    {
        double a = luaenv.Global.Get("a");
        print("a = " + a);//a = 100.1
        string str = luaenv.Global.Get("str");
        print("str = " + str);//str = hahaha
        bool isDie = luaenv.Global.Get("isDie");
        print("isDie = " + isDie);//isDie = False

        a = 10;
        a = luaenv.Global.Get("a");
        print(a);//a = 100.1  说明在这种直接获取的情况下不能改变Lua脚本中的变量值,只能获取
    }
    #endregion

    #region 2.C#访问Lua中全局的table类型
    //定义一个class,有对应于table的字段的public属性,而且有无参数构造函数即可,比如对于{f1 = 100, f2 =100}可以定义一个包含public int f1;public int f2;的class。
    //这种方式下xLua会帮你new一个实例,并把对应的字段赋值过去。
    //table的属性可以多于或者少于class的属性(没有对应的属性就不会映射)。可以嵌套其它复杂类型。
    //要注意的是,这个过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会。
    //这个功能可以通过把类型加到GCOptimize生成降低开销

    class Person
    {
        public string name;
        public int age1;
        public int age2;
    }

    class DTable
    {
        public int f1;
        public int f2;
    }

    #region 2.1.1.读取lua脚本中的数据映射到普通class或struct(比较耗费性能)
    void GetTable()
    {
        Person p = luaenv.Global.Get("person");
        print(p.name + "-" + p.age1 + "-" + p.age2);
        p.name = "xudada";
        luaenv.DoString("print(person.name)");
    }
    #endregion

    #region 2.1.2.读取文本形式的lua代码映射到普通class或struct(比较耗费性能)
    string script =
        @"
       person = {
           name = 'xumeixi';
           age1 = 26,108,18,'haha',true;
                }

       d = {
               f1 = 10;
               f2 = 20;
               add = function(self,a,b)
                  print('d.add called')
                  return a + b;
               end
        }";

    void GetTableScript()
    {
        Person p = luaenv.Global.Get("person");
        print(p.name + "-" + p.age1 + "-" + p.age2);
        p.name = "xudada";
        luaenv.DoString("print(person.name)");

        DTable d = luaenv.Global.Get("d");
        print("f1+f2=" + d.f1 + d.f2);
    }
    #endregion

    #region 2.2.映射到一个interface(接口)
    //这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,
    //生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。
    //使用[CSharpCallLua]标记后就会生成代码,不适用标记就会报错InvalidCastException异常
    [CSharpCallLua]
    interface IPerson
    {
        string name { get; set; }
        int age1 { get; set; }
        void add(int a, int b);
    }

    //可以获取和设置数据,双向的,改变C#这边的值Lua那边的也会改变
    void GetOrSetData()
    {
        IPerson p = luaenv.Global.Get("person");
        print(p.name + "-" + p.age1);
        p.name = "xudada";
        luaenv.DoString("print(person.name)");
        p.add(28, 30);
    }

    #endregion

    #region 2.3.更轻量级(代码量较少)的by value方式:映射到Dictionary<>、List<>
    //不想定义class或者interface的话,可以考虑用这个,前提table下key和value的类型都是一致的。
    //映射成Dictionary指只会射有key值的,而List只能映射无Key值的,两者互补可以得到全部数据(这里的有无Key值只lua代码中的一些值没有指定Key,age1 = 26,108,18,"haha",true,2.34;这里只有26是有key值的)
    void GetDataByDictionatyOrList()
    {
        //因为value类型不确定,因此用object代替,只会映射有key的(有变量名的,包括函数)
        Dictionary dict = luaenv.Global.Get>("person");
        foreach(string key in dict.Keys)
        {
            print("dic:" + key + "-" + dict[key]);
        }

        //只会映射没有key(没有变量名)的数据,包括所有类型(int,string,bool等)
        List listObj = luaenv.Global.Get>("person");//只会映射没有key的,也就是数值
        foreach (var item in listObj)
        {
            print("listObj:" + item);
        }

        //只会映射没有key(没有变量名)的int类型数据
        List listInt = luaenv.Global.Get>("person");
        foreach (var item in listInt)
        {
            print("listInt:" + item);
        }
    }
    #endregion

    #region 2.4.另外一种by ref方式:映射到LuaTable类(不推荐,一般用2.2映射到一个interface的方法)
    //这种方式的好处是不需要生成代码,但也有一些问题,比如慢,比方式2.2要慢一个数量级,比如没有类型检查。
    void GetDataByLUaTable()
    {
        LuaTable tab = luaenv.Global.Get("person");
        print(tab.Get("name"));
        print(tab.Get("age1"));
        print(tab.Length);//lua中会将没有key值的数据自动添加对应的下标,下标从1开始,lua中计算table的长度是根据下标来的,遇到中断就停止计算长度了,哪怕你长度不止这么多
    }
    #endregion

    #endregion

    #region 3.访问全局的function
    #region 3.1使用Get方法,不同的是类型映射
    //这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码(如果没生成代码会抛InvalidCastException异常)。
    //多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
    //参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。
    [CSharpCallLua]
    public delegate int CutFunc(int a, int b);

    [CSharpCallLua]
    public delegate void Add_cut_mul_div(int a, int b,out int resAdd,out int resCut,out int resMul,out float resDiv);

    void GetDataByFunction()
    {
        //没有参数没有返回值
        Action act1 = luaenv.Global.Get("add");
        act1();
        act1 = null;

        //有参数一个返回值
        CutFunc cut1 = luaenv.Global.Get("cut");
        int result = cut1(10, 5);
        print(result);
        cut1 = null;

        //有参数,多返回,通过out返回
        Add_cut_mul_div func = luaenv.Global.Get("add_cut_mul_div");
        int resAdd = 0, resCut = 0, resMul = 0;
        float resDiv = 0;
        func(10, 4,out resAdd,out resCut,out resMul,out resDiv);
        print(resAdd + "--" + resCut + "--" + resMul + "--" + resDiv);
    }
    #endregion

    #region 3.2.映射到LuaFunction(性能较慢)
    //这种方式的优缺点刚好和3.1方法相反。
    //使用也简单,LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。
    void GetDataByLuaFunction()
    {
        LuaFunction func = luaenv.Global.Get("add_cut_mul_div");
        object[] os = func.Call(1, 2);
        foreach (object o in os)
        {
            print(o);
        }
    }

    #endregion
    #endregion

    #region 使用建议
    /*
     1、访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始
     化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用
     该delegate即可。table也类似。

     2、如果lua测的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解
     耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这
     些delegate和interface设置到要用到它们的地方。
    */
    #endregion

    #region 其他常态函数
    // Update is called once per frame
    void Update()
    {
        if (luaenv != null)
        {
            //清除Lua未手动释放的LuaBase对象(这个操作是未处理过的,太频繁了,可以增加定时清理)
            luaenv.Tick();
        }
    }

    private void OnDestroy()
    {
        luaenv.Dispose();
    }
    #endregion
}

 
  

2.Lua调用C# (耐心!耐心!耐心!)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System;

//添加了[LuaCallCSharp]标签的类才能被Lua调用
[LuaCallCSharp]
public class BaseClass
{
    public static int BSF = 1;
    public static void BSFunc()
    {
        Debug.Log("Driven Static Func,BSF = " + BSF);
    }

    public int BMF { get; set; }
    public void BMFunc()
    {
        Debug.Log("Driven Member Func, BMF = " + BMF);
    }
}

public struct Paraml
{
    public int x;
    public string y;
}

public enum TestEnum
{
    E1,
    E2
}

[LuaCallCSharp]
public class DrivenClass:BaseClass
{
    public int DMF { get; set; }
    public void DMFunc()
    {
        Debug.Log("Driven Member Func, DMF = " + DMF);
    }

    //复杂函数:C#和Lua相互调用
    public double ComplexFunc(Paraml p1,ref int p2, out string p3, Action luaFunc,out Action csFunc)
    {
        Debug.Log("p1 = {x = " + p1.x + ", y = " + p1.y + "},  p2 = " + p2);
        luaFunc();
        p2 = p2 * p1.x;
        p3 = "hello" + p1.y;
        csFunc = () =>
        {
            Debug.Log("csharp callback invoked!");
        };
        return 1.23;
    }

    //重载方法
    public void TestFunc(int i)
    {
        Debug.Log("TestFunc(int i)");
    }
    public void TestFunc(string i)
    {
        Debug.Log("TestFunc(string i)");
    }

    //操作符
    public static DrivenClass operator +(DrivenClass a, DrivenClass b)
    {
        DrivenClass ret = new DrivenClass();
        ret.DMF = a.DMF + b.DMF;
        return ret;
    }

    //函数包含默认参数
    public void DefaultValueFunc(int a = 100, string b = "bbbbbb", string c = null)
    {
        Debug.Log("DefaultValueFunc: a=" + a + ",b=" + b + ",c=" + c);
    }

    //可变参数
    public void VariableParamsFunc(int a, params string[] strs)
    {
        UnityEngine.Debug.Log("VariableParamsFunc: a =" + a);
        foreach (var str in strs)
        {
            UnityEngine.Debug.Log("str:" + str);
        }
    }

    //通过Extension methods实现访问泛化方法
    public void GenericMethod()
    {
        Debug.Log("GenericMethod<" + typeof(T) + ">");
    }

    //枚举类型
    public TestEnum EnumTestFunc(TestEnum e)
    {
        Debug.Log("EnumTestFunc: e=" + e);
        return TestEnum.E2;
    }
    public enum TestEnumInner
    {
        E3,
        E4
    }

    //委托调用:下面是直接用lambda表达式实现了带有一个string类型参数的无返回值委托
    public Action TestDelegate = (param) =>
    {
        Debug.Log("TestDelegate in c#:" + param);
    };

    //事件回调
    //定义一个事件成员,在lua层那边可以通过特殊方式添加和删除事件回调,然后启动事件
    public event Action TestEvent;
    //启动事件的函数
    public void CallEvent()
    {
        TestEvent();
    }

    //支持lua64位计算
    public ulong TestLong(long n)
    {
        return (ulong)(n + 1);
    }

    //强转
    class InnerCalc:ICalc
    {
        public int add(int a, int b)
        {
            return a + b;
        }
        public int id = 100;
    }
    public ICalc GetCalc()
    {
        return new InnerCalc();
    }

}

[LuaCallCSharp]
public interface ICalc
{
    int add(int a, int b);
}


//扩展方法:在不改变类的情况下扩展类对象的方法
//实现多种类型的扩展方法,静态类,静态方法,参数为类类型前面需要加上this关键字,调用的时候使用参数类型的对象
[LuaCallCSharp] //必须加上标签,不然lua会找不到这个扩展方法类
public static class DrivenClassExtensions
{
    public static int GetSomeData(this DrivenClass obj)
    {
        Debug.Log("GetSomeData ret = " + obj.DMF);
        return obj.DMF;
    }

    public static int GetSomeBaseData(this BaseClass obj)
    {
        Debug.Log("GetSomeBaseData ret = " + obj.BMF);
        return obj.BMF;
    }

    public static void GenericMethodOfString(this DrivenClass obj)
    {
        obj.GenericMethod();
    }
}

/// 
/// 研究普通类中的函数在lua中的调用
/// 
public class TestClass
{
    [LuaCallCSharp]
    public void Test()
    {
        Debug.Log("我是普通类(既不是静态类也继承于MonoBehaviour,也没有添加[LuaCallCSharp])中加了[LuaCallCSharp]的函数");
    }
}

public class HelloXLua_LuaCallCSharp : MonoBehaviour {
    LuaEnv luaenv = null;

    //使用文本形式的代码代替Lua脚本
    string script = @"
        function demo()
            --new出来的C#对象:也会出现在场景中,对象的名称没有给定就会显示默认的new GameObject
            local newGameObj = CS.UnityEngine.GameObject()
            local newGameObj2 = CS.UnityEngine.GameObject('helloWorld')
            print(newGameObj,newGameObj2)
            
            --访问静态属性和方法:使用点号
            local GameObject = CS.UnityEngine.GameObject
            print('UnityEngine.Time.deltaTime:',CS.UnityEngine.Time.deltaTime) --读静态属性
            CS.UnityEngine.Time.timeScale = 0.5 --写静态属性
            print('hello world',GameObject.Find('Main Camera')) --静态方法调用
            
            --访问成员属性和方法:成员属性使用点号,成员方法使用冒号
            local DrivenClass = CS.DrivenClass; --在实际项目中,可以先用一个局部变量将C#那边的对象引用一下,今后只需要使用这个变量就好了,避免多次映射访问C#那边的对象增加消耗
            local testObj = DrivenClass() --创建一个对象时不需要new关键字
            testObj.DMF = 1024 --设置成员属性使用类对象+点号
            print(testObj.DMF) --调用成员属性使用类对象+点号
            testObj:DMFunc() --调用成员方法使用类对象+冒号
            --testObj.DMFunc() --成员方法使用点号访问会报错
            
            --基类属性和方法
            print(DrivenClass.BSF) --读取基类静态属性使用类名+点号
            DrivenClass.BSF = 2048 --写基类静态属性使用类名+点号
            DrivenClass.BSFunc() --调用基类静态方法使用类名+点号
            print(testObj.BMF) --读基类成员属性使用类对象+点号
            testObj.BMF = 4096 --写基类成员属性使用类对象+点号
            testObj:BMFunc() --基类方法调用使用类对象+冒号
            
            --复杂方法调用:接收多个返回值,如果C#函数有返回值那么这个返回值排在第一位,其余以ref和out方式返回的按照从左到右依次排序
            local ret,p2,p3,csfunc = testObj:ComplexFunc({x=3,y='john'},100,function() print('I am lua callBack') end)
            print('ComplexFunc ret:',ret,p2,p3,csfunc)
            csfunc()
            
            --重载方法调用:只需要传入不同类型的参数就可以了
            testObj:TestFunc(100);
            testObj:TestFunc('haha')
            
            --操作符
            local testObj2 = DrivenClass()
            testObj2.DMF = 2048
            print('(testObj + testObj2).DMF = ',(testObj + testObj2).DMF)
            
            --函数包含默认参数
            testObj:DefaultValueFunc(1)
            testObj:DefaultValueFunc(3,'hello','john')
            
            --可变参数:调用的C#成员函数的参数是不固定的,lua这边也只需要传入对应类型的参数即可,也可以不传
            testObj:VariableParamsFunc(5, 'hello', 'john')
            
            --Extension methods扩展方法,扩展方法是静态方法,使用对象+冒号访问
            print(testObj:GetSomeData())
            print(testObj:GetSomeBaseData()) --访问基类的扩展方法
            testObj:GenericMethodOfString()  --通过扩展方法访问泛型方法
            
            --枚举类型的操作:如果枚举类加入到生成代码的话,枚举类将支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换
            local e = testObj:EnumTestFunc(CS.TestEnum.E1)
            print(e, e == CS.TestEnum.E2)
            print(CS.TestEnum.__CastFrom(1), CS.TestEnum.__CastFrom('E1'))
            print(CS.DrivenClass.TestEnumInner.E3)
            assert(CS.BaseClass.TestEnumInner == nil)
            
            --委托调用Delegate
            testObj.TestDelegate('hello') --直接调用
            local function lua_delegate(str)
                print('TestDelegate in lua:', str)
            end
            testObj.TestDelegate = lua_delegate + testObj.TestDelegate --combine,这里演示的是C#delegate作为右值,左值也支持
            testObj.TestDelegate('hello')
            testObj.TestDelegate = testObj.TestDelegate - lua_delegate --remove
            testObj.TestDelegate('hello')
            
            --事件:相当于一个函数的集合,访问方式同函数一样使用类对象+冒号
            local function lua_event_callback1() print('lua_event_callback1') end
            local function lua_event_callback2() print('lua_event_callback2') end
            testObj:TestEvent('+', lua_event_callback1) --给事件添加回调函数
            testObj:CallEvent() --启动事件
            testObj:TestEvent('+', lua_event_callback2) --给事件移除回调函数
            testObj:CallEvent()
            testObj:TestEvent('-', lua_event_callback1)
            testObj:CallEvent()
            testObj:TestEvent('-', lua_event_callback2)
            
            --64位计算支持
            local num = testObj:TestLong(11)
            print(type(num),num,num+100,num+10000)
            
            --typeof:使用typeof如以下的方式给一个Unity的GameObject对象添加组件
            newGameObj:AddComponent(typeof(CS.UnityEngine.ParticleSystem))--添加粒子系统
            
            --cast强转函数,将参数一类型强转为参数二类型
            local calc = testObj:GetCalc()
            print('assess instance of InnerCalc via reflection', calc:add(1, 2))--直接映射访问消耗大
            assert(calc.id == 100)
            print(calc.id)
            calc.id = 50
            print(calc.id)
            cast(calc, typeof(CS.DrivenClass.InnerCalc)) --用CS.Calc的生成代码来访问calc对象,消耗减小
            --cast(calc, typeof(CS.Calc)) --使用CS.Calc为目标转换类型,由于此类型里面没有id这个变量,所以calc.id的值会变为nil
            print('cast to interface ICalc', calc:add(1, 2))
            print(calc.id)
            assert(calc.id ~= nil) --断言括号中的语句成立,如果不成立就报错

            --继承MonoBehavior的类Lua中调用静态函数:可以直接CS.类名.静态函数名调用
            CS.HelloXLua_LuaCallCSharp.TestLuaCallCS();
            --继承MonoBehavior的类Lua中调普通成员函数,需要生成对象后调用,使用冒号
            --以下两种方式都可以
            --local testObj = CS.HelloXLua_LuaCallCSharp():TestLuaCallCS111();
            local testObj = CS.HelloXLua_LuaCallCSharp();
            testObj:TestLuaCallCS111();

            --研究普通类中的函数在lua中的调用
            CS.TestClass():Test();
        end
        
        --函数的普通调用
        --demo()

        --协程下使用
        local co = coroutine.create(function()
            print('------------------------------------------------------')
            demo()
        end)
        assert(coroutine.resume(co))
    ";

    public static void TestLuaCallCS()
    {
        print("继承于MonoBehaviour的类,可以直接CS.类名.静态函数名调用");
    }

    public void TestLuaCallCS111()
    {
        print("继承于MonoBehaviour的类,调用里面的普通成员函数需要生成对象后调用");
    }

    // Use this for initialization
    void Start()
    {
        luaenv = new LuaEnv();
        luaenv.DoString(script);
    }

    // Update is called once per frame
    void Update()
    {
        if (luaenv != null)
        {
            //清除Lua未手动释放的LuaBase对象(这个操作是未处理过的,太频繁了,可以增加定时清理)
            luaenv.Tick();
        }
    }

    void OnDestroy()
    {
        luaenv.Dispose();
    }
}

你可能感兴趣的:(XLua,Unity,XLua,C#和Lua之间相互调用)