找回密码
 立即注册
查看: 280|回复: 0

初识xLua(一)

[复制链接]
发表于 2021-8-14 12:42 | 显示全部楼层 |阅读模式
目录


      通过xLua运行Lua程序加载运行lua源文件添加自定义的Loader方法C#访问Lua
        全局变量Table全局function
      Lua调用C#常用方式
        创建C#对象访问静态属性和方法访问成员属性和方法访问父类(子类)属性和方法Lua调用C#的方法重载现象
      Lua调用C#中带参方法
        C#中可变参数方法,Parmms关键字C#结构体参数C#接口参数C#委托参数
      Lua接收C#方法返回的多个结果数值Lua如何调用C#泛型方法其他知识点Lua调用C#经验小结


通过xLua运行Lua程序

导入网上下载的 xLua 包,其中也包涵示例代码和场景。
  1. using XLua;
  2.     private LuaEnv luaenv;void Start (){//构建Lua虚拟机
  3.         luaenv = new LuaEnv();//通过 Lua 语法(执行一个字符串)输出 Hello world//luaenv.DoString("print('Hello world!')"); //通过 Lua 调用 C# 语法输出 Hello world
  4.         luaenv.DoString(" CS.UnityEngine.Debug.Log('Hello world') ");}
  5.     private voidOnDestroy(){
  6.         
  7.         luaenv.Dispose();}}
复制代码

加载运行lua源文件

这里要注意一点 该文件的全名(包括后缀)为:helloworld.lua.txt
  1.         TextAsset ta = Resources.Load<TextAsset>("helloworld.lua");
  2.         LuaEnv env = new LuaEnv();
  3.         
  4.         env.DoString(ta.text);
  5.         env.Dispose();
复制代码
通过内置的 Loader 方法加载,比如,DoString(" require ‘byfile’ ")
  1.         LuaEnv env = new LuaEnv();
  2.         env.DoString("require 'helloworld'");
  3.         
  4.         env.Dispose();
复制代码

添加自定义的Loader方法

涉及到一个接口:
  1. public delegate byte[]CustomLoader(ref string filepath);
  2. public void LuaEnv.AddLoader(CustomLoader loader)
复制代码
示例 :
  1. void Start (){
  2.          LuaEnv env = new LuaEnv();
  3.         env.AddLoader(MyLoader);
  4.         env.DoString("require 'test'");
  5.         env.Dispose();}
  6.        
  7.     private byte[]MyLoader(ref string filePath){
  8.         string absPath = Application.streamingAssetsPath +"/"+ filePath +".lua.txt";return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(absPath));}
复制代码

C#访问Lua

全局变量

要注意,获取的变量类型一定要对
  1.         LuaEnv luaEnv = new LuaEnv();
  2.         luaEnv.DoString("require 'CSharpCallLua'");int a = luaEnv.Global.Get<int>("a");//获取到lua里面的全局变量 aprint(a);
  3.         string str = luaEnv.Global.Get<string>("str");//获取到lua里面的全局变量 strprint(str);
  4.         bool isDie = luaEnv.Global.Get<bool>("isDie");//获取到lua里面的全局变量 isDieprint(isDie);
  5.         luaEnv.Dispose();
复制代码

Table

table 的名字为 person
  1. person ={ name="MoGu",age=100}
复制代码
把访问到的 table 映射到 class
  1. class Person
  2.     {
  3.         public string name;
  4.         public int age;}
  5.         LuaEnv luaEnv = new LuaEnv();
  6.         luaEnv.DoString("require 'CSharpCallLua'");
  7.         
  8.         Person p = luaEnv.Global.Get<Person>("person");print(p.name+"-"+ p.age);
  9.         luaEnv.Dispose();
复制代码
用这种方式访问到 table 后,对类中的值修改,是不会影响原来的 table 的。

把访问到的 table 映射到 interface
  1. person ={
  2.         name="MoGu",age=100,
  3.        
  4.         eat=function(self,a,b)print(a+b)
  5.         end
  6. }
