Unity C#与Xlua的交互 Lua调用C# LuaCallCSharp

继上篇的C#调用Lua之后,这篇主要记录Unity中使用XLua是如何在Lua代码中调用C#代码的。相关的文档资料与上篇相同,这里就不赘述了。除了官方的Demo,自己也加了个小demo,对应文章的内容,在MyExamples目录下的LuaCallCSharp。

LuaCallCSharp

备注:Lua Call C#代码的时候,C#处生成的代码基本都需要打标签[LuaCallCSharp]。关于标签的理解,将在后面的文章详细讲解。

1. new 一个C#对象

我们在C#中生成一个对象,比如GameObject,代码如下:

GameObject go = new GameObject();

那么Lua是怎么写的呢?如下:

local go = CS.UnityEngine.GameObject()

知识点:首先在Lua中没有new关键字,所以新建对象的时候直接省略new即可,其次所有C#相关的都要加上前缀"CS."包括构造函数,静态成员属性、方法。同时别忘了加上命名空间namespace。所以C#的GameObject类,在Lua中就是CS.UnityEngine.GameObject。

当class有多个构造函数时,xlua中也支持重载,如GameObject有一个带一个string参数的重载用于给该GameObject命名,对应的Lua实现如下:

local go = CS.UnityEngine.GameObject('helloworld')

2.访问C#静态属性、方法

由上面我们可以知道Lua中调用GameObject的类为CS.UnityEngine.GameObject,那么如果想调用GameObject中的静态方法,比如Find(string),如下:

local go = CS.UnityEngine.GameObject.Find('helloworld')

访问静态属性也是同理,比如:

CS.UnityEngine.Time.deltaTime

--赋值
CS.UnityEngine.Time.timeScale = 0.5

建议:需要经常访问的类,可以先用局部变量引用后访问,即可以减少代码量,还可以提高性能,如:

local GameObject = CS.UnityEngine.GameObject

--使用的时候
GameObject.Find('helloworld')
GameObject.FindGameObjectsWithTag("tag")

3.访问C#成员属性、方法

与C#一样,访问成员属性方法的时候,需要通过类的实例去访问。

假设我们在C#中定义了如下类:

namespace MyExamples {
    [LuaCallCSharp]
    public class Test {
        public int index;
        public int Add(int a, int b) {
            return a + b;
        }
    }
}

那么对应的Lua即:

local Test = CS.MyExamples.Test;
local test = Test();
test.index = 66;
print('test.index---'..test.index);
print('test.Add---'..test.Add(test,1,2));
print('test:Add---'..test:Add(3,4));

需要注意的是:若用 . 调用成员方法,第一个参数需要传递该对象,所以建议使用 : 访问(冒号语法糖)

4.访问父类属性、方法

XLua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类的实例)访问基类的成员属性,成员方法。

5.参数的输入输出属性(out,ref)

参数处理规则:Lua调用C#方法的时候,C#方法中的参数,从左到右,普通参数算一个输入形参,ref修饰的算一个输入形参,out修饰的不算。

返回值处理规则:Lua接受C#方法返回值的时候,C#方法的返回值(如果有)算一个返回值,(形参中的)ref算一个返回值,out算一个返回值,从左到右对应lua的多返回值。

具体什么意思?直接上代码。如下,我们在C#中有一个方法:

namespace MyExamples {
    [LuaCallCSharp]
    public class A {
        public static int Method(int a, ref int b, out int c, Action funA, out Action funB) {
            Debug.Log("Method-----a:" + a + "----b:" + b);
            c = 10;
            funA();
            funB = () => { Debug.Log("exe---funB"); };
            return 5;
        }
    }
}

按照上面的规则,我们可以用下面的Lua来处理:

--每个返回值都对应返回值的规则
--每个输入的实参都对应参数的规则
local ret, ret_b, ret_c, ret_funB = CS.MyExamples.A.Method(1,2,function()
    print('exe----funA');
end);
print('CS.MyExamples.A.Method  return---', ret, ret_b, ret_c, ret_funB);
ret_funB();

