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

Xlua初识

[复制链接]
发表于 2022-6-7 07:55 | 显示全部楼层 |阅读模式
Xlua简介

      xLua是由腾讯维护的一个开源项目,xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在,腾讯已经将xLua开源到GitHub。其git地址是:https://github.com/Tencent/xLua。
lua文件加载
  1.         void Start()
  2.         {
  3.             //new 一个lua解释器
  4.             LuaEnv luaenv = new LuaEnv();
  5.             //1.调用dostring方法,执行字符串
  6.             luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
  7.             //2.加载lua脚本
  8.             luaenv.DoString("require('Main')");
  9.             //3.通过AddLoader添加回调,可以自定义加载lua文件路径,返回byte数组
  10.             luaenv.AddLoader(CustomLoader);
  11.             luaenv.Dispose();
  12.         }
  13.         /// <summary>
  14.         /// 自定义加载lua脚本
  15.         /// </summary>
  16.         /// <param name="filepath"></param>
  17.         /// <returns></returns>
  18.         public  byte[] CustomLoader(ref string filepath)
  19.         {
  20.             //自定义加载路径
  21.             //加载lua文件
  22.             //读取字节返回
  23.         }
复制代码
C#访问Lua

  1.获取全局数据结构类型。访问LuaEnv.Global即可。 Global定义了一个lua全局环境_G
  1. luaenv.Global.Get<int>("a")
  2. luaenv.Global.Get<string>("b")
  3. luaenv.Global.Get<bool>("c")
复制代码
2.访问一个全局Table
    方式一:映射到普通class或struct
  1.     string script = @"
  2.         d = {
  3.            f1 = 12, f2 = 34,
  4.            1, 2, 3,
  5.            add = function(self, a, b)
  6.               print('d.add called')
  7.               return a + b
  8.            end
  9.         }
  10.     ";
  11.     public class DClass {
  12.             public int f1;
  13.             public int f2;
  14.     }
  15.     DClass d = luaenv.Global.Get<DClass>("d");//映射到有对应字段的class,by value
  16.     Debug.Log("_G.d = {f1=" + d.f1 + ", f2=" + d.f2 + "}");
  17. //定义一个Class,有对应的table的字段public属性,而且有无参数构造函数即可。这种方式下xlua会帮你new一个实例,并把对应的字段赋值过去
  18. //table的属性可以多于或少于class属性。可以嵌套其他复杂类型。要注意的是,这个过程是值拷贝,如果class比较复代价会比较大,而且修改class字段的值不会同步到table,反过来也不会
复制代码
  方式二:映射到interface

  1.         [CSharpCallLua]//需要加[CSharpCallLua]特性
  2.         public interface ItfD
  3.         {
  4.             int f1 { get; set; }
  5.             int f2 { get; set; }
  6.             int add(int a, int b);
  7.         }
  8.             ItfD d3 = luaenv.Global.Get<ItfD>("d"); //映射到interface实例,by ref,这个要求interface加到生成列表,否则会返回null,建议用法
  9.             d3.f2 = 1000;
  10.             Debug.Log("_G.d = {f1=" + d3.f1 + ", f2=" + d3.f2 + "}");
  11.             Debug.Log("_G.d:add(1, 2)=" + d3.add(1, 2));
  12. //这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。
复制代码
方式三:更轻量级的by value方式:映射到Dictionary<> , list<> 不想定义class或者interface的话,可以考虑这个,前提是table下key和value类型都是一致的。
  1.     string script = @"
  2.         d = {
  3.            f1 = 12, f2 = 34,
  4.            1, 2, 3,
  5.            add = function(self, a, b)
  6.               print('d.add called')
  7.               return a + b
  8.            end
  9.         }
  10.     ";
  11.             Dictionary<string, double> d1 = luaenv.Global.Get<Dictionary<string, double>>("d");//映射到Dictionary<string, double>,by value
  12.             Debug.Log("_G.d = {f1=" + d1["f1"] + ", f2=" + d1["f2"] + "}, d.Count=" + d1.Count);
  13.             List<double> d2 = luaenv.Global.Get<List<double>>("d"); //映射到List<double>,by value
  14.             Debug.Log("_G.d.len = " + d2.Count);
复制代码
2.4 另外一种by ref方式:映射到LuaTable类。这种方式好处是不需要生成代码,但也有一下问题,比如慢,没有类型检查
  1.             LuaTable d4 = luaenv.Global.Get<LuaTable>("d");//映射到LuaTable,by ref
  2.             Debug.Log("_G.d = {f1=" + d4.Get<int>("f1") + ", f2=" + d4.Get<int>("f2") + "}");