复制代码
需要给接口添加一个特性 [CSharpCallLua]
可以通过 interface 的方法访问 Lua 的函数
  1. [CSharpCallLua]
  2.    interface IPerson
  3.     {
  4.         string name { get; set;}int age { get; set;}voideat(int a,int b);}
  5.         LuaEnv luaEnv = new LuaEnv();
  6.         luaEnv.DoString("require 'CSharpCallLua'");
  7.         
  8.         IPerson p = luaEnv.Global.Get<IPerson>("person");print(p.name+"-"+p.age);
  9.         p.eat(12,34);
  10.         luaEnv.Dispose();
复制代码
用这种方式访问到 table 后,对值修改,是影响原来的 table 的。

把访问到的 table 映射到 Dictionary<>,List<>
给原来的 table 添加几个数据
  1. person ={
  2.         name="MoGu",age=100,12,2,2,2,2,true,3.3,
  3.        
  4.         eat=function(self,a,b)print(a+b)
  5.         end
  6. }
复制代码
  1.         LuaEnv luaEnv = new LuaEnv();
  2.         luaEnv.DoString("require 'CSharpCallLua'");
  3.         
  4.         Dictionary<string, object> dict = luaEnv.Global.Get<Dictionary<string, object>>("person");foreach(string key in dict.Keys){print(key +"-"+ dict[key]);}
  5.         
  6.         List<int> list = luaEnv.Global.Get<List<int>>("person");
  7.         foreach (object o in list){print(o);}
  8.         luaEnv.Dispose();
复制代码

把访问到的 table 映射到 LuaTable
优点是不需要生成代码,缺点是慢
  1.         LuaEnv luaEnv = new LuaEnv();
  2.         luaEnv.DoString("require 'CSharpCallLua'");
  3.         
  4.         LuaTable tab= luaEnv.Global.Get<LuaTable>("person");print(tab.Get<string>("name"));print(tab.Get<int>("age"));print(tab.Length);
  5.         
  6.         luaEnv.Dispose();
复制代码

全局function

先定义一个全局 function
  1. function add(a,b)print(a+b)return a+b,a,b
  2. end
复制代码
方式一,映射到delegate
优点,性能好,类型安全;缺点,要生成代码
  1. [CSharpCallLua]
  2.     delegate intAdd(int a,int b,out int resa,out int resb);
  3.         LuaEnv luaEnv = new LuaEnv();
  4.         luaEnv.DoString("require 'CSharpCallLua'");
  5.         
  6.         Add add = luaEnv.Global.Get<Add>("add");int resa =0;int res b=0;int res =add(34,78,out resa,out resb);print(res);print(resa);print(resb);
  7.         
  8.         add = null;        
  9.         luaEnv.Dispose();
复制代码

方式二,映射到 LuaFunction
优缺点和第一种相反
  1.         LuaEnv luaEnv = new LuaEnv();
  2.         luaEnv.DoString("require 'CSharpCallLua'");
  3.         
  4.         LuaFunction func = luaEnv.Global.Get<LuaFunction>("add");
  5.         object[] os= func.Call(1,2);foreach(object o in os){print(o);}
  6.         
  7.         luaEnv.Dispose();
复制代码

Lua调用C#常用方式

创建C#对象

在 lua 脚本中
  1. --构造游戏物体,new对象
  2. CS.UnityEngine.GameObject("new by lua")
复制代码
在 Unity 中调用
  1.         LuaEnv luaEnv = new LuaEnv();
  2.    
  3.         luaEnv.DoString("require 'LuaCallCSharp'");
  4.         luaEnv.Dispose();
复制代码

访问静态属性和方法
  1. local gameObject = CS.UnityEngine.GameObject
  2. local camera = gameObject.Find("Main Camera")
  3. camera.name ="update by lua"
复制代码

访问成员属性和方法

注意:
lua 中使用冒号,表示成员方法的调用。它自动完成把当前对象作为一个参数,传入方法。
lua 中使用点,则表示静态属性与方法调用。它需要手工往方法中传递当前对象。
  1. local cameraCom= camera.GetComponent(camera,"Camera")
  2. gameObject.Destroy(cameraCom)
复制代码

访问父类(子类)属性和方法