注意:这里我们将lua中的function传递到了C#中的Action参数中,等于上一篇说讲到的lua函数映射到c#委托,因此我们需要将Action添加到CSharpCallLua的白名单中,关于标签白名单,后续文章会讲解。类似的还有Func<>等委托(否则将会报错LuaException: c# exception:System.InvalidCastException: This type must add to CSharpCallLua: System.Action)

6.重载方法

与C#一样,通过访问方法时不同的实参,即可进行重载函数的访问,例如

testSon:Log(3);
testSon:Log('qwe');

注意:XLua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#的丰富,比如C#中的int,float,double等都对应lua的number。若C#有这些类型的重载,Lua则无法区分开来,只能调用到其中的一个(生成代码中排前面的那个)。

7.参数带默认值的方法,与可变参数方法

对于参数带默认值的方法,和C#一样,当所给的实参少于形参,则会用默认值补上。

对于可变参数,代码如下:

public class A {
    public static void MethodC(string s, params int[] arr) {
        foreach(int i in arr) {
            Debug.Log("MethodC----" + i);
        }
    }
}

对于的lua:

CS.MyExamples.A.MethodC('s',1,2,3);

8.枚举类型

枚举值就像枚举类型下的静态属性一样。假设我们C#有如下枚举

namespace MyExamples {
    public enum ETest {
        T1,
        T2,
        T3
    }
}

那么lua中使用CS.MyExamples.ETest.T1即可。

另外,如果枚举类加入到生成代码的话(即添加[LuaCallCSharp]标签),枚举类将支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换:

CS.MyExamples.A.MethodD(CS.MyExamples.ETest.T1);
CS.MyExamples.A.MethodD(CS.MyExamples.ETest.__CastFrom(1));
CS.MyExamples.A.MethodD(CS.MyExamples.ETest.__CastFrom('T3'));

9.delegate的使用(调用 + -)

C#的delegate调用:和调用普通的lua函数一样

例如,我们在C#定义了如下的delegate:

[LuaCallCSharp]
public class TestSon : Test {
    public delegate int IntDelegate(int a);
    public IntDelegate intDelegate = (a) => {
        Debug.Log("C#--intDelegate----a:" + a);
        return a;
    };
}

对应的Lua访问即:

testSon.intDelegate(10);

+操作符:对应C#的+操作符,把两个调用串成一个调用链,右操作数可以是同类型的C# delegate或者lua函数。

-操作符:和+相反,把一个delegate从调用链中移除。

ps:delegate属性可以用一个lua function来赋值。

local function lua_delegate(a)
    print('lua_delegate :', a)
end

testSon.intDelegate = lua_delegate + testSon.intDelegate --combine,这里演示的是C#delegate作为右值,左值也支持
testSon.intDelegate(100)
testSon.intDelegate = testSon.intDelegate - lua_delegate --remove
testSon.intDelegate(1000)

和第五条类似:需要将IntDelegate加入CSharpCallLua白名单。

10.event的使用

例如,我们定义了下面的event

public class TestSon : Test {
    public event IntDelegate intEvent;

    public void ExeEvent(int a) {
        intEvent(a);
    }
}

Lua中即可通过下面的方面去处理与访问

local function lua_eventCallback1(a)
    print('lua_eventCallback1 :', a)
end
local function lua_eventCallback2(a)
    print('lua_eventCallback2 :', a)
end
--增加事件回调
testSon:intEvent('+', lua_eventCallback1);
testSon:intEvent('+', lua_eventCallback2);
testSon:ExeEvent(100);
--移除事件回调
testSon:intEvent('-', lua_eventCallback1);
testSon:ExeEvent(1000);
testSon:intEvent('-', lua_eventCallback2);

11.C#的复杂类型和table的自动转换

