继上篇的C#调用Lua之后,这篇主要记录Unity中使用XLua是如何在Lua代码中调用C#代码的。相关的文档资料与上篇相同,这里就不赘述了。除了官方的Demo,自己也加了个小demo,对应文章的内容,在MyExamples目录下的LuaCallCSharp。
备注:Lua Call C#代码的时候,C#处生成的代码基本都需要打标签[LuaCallCSharp]。关于标签的理解,将在后面的文章详细讲解。
我们在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')
由上面我们可以知道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")
与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));
需要注意的是:若用 . 调用成员方法,第一个参数需要传递该对象,所以建议使用 : 访问(冒号语法糖)
XLua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类的实例)访问基类的成员属性,成员方法。
参数处理规则: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)
与C#一样,通过访问方法时不同的实参,即可进行重载函数的访问,例如
testSon:Log(3);
testSon:Log('qwe');
注意:XLua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#的丰富,比如C#中的int,float,double等都对应lua的number。若C#有这些类型的重载,Lua则无法区分开来,只能调用到其中的一个(生成代码中排前面的那个)。
对于参数带默认值的方法,和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);
枚举值就像枚举类型下的静态属性一样。假设我们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'));
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白名单。
例如,我们定义了下面的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);
对于一个有无参构造函数的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'});
例如,我们在lua中要获取一个类的Type信息,可以用typeof(),在添加组件的时候常常会用到,如下
go:AddComponent(typeof(CS.UnityEngine.ParticleSystem));
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时,则会中断程序抛出异常。
在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');
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();