定义一个C#父类
  1. namespace XluaPro
  2. {
  3.         public class IsInvoked_Father
  4.         {
  5.         public string FatherClassName ="父类字段";
  6.         public IsInvoked_Father(){
  7.             Debug.Log("IsInvoked_Father 父类构造函数");}
  8.         public voidShowFatherInfo(){
  9.             Debug.Log("IsInvoked_Father.cs 父类的方法 ShowFatherInfo()");}}}
复制代码
再写一个子类
  1. namespace XluaPro
  2. {
  3.         public class IsInvokedClass:IsInvoked_Father
  4.         {
  5.         public string ChildClassName="子类字段";
  6.        public IsInvokedClass(){
  7.             Debug.Log("IsInvokedClass 子类构造函数");}
  8.         public voidMehtod1(){
  9.             Debug.Log("IsInvokedClass.cs/Mehtod1 方法");}}}
复制代码
在 lua 文件中调用
  1. local IsInvoked = CS.XluaPro.IsInvokedClass
  2. local classObj =IsInvoked()--自动调用父类与子类的构造函数
  3. --调用普通方法
  4. classObj:Mehtod1()--ok
  5. --classObj.Mehtod1()--语法报错!
  6. classObj.Mehtod1(classObj)--语法OK
  7. --调用父类的字段与方法
  8. classObj:ShowFatherInfo();--调用父类的方法
  9. print(classObj.FatherClassName)--调用父类的公共字段
  10. print(classObj.ChildClassName)--调用子类的公共字段
复制代码

Lua调用C#的方法重载现象
  1. /*  定义方法重载  */
  2.         public voidMethod2(int num1,int num2){
  3.             Debug.Log(GetType()+"/Method2()/ 重载方法/int浮点型/num1="+ num1 +" num2="+ num2);}
  4.         public voidMethod2(float num1,float num2){
  5.             Debug.Log(GetType()+"/Method2()/ 重载方法/float浮点型/num1="+ num1+" num2="+num2);}
  6.         public voidMethod2(string str1, string str2){
  7.             Debug.Log(GetType()+"/Method2()/ 重载方法/字符串类型/str1="+ str1 +" str2="+ str2);}
复制代码
lua 中去调用
  1. --测试调用C#方法重载
  2. classObj:Method2(10,20)
  3. classObj:Method2("abc","def")
复制代码
这里发现 xlua 是支持方法重载的,但为“有限重载”。可以直接通过不同的参数类型进行重载函数的访问,例如:
testobj:TestFunc(100)
testobj:TestFunc(‘hello’)
将分别访问整数参数的 TestFunc 和字符串参数的 TestFunc。
但是,xlua 只一定程度上支持重载函数的调用,因为 lua 的类型远远不如C#丰富,存在一对多的情况,比如C#的int,float,double都对应于 lua 的 number,上面的例子中 TestFunc 如果有这些重载参数,第一行将无法区分开来,只能调用到其中一个(生成代码中排在前面的那个)

Lua调用C#中带参方法

C#中可变参数方法,Parmms关键字
  1. //定义带有返回数值,有参数的方法,且有params 关键字
  2.         public intMethod3(int num1,int num2, params string[] strArray){
  3.             Debug.Log(GetType()+"/Method3()/ 带有params关键字的方法/");
  4.             foreach (string item in strArray){
  5.                 Debug.Log("输入的字符串内容:"+item);}return num1 + num2;}
复制代码
在 lua 中
  1. --测试C#中带有params 关键字的方法
  2. local intResult=classObj:Method3(20,70,"Hello ","World","EveryOne")print("调用parmas关键字的方法,返回数值= "..intResult)
复制代码

C#结构体参数

lua 使用一个表,来映射C#的结构体
  1. //定义结构体(建议结构体成员为小写)
  2.     public struct MyStruct
  3.     {
  4.         public string x;
  5.         public string y;}//带有结构体参数的方法
  6.         public voidMethod4(MyStruct p){
  7.             Debug.Log("测试lua调用结构体方法");
  8.             Debug.Log("p.x="+ p.x);
  9.             Debug.Log("p.y="+ p.y);}
复制代码
在 lua 中
  1. --测试lua调用C#中带有结构体参数的方法
  2. --定义一个表
  3. myStructTable={x="C#语言",y="lua语言"}
  4. classObj:Method4(myStructTable)