对于一个有无参构造函数的C#复杂类型,在Lua中可以直接用一个table来代替,该table对应复杂类型的public字段有相应字段即可,支持函数参数传递,属性赋值等。(和C#调用Lua的时候类似)

例如我们有如下struct(class也是一样的)

public struct BoxA {
    int x;
    int y;
}

public struct BoxB {
    BoxA boxA;
    string name;
}

然后在C#中有如下方法,使用到该struct:

public static void MethodE(BoxB box) {
    Debug.Log("MethodE----name:" + box.name + "----x:" + box.boxA.x);
}

那么在lua中,我们就可以将如下进行调用:

CS.MyExamples.A.MethodE({boxA={x=1,y=2},name='box'});

12.获取类型(相当于C#的typeof)

例如,我们在lua中要获取一个类的Type信息,可以用typeof(),在添加组件的时候常常会用到,如下

go:AddComponent(typeof(CS.UnityEngine.ParticleSystem));

13.“强”转类型

lua中没有类型,所以不会有强类型语言的“强转”,但是有个有点像的东西:告诉XLua要用指定的生成代码去调用一个对象。比如有的时候第三方库对外暴露的时一个interface或者抽象类,实现类是隐藏的,这样我们无法对实现类进行代码的生成。该实现类将会被XLua识别为未生成代码而用反射来访问,如果这个调用是很频繁的话会很影响性能,这时我们可以把这个interface或者抽象类加到生成代码,然后指定用该生成代码来访问。

XLua提供了一个T cast(table, T)的API用于强转,把该table转成一个T指明的类型,可以是一个加了CSharpCallLua声明的interface,一个有默认构造函数的class或者struct,一个Dictionary,List等等。

例如我们在C#中定义了如下

//对外接口
[LuaCallCSharp]
public interface ICalc {
    int add(int a, int b);
}

//接口的内部实现,不对外
class CalcClass : ICalc {
    public int add(int a, int b) {
        return a + b;
    }

    public int id = 100;
}
public class Test {
    //对外api 获取对应接口的实例
    public ICalc GetCalc() {
        return new CalcClass();
    }
}

我们在Lua中可以如下实现:

local calc = test:GetCalc();
print('calc------Add:',calc:Add(1,2));

上面这种方式就是前面所提到的映射方式,性能较差。所以我们可以加一步强转如下,用于提高性能:

cast(calc, typeof(CS.MyExamples.ICalc));
print('calc---case---add:',calc:Add(3,4));
--需要注意的是,强转interface之后,因为ICalc中并未定义id属性,所以强转之后去访问id的值(其实就没有这个字段了)为nil
assert(calc.id == nil);
--如同随便访问一个没有的字段qwer
assert(calc.qwer == nil);

ps: lua  assert() API,当参数为false或nil时,则会中断程序抛出异常。

14.扩展方法Extension methods

在C#定义好了扩展方法,在Lua中就可以和访问普通的成员方法一样使用,如下,为前文的Test类添加扩展方法。

注意:扩展方法必须加上[LuaCallCSharp]标签才能访问到。

[LuaCallCSharp]
public static class ExtraTest {
    public static void ExtraLog(this Test test, string s) {
        Debug.Log("ExtraTest----ExtraLog---" + s);
    }
}

然后在lua中,我们用之前的Test类的实例就可访问:

test:ExtraLog('122334');

15.泛化(模板)方法

Lua中不直接支持泛化,需要我们通过扩展方法进行封装,然后调用该扩展方法。例如,我们在C#有如下定义:

public class Test {
    public void GenericMethod() {
        Debug.Log("GenericMethod<" + typeof(T) + ">");
    }
}

在lua中我们不能像C#那样直接用test.GenericMethod()这样调用,需要我们手动封装一层,例如:

public static class ExtraTest {
    public static void GenericMethodWithString(this Test test) {
        test.GenericMethod();
    }
}

然后在用lua去调用:

test:GenericMethodWithString();


你可能感兴趣的:(XLua)