Unity XLua 官方教程学习
一、Lua 文件加载
1. 执行字符串
1 using UnityEngine; 2 using XLua; 3 4 public class ByString : MonoBehaviour { 5 LuaEnv luaenv = null; 6 // Use this for initialization 7 void Start () { 8 luaenv = new LuaEnv(); 9 // 执行代码块,输出 hello world 10 luaenv.DoString("print('hello world')"); 11 } 12 13 // Update is called once per frame 14 void Update () { 15 if (luaenv != null) 16 { 17 // 清楚 Lua 未手动释放的 LuaBase 对象 18 luaenv.Tick(); 19 } 20 } 21 22 void OnDestroy() 23 { 24 // 销毁 25 luaenv.Dispose(); 26 } 27 }
其中 Dostring 函数返回值即为代码块里 return 语句的返回值。
2. 加载 Lua 文件
1 luaenv = new LuaEnv(); 2 // 加载 byfile Lua 文件 3 luaenv.DoString("require 'byfile'");
其中 Lua 文件代码为:
print('hello world')
需要注意的是因为 Resource 只支持有限的后缀,放 Resources 下 lua 文件得加上 txt 后缀,如:byfile.lua.txt。
3. 自定义 Loader
1 void Start() 2 { 3 luaenv = new LuaEnv(); 4 // 自定义 loader 5 luaenv.AddLoader((ref string filename) => { 6 // 若要加载 InMemory 7 if (filename == "InMemory") 8 { 9 string script = "return {ccc = 9999}"; 10 // 将字符串转换成byte[] 11 return System.Text.Encoding.UTF8.GetBytes(script); 12 } 13 return null; 14 }); 15 // 执行代码块,访问table中的常量ccc 16 luaenv.DoString("print('InMemory.ccc=', require('InMemory').ccc)"); 17 }
通过 Addloader 可以注册个回调,该回调参数是字符串,返回一个 byte 数组。lua 代码里调用 require 时,参数就会传给回调。
注意,require 返回一个由模块常量和函数组成的table。
二、C# 访问 Lua
其中 lua 代码如下:
1 a = 1 2 b = 'hello world' 3 c = true 4 5 d = { 6 f1 = 12, f2 = 34, 7 1, 2, 3, 8 add = function(self, a, b) 9 print('d.add called') 10 return a + b 11 end 12 } 13 14 function e() 15 print('i am e') 16 end 17 18 function f(a, b) 19 print('a', a, 'b', b) 20 return 1, {f1 = 1024} 21 end 22 23 function ret_e() 24 print('ret_e called') 25 return e 26 end
其中包含常量,表和函数。C# 代码如下:
1 public class DClass 2 { 3 public int f1; // 属性与Xlua里的对应 4 public int f2; 5 } 6 7 [CSharpCallLua] 8 public interface ItfD 9 { 10 int f1 { get; set; } 11 int f2 { get; set; } 12 int add(int a, int b); 13 } 14 15 // 生成代码 16 // 若有多个参数,可用 out 属性接收剩下的返回 17 [CSharpCallLua] 18 public delegate int FDelegate(int a, string b, out DClass c); 19 20 [CSharpCallLua] 21 public delegate Action GetE(); 22 23 // Use this for initialization 24 void Start() 25 { 26 luaenv = new LuaEnv(); 27 luaenv.DoString(script); 28 29 // 访问全局常量 30 Debug.Log("_G.a = " + luaenv.Global.Get<int>("a")); // 1 31 Debug.Log("_G.b = " + luaenv.Global.Get<string>("b")); // hello world 32 Debug.Log("_G.c = " + luaenv.Global.Get<bool>("c")); // Ture 33 34 // 访问全局的 table 35 // 映射到普通的class或struct 36 //映射到有对应字段的class,值拷贝,class字段的修改不会影响到table,反之也不会 37 DClass d = luaenv.Global.Get<DClass>("d"); 38 Debug.Log("_G.d = {f1=" + d.f1 + ", f2=" + d.f2 + "}"); // 12 34 39 40 // 映射有 key 的 41 Dictionary<string, double> d1 = luaenv.Global.Get<Dictionary<string, double>>("d");//映射到Dictionary<string, double>,值拷贝 42 Debug.Log("_G.d = {f1=" + d1["f1"] + ", f2=" + d1["f2"] + "}, d.Count=" + d1.Count); // 12 34 2 43 44 // 映射没有 key 的 45 List<double> d2 = luaenv.Global.Get<List<double>>("d"); //映射到List<double>,值拷贝 46 Debug.Log("_G.d.len = " + d2.Count); // 3 47 48 // 映射到一个 interface 49 // 要在 interface 定义前加 [CSharpCallLua] 50 ItfD d3 = luaenv.Global.Get<ItfD>("d"); //映射到interface实例,by ref,这个要求interface加到生成列表,否则会返回null,建议用法 51 d3.f2 = 1000; // 外部修改会影响 Lua 内的值 52 Debug.Log("_G.d = {f1=" + d3.f1 + ", f2=" + d3.f2 + "}"); // 12 1000 53 Debug.Log("_G.d:add(1, 2)=" + d3.add(1, 2)); // d.add called 54 55 //映射到LuaTable,by ref 56 LuaTable d4 = luaenv.Global.Get<LuaTable>("d"); 57 Debug.Log("_G.d = {f1=" + d4.Get<int>("f1") + ", f2=" + d4.Get<int>("f2") + "}"); 58 59 // 访问一个全局的函数 60 // 映射到 delegate 61 Action e = luaenv.Global.Get<Action>("e");//映射到一个delgate,要求delegate加到生成列表,否则返回null,建议用法 62 e(); // i am e 63 64 FDelegate f = luaenv.Global.Get<FDelegate>("f"); 65 DClass d_ret; 66 // 多值返回,可用out接收多余的参数 67 // 输出 a 100 b John 68 int f_ret = f(100, "John", out d_ret);//lua的多返回值映射:从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数 69 // table只含有常量f1,所以f2赋值为0 70 Debug.Log("ret.d = {f1=" + d_ret.f1 + ", f2=" + d_ret.f2 + "}, ret=" + f_ret); // 1024 0 1 71 72 GetE ret_e = luaenv.Global.Get<GetE>("ret_e");//delegate可以返回更复杂的类型,甚至是另外一个delegate 73 e = ret_e(); 74 e(); 75 76 // 映射到 LuaFunction 77 LuaFunction d_e = luaenv.Global.Get<LuaFunction>("e"); 78 // Call 函数可以传任意类型,任意个数的参数 79 d_e.Call(); 80 81 }
访问 lua 全局数据,特别是 table 以及 function,代价比较大,建议尽量少做,比如在初始化时调用获取一次后,保存下来,后续直接使用即可。
三、Lua 调用 C#
其中 C# 代码如下:
1 namespace Tutorial 2 { 3 [LuaCallCSharp] 4 public class BaseClass 5 { 6 public static void BSFunc() 7 { 8 Debug.Log("Driven Static Func, BSF = "+ BSF); 9 } 10 11 public static int BSF = 1; 12 13 public void BMFunc() 14 { 15 Debug.Log("Driven Member Func, BMF = " + BMF); 16 } 17 18 public int BMF { get; set; } 19 } 20 21 public struct Param1 22 { 23 public int x; 24 public string y; 25 } 26 27 [LuaCallCSharp] 28 public enum TestEnum 29 { 30 E1, 31 E2 32 } 33 34 [LuaCallCSharp] 35 public class DrivenClass : BaseClass 36 { 37 [LuaCallCSharp] 38 public enum TestEnumInner 39 { 40 E3, 41 E4 42 } 43 44 public void DMFunc() 45 { 46 Debug.Log("Driven Member Func, DMF = " + DMF); 47 } 48 49 public int DMF { get; set; } 50 51 public double ComplexFunc(Param1 p1, ref int p2, out string p3, Action luafunc, out Action csfunc) 52 { 53 Debug.Log("P1 = {x=" + p1.x + ",y=" + p1.y + "},p2 = "+ p2); 54 luafunc(); 55 p2 = p2 * p1.x; 56 p3 = "hello " + p1.y; 57 csfunc = () => 58 { 59 Debug.Log("csharp callback invoked!"); 60 }; 61 return 1.23; 62 } 63 64 public void TestFunc(int i) 65 { 66 Debug.Log("TestFunc(int i)"); 67 } 68 69 public void TestFunc(string i) 70 { 71 Debug.Log("TestFunc(string i)"); 72 } 73 74 public static DrivenClass operator +(DrivenClass a, DrivenClass b) 75 { 76 DrivenClass ret = new DrivenClass(); 77 ret.DMF = a.DMF + b.DMF; 78 return ret; 79 } 80 81 public void DefaultValueFunc(int a = 100, string b = "cccc", string c = null) 82 { 83 UnityEngine.Debug.Log("DefaultValueFunc: a=" + a + ",b=" + b + ",c=" + c); 84 } 85 86 public void VariableParamsFunc(int a, params string[] strs) 87 { 88 UnityEngine.Debug.Log("VariableParamsFunc: a =" + a); 89 foreach (var str in strs) 90 { 91 UnityEngine.Debug.Log("str:" + str); 92 } 93 } 94 95 public TestEnum EnumTestFunc(TestEnum e) 96 { 97 Debug.Log("EnumTestFunc: e=" + e); 98 return TestEnum.E2; 99 } 100 101 public Action<string> TestDelegate = (param) => 102 { 103 Debug.Log("TestDelegate in c#:" + param); 104 }; 105 106 public event Action TestEvent; 107 108 public void CallEvent() 109 { 110 TestEvent(); 111 } 112 113 public ulong TestLong(long n) 114 { 115 return (ulong)(n + 1); 116 } 117 118 class InnerCalc : ICalc 119 { 120 public int add(int a, int b) 121 { 122 return a + b; 123 } 124 125 public int id = 100; 126 } 127 128 public ICalc GetCalc() 129 { 130 return new InnerCalc(); 131 } 132 133 public void GenericMethod<T>() 134 { 135 Debug.Log("GenericMethod<" + typeof(T) + ">"); 136 } 137 } 138 139 [LuaCallCSharp] 140 public interface ICalc 141 { 142 int add(int a, int b); 143 } 144 145 [LuaCallCSharp] 146 public static class DrivenClassExtensions 147 { 148 public static int GetSomeData(this DrivenClass obj) 149 { 150 Debug.Log("GetSomeData ret = " + obj.DMF); 151 return obj.DMF; 152 } 153 154 public static int GetSomeBaseData(this BaseClass obj) 155 { 156 Debug.Log("GetSomeBaseData ret = " + obj.BMF); 157 return obj.BMF; 158 } 159 160 public static void GenericMethodOfString(this DrivenClass obj) 161 { 162 obj.GenericMethod<string>(); 163 } 164 } 165 }
其中可变参数可用 params string[] strs 实现。要在 Lua 直接访问什么,记得在定义前加上 [LuaCallCSharp]
对应的 lua 代码为:
1 function demo() 2 -- new C#对象 3 -- 没有new,所有C#相关的都放在CS下 4 local newGameObj = CS.UnityEngine.GameObject() 5 -- 创建一个名为helloworld的物体 6 local newGameObj2 = CS.UnityEngine.GameObject('helloworld') 7 print(newGameObj, newGameObj2) 8 9 --访问静态属性,方法 10 local GameObject = CS.UnityEngine.GameObject 11 print('UnityEngine.Time.deltaTime:', CS.UnityEngine.Time.deltaTime) --读静态属性 12 CS.UnityEngine.Time.timeScale = 0.5 --写静态属性 13 -- 查找物体 helloworld 14 print('helloworld', GameObject.Find('helloworld')) --静态方法调用 15 16 --访问成员属性,方法 17 local DrivenClass = CS.Tutorial.DrivenClass 18 local testobj = DrivenClass() 19 testobj.DMF = 1024--设置成员属性 20 print(testobj.DMF)--读取成员属性 21 -- 输出 DMF=1024 22 testobj:DMFunc()--成员方法 使用冒号 23 24 --基类属性,方法 25 print(DrivenClass.BSF)--读基类静态属性 1 26 DrivenClass.BSF = 2048--写基类静态属性 27 DrivenClass.BSFunc();--基类静态方法 2048 28 -- BMF 初始为0 29 print(testobj.BMF)--读基类成员属性 0 30 testobj.BMF = 4096--写基类成员属性 31 testobj:BMFunc()--基类方法调用 4096 32 33 --复杂方法调用 34 -- 参数处理规则:C#的普通参数和ref修饰的算一个参数,out不算,从左往右顺序 35 -- 返回值处理规则:返回值(如果有)算第一个,out,ref修饰的参数算一个,从左往右 36 local ret, p2, p3, csfunc = testobj:ComplexFunc({x=3, y = 'john'}, 100, function() 37 print('i am lua callback') 38 end) 39 print('ComplexFunc ret:', ret, p2, p3, csfunc) 40 csfunc() 41 42 --重载方法调用 43 testobj:TestFunc(100) 44 testobj:TestFunc('hello') 45 46 --操作符 47 local testobj2 = DrivenClass() 48 testobj2.DMF = 2048 49 -- 输出DMF=1024+2048=3072 50 print('(testobj + testobj2).DMF = ', (testobj + testobj2).DMF) 51 52 --默认值 53 testobj:DefaultValueFunc(1) 54 testobj:DefaultValueFunc(3, 'hello', 'john') 55 56 --可变参数 57 testobj:VariableParamsFunc(5, 'hello', 'john') 58 59 --Extension methods 60 print(testobj:GetSomeData()) 61 print(testobj:GetSomeBaseData()) --访问基类的Extension methods 62 testobj:GenericMethodOfString() --通过Extension methods实现访问泛化方法 63 64 --枚举类型 65 -- 返回E2 66 local e = testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1) 67 -- 输出枚举类型格式为 E2:1 68 print(e, e == CS.Tutorial.TestEnum.E2) 69 -- 整数或者字符串到枚举类型的转换 70 print(CS.Tutorial.TestEnum.__CastFrom(1), CS.Tutorial.TestEnum.__CastFrom('E1')) 71 print(CS.Tutorial.DrivenClass.TestEnumInner.E3) 72 assert(CS.Tutorial.BaseClass.TestEnumInner == nil) 73 74 --委托 75 testobj.TestDelegate('hello') --直接调用 76 local function lua_delegate(str) 77 print('TestDelegate in lua:', str) 78 end 79 testobj.TestDelegate = lua_delegate + testobj.TestDelegate --combine,这里演示的是C#delegate作为右值,左值也支持 80 testobj.TestDelegate('hello') 81 testobj.TestDelegate = testobj.TestDelegate - lua_delegate --remove 82 testobj.TestDelegate('hello') 83 84 --事件 85 local function lua_event_callback1() print('lua_event_callback1') end 86 local function lua_event_callback2() print('lua_event_callback2') end 87 -- 增加回调事件 88 testobj:TestEvent('+', lua_event_callback1) 89 testobj:CallEvent() 90 testobj:TestEvent('+', lua_event_callback2) 91 testobj:CallEvent() 92 -- 移除回调事件 93 testobj:TestEvent('-', lua_event_callback1) 94 testobj:CallEvent() 95 testobj:TestEvent('-', lua_event_callback2) 96 97 --64位支持 98 local l = testobj:TestLong(11) 99 print(type(l), l, l + 100, 10000 + l) 100 101 --typeof 102 -- 增加粒子系统 103 newGameObj:AddComponent(typeof(CS.UnityEngine.ParticleSystem)) 104 105 --cast 强转 106 -- 返回 InnerCalc 类 107 local calc = testobj:GetCalc() 108 print('assess instance of InnerCalc via reflection', calc:add(1, 2)) 109 assert(calc.id == 100) 110 -- 强转 111 cast(calc, typeof(CS.Tutorial.ICalc)) 112 print('cast to interface ICalc', calc:add(1, 2)) 113 assert(calc.id == nil) 114 end 115 116 demo() 117 118 --协程下使用 119 local co = coroutine.create(function() 120 print('------------------------------------------------------') 121 demo() 122 end) 123 assert(coroutine.resume(co))
其中 assert 函数用于有错误时抛出异常。
注意,C# 的 int, float, double 都对应于 lua 的 number,重载时会无法区分。