复制代码

C#接口参数

注意: 接口需要加入标记: [CSharpCallLua]
lua 使用一个表,来映射C#的接口
  1. //定义接口[XLua.CSharpCallLua]
  2.     public interface MyInterface
  3.     {int x { get; set;}int y { get; set;}voidSpeak();}//方法具有接口为参数的
  4.         public voidMethod5(MyInterface p){
  5.             Debug.Log("测试lua调用具有接口为参数的方法");
  6.             Debug.Log("p.x="+ p.x);
  7.             Debug.Log("p.y="+ p.y);
  8.             p.Speak();}
复制代码
在 lua 中
  1. --测试lua调用C#中带有接口参数的方法
  2. --定义一个表
  3. myInterfaceTable={
  4.         x=1000,
  5.         y=300,
  6.         Speak=function()print("lua中 Speak 方法被调用!")
  7.         end
  8. }
  9. classObj:Method5(myInterfaceTable)
复制代码

C#委托参数

委托需要加入标记: [CSharpCallLua]
lua 使用一个函数,来映射C#的委托
  1. //定义委托[XLua.CSharpCallLua]
  2.     public delegate voidMyDelegate(int num);//方法具有委托为参数
  3.         public voidMethod6(MyDelegate p){
  4.             Debug.Log(GetType()+"/Method6()/委托参数:");//调用
  5.             p.Invoke(88);}
复制代码
在 lua 中
  1. --定义lua调用C#中带有委托参数的方法
  2. --定义函数
  3. myDelegate=function(num)print("lua 中对应委托方法。参数num="..num)
  4. end
  5. classObj:Method6(myDelegate)
复制代码
__
Lua接收C#方法返回的多个结果数值

基本规则: 参数的输入输出属性(out,ref)
A: C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算, 然后从左往右对应lua 调用的实参列表。
B: Lua调用返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。
  1. //定义一个具有多返回数值的方法
  2.         public intMethod7(int num1,out int num2,ref int num3){
  3.             Debug.Log(GetType()+"/Method7()/测试lua接收C#的多返回数值");
  4.             num2 =3000;
  5.             num3 =999;return num1 +100;}
复制代码
在 lua 中
  1. --接收C#多返回数值
  2. local num1=10
  3. local num2=20
  4. local res1,res2,res3=classObj:Method7(num1,num2)print("res1="..res1)--输出结果: 110print("res2="..res2)--输出结果: 3000print("res3="..res3)--输出结果: 999
复制代码

Lua如何调用C#泛型方法

基本规则:lua 不直接支持 C# 的泛型方法,但可以通过扩展方法功能进行封装后调用。
使用 Extension methods (扩展方法)技术就是 C# 中在不改变原始类的基础上,使用一种机制可以无限扩展这个类功能的机制。
原始类为: A
扩展类为: Ext_A
注意: Ext_A 必须是一个静态类,且扩展方法也必须是静态的。方法的参数中必须要有被扩展类作为其中一个参数,此参数前面必须有this 关键字修饰。
  1. //定义一个具有泛型方法为参数的。
  2.         public voidMethod8(List<string> strArray){
  3.             Debug.Log(GetType()+"/Method8()/这是一个具有泛型方法为参数的方法");
  4.             foreach (string item in strArray){
  5.                 Debug.Log("泛型集合中的内容="+item);}}
复制代码
在 lua 中
  1. --lua中可以直接调用具有泛型为参数的方法
  2. myTable8={"lua语言","C#语言","C++语言"}
  3. classObj:Method8(myTable8);
复制代码

另外一种就是
自定义泛型类
  1. namespace XluaPro
  2. {[XLua.LuaCallCSharp]
  3.         public class MyGengerric
  4.         {
  5.         public T GetMax<T>(T num1, T num2) where T : IComparable
  6.         {if(num1.CompareTo(num2)<0){return num2;}else{return num1;}}}}