复制代码
3.访问一个全局function
任然使用Get方法,不同的是类型映射
方式一:映射到delegate。这种事建议的方法,性能好很多。而且类型安全。缺点是要生成代码。
delegate要怎样声明呢? 对于function的每个参数就声明一个输入类型的参数。 多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。
delegate的使用就更简单了,直接像个函数那样用就可以了。
  1. string script = @"
  2.     function e()
  3.         print('i am e')
  4.     end
  5.     function f(a, b)   
  6.         print('a', a, 'b', b)
  7.         return 1, {f1 = 1024}
  8.     end
  9.     function ret_e()
  10.         print('ret_e called')
  11.         return e
  12.     end";
  13. public class DClass
  14. {   
  15.     public int f1;
  16.     public int f2;
  17. }
  18. [CSharpCallLua]
  19. public delegate int FDelegate(int a, string b, out DClass c);
  20. [CSharpCallLua]
  21. public delegate Action GetE();
  22. Action e = luaenv.Global.Get<Action>("e");//映射到一个delgate,要求delegate加到生成列表,否则返回null,建议用法
  23. e();
  24. FDelegate f = luaenv.Global.Get<FDelegate>("f");
  25. DClass d_ret;
  26. int f_ret = f(100, "John", out d_ret);//lua的多返回值映射:从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数
  27. Debug.Log("ret.d = {f1=" + d_ret.f1 + ", f2=" + d_ret.f2 + "}, ret=" + f_ret);
  28. GetE ret_e = luaenv.Global.Get<GetE>("ret_e");//delegate可以返回更复杂的类型,甚至是另外一个delegate
  29. e = ret_e();
  30. e();
复制代码
方式二:映射到LuaFunction。这种方式优缺点和方式一相反。使用简单,LuaFunction上有个变参Call函数,可以传入任意类型,任意个数的参数,返回值是object数组,对应于lua的多返回值
  1. LuaFunction d_e = luaenv.Global.Get<LuaFunction>("e");
  2. d_e.Call();
复制代码
官方使用建议:

    访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
    如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。
Lua调用C#

new C# 对象
  1. --lua脚本代码实例
  2. local newGameObj = CS.UnityEngine.GameObject()
  3. local newGameObj2 = CS.UnityEngine.GameObject('helloworld')
  4. print(newGameObj, newGameObj2)
  5. ---------------
  6. 基本类似,除了:
  7.     1. lua里头没有new关键字;
  8.     2. 所有C#相关的都放到CS下,包括构造函数,静态成员属性、方法;
  9. 如果有多个构造函数呢?放心,xlua支持重载,比如你要调用GameObject的带一个string参数的构造函数,这么写:
  10. local newGameObj2 = CS.UnityEngine.GameObject('helloworld')
复制代码
访问C#静态属性,方法
  1. --读静态属性
  2. Cs.UnityEngine.Time.deltaTime
  3. --写静态属性
  4. Cs.UnityEngine.Time.timeScale = 0.5
  5. --调用静态方法
  6. CS.UnityEngine.GameObject.Find('helloworld')
  7. 小技巧:如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能:
  8. local GameObject = CS.UnityEngine.GameObject
  9. GameObject.Find('helloworld')
  10. --建议_G内设置全局变量
复制代码
访问C#成员属性, 方法
  1. --读成员属性
  2. testobj.DMF
  3. --写成员属性
  4. testobj.DMF = 1024
  5. --调用成员方法
  6. --注意:调用成员方法,第一个参数需要传该对象,建议用冒号语法糖,如下
  7. testobj:DMFunc()
复制代码
父类属性,方法。
  1. xlua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类实例)访问基类的成员属性,成员方法
复制代码
参数的输入输出属性(out,ref)
  1. Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,然后从左往右对应lua 调用侧的实参列表;
  2. Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。
复制代码
重载方法
  1. --直接通过不同的参数类型进行重载函数的访问,例如:
  2. testobj:TestFunc(100)
  3. testobj:TestFunc('hello')
  4. --注意:xlua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#丰富,存在一对多的情况,比如C#的int,float,double都对应于lua的number,上面的例子中TestFunc如果有这些重载参数,第一行将无法区分开来,只能调用到其中一个(生成代码中排前面的那个)
复制代码
操作符
  1. 支持的操作符有:+,-,*,/,==,一元-,<,<=, %,[]
复制代码
参数带默认值的方法
  1. 和C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会用默认值补上。
复制代码
可变参数方法: 实例: params string[] str
  1. //对于C#的如下方法:
  2. void VariableParamsFunc(int a, params string[] strs)
  3. --可以在lua里头这样调用:
  4. testobj:VariableParamsFunc(5, 'hello', 'john')
复制代码
使用Extension methods (扩展方法),在C#里定义了,lua里就能直接使用。

Extension Methods - C# Programming Guide | Microsoft Docs
枚举类型
  1. 枚举值就像枚举类型下的静态属性一样。
  2. //C#
  3. [LuaCallCSharp]
  4. public enum TestEnum
  5. {
  6.         E1,
  7.         E2
  8. }
  9. -- lua
  10. local test = CS.TestEnum.E1
  11. 枚举类支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换,例如:
  12. CS.TestEnum.__CastFrom(1)
  13. CS.TestEnum.__CastFrom('E1')
复制代码
delegate使用(调用,+,-)

C#的delegate调用:和调用普通lua函数一样
+操作符:对应C#的+操作符,把两个调用串成一个调用链,右操作数可以是同类型的C# delegate或者是lua函数。
-操作符:和+相反,把一个delegate从调用链中移除。
Ps:delegate属性可以用一个luafunction来赋值。
event
比如testobj里头有个事件定义是这样:public event Action TestEvent;
增加事件回调
  1. testobj:TestEvent('+', lua_event_callback)
复制代码
移除事件回调
  1. testobj:TestEvent('-', lua_event_callback)
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-26 20:47 , Processed in 0.066696 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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