复制代码
  1. //C#方法中,调用我们自定义的泛型方法
  2.         public voidMethod_InvokeGenger(){//int maxNum = 0;//int num1 = 100;//int num2 = 200;//MyGengerric obj = new MyGengerric();//maxNum = obj.GetMax<int>(num1, num2);//Debug.Log("C#中比较两个数字大小: "+maxNum);//测试字符串的比较
  3.             string maxStr = string.Empty;
  4.             string str1 ="xd";
  5.             string str2 ="kb";
  6.             MyGengerric obj = new MyGengerric();
  7.             maxStr = obj.GetMax<string>(str1, str2);
  8.             Debug.Log("C#中比较两个字符串大小: "+ maxStr);}
复制代码
在 lua 中,只能让上述方法跑起来
  1. --让C#方法运行起来。
  2. classObj:Method_InvokeGenger()
复制代码
如果要这样,去调用那个自定义泛型方法,会有错误
  1. --lua中直接调用C#中定义的泛型方法
  2. local maxNum=CS.XluaPro.MyGengerric:GetMax<int>(20,30)--报语法错误
  3. print("maxNum="..maxNum)
复制代码
那么怎么办,就有了扩展方法
这个类是一个“扩展方法”,本类的功能是扩展原有“MyGengerric”类的功能。
注意:
扩展方法有两大注意事项:
A: 扩展方法类,必须是静态类。
B: 定义的扩展方法的参数,第一个参数必须是this ,然后跟需要扩展的类名称全称。
  1. namespace XluaPro
  2. {[XLua.LuaCallCSharp]
  3.     public static class Extension_MyGengerric
  4.         {/// <summary>/// 定义扩展方法/// </summary>/// <param name="gen"></param>/// <param name="num1"></param>/// <param name="num2"></param>/// <returns></returns>
  5.         public staticintExtGetMax(this XluaPro.MyGengerric gen,int num1,int num2){if(num1<num2){return num2;}else{return num1;}}}}
复制代码
在 C# 中调用这个扩展方法
  1. //在C#中学习调用C#的扩展方法
  2.         public voidTest8_InvokeExtensionMethod(){int maxNum =0;int num1 =800;int num2 =200;
  3.             MyGengerric obj = new MyGengerric();
  4.             maxNum=obj.ExtGetMax(num1, num2);
  5.             Debug.Log("[应用扩展方法] C#中得到最大数值="+maxNum);}
复制代码
那如果是在 lua 中调用这个扩展方法
  1. --在lua中通过调用"扩展方法",来间接完成对C#“泛型方法”功能的实现。
  2. local maxNum=CS.XluaPro.MyGengerric():ExtGetMax(888,66)print("[在lua中扩展方法调用] maxNum="..maxNum)
复制代码

其他知识点

参数带默认值的方法
与C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会用默认值补上。
枚举类型
枚举值就像枚举类型下的静态属性一样。
testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1)
上面的 EnumTestFunc 函数参数是 Tutorial.TestEnum 类型的。
委托与事件
delegate 属性可以用一个 luaFunction 来赋值
比如 testobj 里头有个事件定义是这样:public event Action TestEvent;
增加事件回调
testobj:TestEvent(’+’, lua_event_callback)
移除事件回调
testobj:TestEvent(’-’, lua_event_callback)

Lua调用C#经验小结

1。lua 调用C#,需要在 Xlua 中生成“适配代码”,并在这个类打入一个 [LuaCallCSharp] 的标签。
2。如果lua调用C#的系统API ,则无法拿到源代码,无法打入标签。则使用“静态列表”方式解决。
  1. public static List<Type> mymodule_LuaCallCS_List = new List<Type>(){typeof(GameObject),typeof(Dictionary<string,int>),};
复制代码
然后把以上代码放入一个静态类中即可。
3。实际开发过程中,lua 调用 C# 用的比较多。xlua 的优点体现在没有必要每次修改的时候,都要生成代码。主要原理是依赖于编译器环境下,利用反射来动态生成代码接口。
4。在标有 “[XLua.LuaCallCSharp]” 的 C# 类中,添加新的方法后,如果是生成了代码类,则必须重新生成或者删除,否则 Xlua 还是用以前生成的代码进行注册查询,会出现 lua 异常:“试图访问一个nil 的方法”。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-28 04:55 , Processed in 0.092811 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表