lua解析器
cs
void Start()
{
//Lua解析器 能够让我们在Unity中执行Lua
//一般情况下 保持它的唯一性
LuaEnv env = new LuaEnv();
//执行Lua语言
env.DoString("print('你好世界')");
//执行一个Lua脚本 Lua知识点 :多脚本执行 require
//默认寻找脚本的路径 是在 Resources下 并且 因为在这里
//估计是通过 Resources.Load去加载Lua脚本 txt bytes等等
//所以Lua脚本 后缀要加一个txt
env.DoString("require('Main')");
//帮助我们清除Lua中我们没有手动释放的对象 垃圾回收
//帧更新中定时执行 或者 切场景时执行
env.Tick();
//销毁Lua解析器
env.Dispose();
}
文件加载重定向
cs
env.AddLoader(MyCustomLoader);
private byte[] MyCustomLoader(ref string filePath){
return null;
}
解析器管理器
Lua管理器:
提供 lua解析器,保证解析器的唯一性-->使用单例模式
(Lua脚本会放到AB包中,最终通过加载AB包再加载其中的lua脚本资源来执行,但是AB包中如果要加载文本,后缀有一定的限制,.lua不能被识别)
从AB包加载文件:
cs
private byte[] MyCustomABLoader(ref string filePath)
{
//Debug.Log("进入AB包加载 重定向函数");
//从AB包中加载lua文件
//加载AB包
//string path = Application.streamingAssetsPath + "/lua";
//AssetBundle ab = AssetBundle.LoadFromFile(path);
//加载Lua文件 返回
//TextAsset tx = ab.LoadAsset<TextAsset>(filePath + ".lua");
//加载Lua文件 byte数组
//return tx.bytes;
//通过我们的AB包管理器 加载的lua脚本资源
TextAsset lua = ABMgr.GetInstance().LoadRes<TextAsset>("lua", filePath + ".lua");
if (lua != null)
return lua.bytes;
else
Debug.Log("MyCustomABLoader重定向失败,文件名为:" + filePath);
return null;
}
(Lua脚本会放在AB包 最终会通过加载AB包再加载其中的Lua脚本资源 来执行)
获取全局变量
只要在xlua中执行lua程序,都会自动进入重定向文件找
Lua代码
Lua
testNumber = 1
testBool = true
testFloat = 1.2
testString = "123"
C#代码
cs
//使用lua解析器luaenv中的 Global属性 (利用大G表)
int i = LuaMgr.GetInstance().Global.Get<int>("testNumber");
Debug.Log("testNumber:" + i);
i = 10;
//改值
LuaMgr.GetInstance().Global.Set("testNumber", 55);
//值拷贝 不会影响原来Lua中的值
int i2 = LuaMgr.GetInstance().Global.Get<int>("testNumber");
Debug.Log("testNumber_i2:" + i2);
bool b = LuaMgr.GetInstance().Global.Get<bool>("testBool");
Debug.Log("testBool:" + b);
float f = LuaMgr.GetInstance().Global.Get<float>("testFloat");
Debug.Log("testFloat:" + f);
double d = LuaMgr.GetInstance().Global.Get<double>("testFloat");
Debug.Log("testFloat_double:" + d);
string s = LuaMgr.GetInstance().Global.Get<string>("testString");
Debug.Log("testString:" + s);
不能直接得到lua代码中的本地变量(local)
获取全局函数
lua代码:
Lua
--无参无返回
testFun = function()
print("无参无返回")
end
--有参有返回
testFun2 = function(a)
print("有参有返回")
return a + 1
end
--多返回
testFun3 = function(a)
print("多返回值")
return 1, 2, false, "123", a
end
--变长参数
testFun4 = function(a, ...)
print("变长参数")
print(a)
arg = {...}
for k,v in pairs(arg) do
print(k,v)
end
end
c#代码:
c#去存函数-->使用委托
cs
//无参无返回的获取
//委托
CustomCall call = LuaMgr.GetInstance().Global.Get<CustomCall>("testFun");
call();
//Unity自带委托
UnityAction ua = LuaMgr.GetInstance().Global.Get<UnityAction>("testFun");
ua();
//C#提供的委托
Action ac = LuaMgr.GetInstance().Global.Get<Action>("testFun");
ac();
//Xlua提供的一种 获取函数的方式 少用
//需要引用xlua
LuaFunction lf = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun");
lf.Call();
//有参有返回
CustomCall2 call2 = LuaMgr.GetInstance().Global.Get<CustomCall2>("testFun2");
Debug.Log("有参有返回:" + call2(10));
//C#自带的泛型委托 方便我们使用
Func<int, int> sFun = LuaMgr.GetInstance().Global.Get<Func<int, int>>("testFun2");
Debug.Log("有参有返回:" + sFun(20));
//Xlua提供的
LuaFunction lf2 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun2");
Debug.Log("有参有返回:" + lf2.Call(30)[0]);
//多返回值
//使用 out 和 ref 来接收
CustomCall3 call3 = LuaMgr.GetInstance().Global.Get<CustomCall3>("testFun3");
int b;
bool c;
string d;
int e;
Debug.Log("第一个返回值:" + call3(100, out b, out c, out d, out e));
Debug.Log(b + "_" + c + "_" + d + "_" + e);
CustomCall4 call4 = LuaMgr.GetInstance().Global.Get<CustomCall4>("testFun3");
int b1 = 0;
bool c1 = true;
string d1 = "";
int e1 = 0;
Debug.Log("第一个返回值:" + call4(200, ref b1, ref c1, ref d1, ref e1));
Debug.Log(b1 + "_" + c1 + "_" + d1 + "_" + e1);
//Xlua
LuaFunction lf3 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun3");
object[] objs = lf3.Call(1000);
for( int i = 0; i < objs.Length; ++i )
{
Debug.Log("第" + i + "个返回值是:" + objs[i]);
}
//变长参数
CustomCall5 call5 = LuaMgr.GetInstance().Global.Get<CustomCall5>("testFun4");
call5("123", 1, 2, 3, 4, 5, 566, 7, 7, 8, 9, 99);
LuaFunction lf4 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun4");
lf4.Call("456", 6, 7, 8, 99, 1);
自定义的委托需要在c#脚本中添加特性,并在unity的xlua中生成code
cs
//无参无返回值的委托
public delegate void CustomCall();
//有参有返回 的委托
//该特性是在XLua命名空间中的
//加了过后 要在编辑器里 生成 Lua代码
[CSharpCallLua]
public delegate int CustomCall2(int a);
[CSharpCallLua]
public delegate int CustomCall3(int a, out int b, out bool c, out string d, out int e);
[CSharpCallLua]
public delegate int CustomCall4(int a, ref int b, ref bool c, ref string d, ref int e);
[CSharpCallLua]
public delegate void CustomCall5(string a, params int[] args);
表(Table)映射 List 和 Dictionary
Lua代码:
Lua
--List
testList = {1,2,3,4,5,6}
testList2 = {"123", "123", true, 1, 1.2}
--Dictionary
testDic = {
["1"] = 1,
["2"] = 2,
["3"] = 3,
["4"] = 4
}
testDic2 = {
["1"] = 1,
[true] = 1,
[false] = true,
["123"] = false
}
C#代码:
cs
//同一类型List
List<int> list = LuaMgr.GetInstance().Global.Get<List<int>>("testList");
Debug.Log("*******************List************************");
for ( int i = 0; i <list.Count; ++i )
{
Debug.Log(list[i]);
}
//值拷贝 浅拷贝 不会改变lua中的内容
list[0] = 100;
List<int> list2 = LuaMgr.GetInstance().Global.Get<List<int>>("testList");
Debug.Log(list2[0]);
//不指定类型 object
List<object> list3 = LuaMgr.GetInstance().Global.Get<List<object>>("testList2");
Debug.Log("*******************List object************************");
for (int i = 0; i < list3.Count; ++i)
{
Debug.Log(list3[i]);
}
Debug.Log("*******************Dictionary************************");
Dictionary<string, int> dic = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic");
foreach (string item in dic.Keys)
{
Debug.Log(item + "_" + dic[item]);
}
dic["1"] = 100000;
//值拷贝 不会改变lua中的内容
Dictionary<string, int> dic2 = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic");
Debug.Log(dic2["1"]);
Debug.Log("*******************Dictionary object************************");
Dictionary<object, object> dic3 = LuaMgr.GetInstance().Global.Get<Dictionary<object, object>>("testDic2");
foreach (object item in dic3.Keys)
{
Debug.Log(item + "_" + dic3[item]);
}
表映射到 类
lua代码:
Lua
testClass = {
testInt = 2,
testBool = true,
testFloat = 1.2,
testString = "123",
testFun = function()
print("123123123")
end
}
c#代码:
cs
CallLuaClass obj = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClass");
Debug.Log(obj.testInt);
Debug.Log(obj.testBool);
//Debug.Log(obj.testFloat);
Debug.Log(obj.testString);
Debug.Log(obj.i);
Debug.Log("嵌套:" + obj.testInClass.testInInt);
obj.testFun();
//值拷贝 改变了它 不会改变Lua表里的内容
obj.testInt = 100;
CallLuaClass obj2 = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClass");
Debug.Log(obj2.testInt);
在类声明成员变量的过程中名字一定要和lua的相同
自定义中的变量可以更多也可以更少,如果变量比 lua中的少 就会忽略它,如果变量比 lua中的多 不会赋值 也会忽略
cs
public class CallLuaClass
{
public int testInt;
public bool testBool;
public float testString;
public UnityAction testFun;
public CallLuaInClass testInClass;
public int i;
public void Test()
{
Debug.Log(testInt);
}
}
表映射接口
接口的实现:
由于在接口中不允许成员变量的存在,因此使用属性来接受Lua中的变量
改了c#代码结构后,先在xlua中清掉原来的,再生成新的code
cs
[CSharpCallLua]
public interface ICSharpCallInterface
{
int testInt
{
get;
set;
}
bool testBool
{
get;
set;
}
string testString
{
get;
set;
}
UnityAction testFun
{
get;
set;
}
float testFloat222
{
get;
set;
}
}
(接口是引用拷贝而不是值拷贝)
Lua Table 映射 table
因为效率低,所以不建议使用LuaTable和LuaFunction
cs
LuaTable table = LuaMgr.GetInstance().Global.Get<LuaTable>("testClas");
Debug.Log(table.Get<int>("testInt"));
Debug.Log(table.Get<bool>("testBool"));
Debug.Log(table.Get<float>("testFloat"));
Debug.Log(table.Get<string>("testString"));
table.Get<LuaFunction>("testFun").Call();
table.Set("testInt", 666);
Debug.Log(table.Get<int>("testInt"));
LuaTable table2 = LuaMgr.GetInstance().Global.Get<LuaTable>("testClas");
Debug.Log(table2.Get<int>("testInt"));
table.Dispose();
table2.Dispose();
(是引用拷贝,用完后记得销毁,不定时清空的话会产生内存泄露)
Lua使用C#类
Lua没有办法直接访问c#,一定是先从C#调用Lua脚本后,才把核心逻辑交给Lua编写
lua中使用C#的类:
CS.命名空间.类名
Unity的类 比如 GameObject Transform等等 ------ CS.UnityEngine.类名
CS.UnityEngine.GameObject
Lua
--通过C#中的类 实例化一个对象 lua中没有new 所以直接 类名括号就是实例化对象
--默认调用的 相当于就是无参构造
local obj1 = CS.UnityEngine.GameObject()
local obj2 = CS.UnityEngine.GameObject("HFUT")
为了方便使用 并且节约性能 可以定义全局变量存储 C#中的类
Lua
GameObject = CS.UnityEngine.GameObject
local obj3 = GameObject("HFUTER")
类中的静态对象 可以直接使用.来调用
得到对象中的成员变量 直接对象 . 即可
如果使用对象中的 成员方法!!!!一定要加:
自定义类使用方法相同,只是命名空间不同而已
继承了Mono的类 是不能直接new ,其依附于gameobject
可以通过GameObject的AddComponent添加脚本,由于xlua中不支持 无参泛型函数 所以要使用另一个重载
Lua
--xlua提供了一个重要方法 typeof 可以得到类的Type
obj5:AddComponent(typeof(CS.LuaCallCSharp))
调用枚举
CS.命名空间.枚举名.枚举成员,也支持取别名
Lua
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject
自定义枚举使用方法一样,注意命名空间即可
Lua
E_MyEnum = CS.E_MyEnum
--枚举转换相关
local c = E_MyEnum.Idle
--数值转枚举
local a = E_MyEnum.__CastFrom(1)
--字符串转枚举
local b = E_MyEnum.__CastFrom("Atk")
调用数组/List/字典
Lua
--C#怎么用 lua就怎么用 不能使用#去获取长度
print(obj.array.Length)
遍历要注意 虽然lua中索引从1开始,但是数组是C#那边的规则 所以 还是得按C#的来
Lua
--访问元素
print(obj.array[0])
for i=0,obj.array.Length-1 do
print(obj.array[i])
end
Lua中创建一个C#的数组 Lua中表示数组和List可以用表;如果创建C#中的数组 使用 Array类中的静态方法即可(数组的本质是一个Array类)
Lua
local array2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32), 10)
Lua调用C# list
Lua
obj.list:Add(1)
obj.list:Add(2)
obj.list:Add(3)
--长度
print(obj.list.Count)
--遍历
for i=0,obj.list.Count - 1 do
print(obj.list[i])
end
print(obj.list)
在Lua中创建一个List对象
Lua
//老版本
local list2 = CS.System.Collections.Generic["List`1[System.String]"]()
//新版本
local List_String = CS.System.Collections.Generic.List(CS.System.String)
local list3 = List_String()
Lua调用C# dictionary相关知识点
使用和C#一致,但遍历区别很大
Lua
for k,v in pairs(obj.dic) do
print(k,v)
end
在Lua中创建一个字典对象
Lua
local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String, CS.UnityEngine.Vector3)
local dic2 = Dic_String_Vector3()
(第一步相当于得到了一个 Dictionary<string, Vector3> 的一个类别名 需要再实例化)
在Lua中创建的字典,如果直接通过键中括号得是得不到的,是nil
Lua
--错误写法
print(dic2["123"])
--正确写法
print(dic2:TryGetValue("123"))
--如果要通过键获取值 要通过这个固定方法,输出和上述不同
print(dic2:get_Item("123"))
dic2:set_Item("123", nil)
print(dic2:get_Item("123"))
调用拓展方法
定义拓展方法
cs
public class Lesson{
public string name="HFUT";
public void Speak(string str){Debug.Log(str);}
}
public static class Tools{
public static void Move(this Lesson obj){
}
}
使用静态方法:CS.命名空间.类名.静态方法名()
成员方法:实例化出来用,使用成员方法一定用冒号
Lua
local obj = Lesson4()
obj:Speak("Unity")
--使用拓展方法 和使用成员方法 一致
obj:Move()
发生报错是因为,要调用 C#中某个类的拓展方法 那一定要在拓展方法的静态类前面加上LuaCallCSharp特性,建议在Lua中要使用的类都加上该特性,可以提升性能(反射机制调用会降低效率)
ref 和 out
ref参数:会以多返回值的形式返回给lua,如果函数存在返回值,那么第一个值就是该返回值,之后的返回值就是ref的结果从左到右一一对应(ref参数 需要传入一个默认值来占位置)
Lua
--a 相当于 函数返回值
--b 第一个ref
--c 第二个ref
local a,b,c = obj:RefFun(1, 0, 0, 1)
out参数:会以多返回值的形式返回给lua,如果函数存在返回值,那么第一个值,就是该返回值(之后的返回值就是out的结果,其他从左到右一一对应)
Lua
--out参数 不需要传占位置的值
local a,b,c = obj:OutFun(20,30)
混合使用时 ,综合上面的规则,ref需占位/out不用传
第一个是函数的返回值,之后从左到右依次对应ref或者out
Lua
local a,b,c = obj:RefOutFun(20,1)
调用重载函数
虽然Lua自己不支持写重载函数,但是Lua支持调用C#中的重载函数
虽然支持调用C#重载函数但是因为Lua中的数值类型只有Number,所以对C#中多精度的重载函数支持不好,傻傻分不清。因此在使用时 可能出现意想不到的问题
为了解决重载函数含糊的问题,xlua提供了解决方案---反射机制,但是这种方法只做了解尽量别用
利用Type,Type是反射的关键类,可以得到指定函数的相关信息(Float的类名交Single)
Lua
local m1 = typeof(CS.Lesson6):GetMethod("Calc", {typeof(CS.System.Int32)})
local m2 = typeof(CS.Lesson6):GetMethod("Calc", {typeof(CS.System.Single)})
--通过xlua提供的一个方法 把它转成lua函数来使用
--一般我们转一次 然后重复使用
local f1 = xlua.tofunction(m1)
local f2 = xlua.tofunction(m2)
--成员方法 第一个参数传对象
--静态方法 不用传对象
如果是成员方法------第一个参数传对象;如果是静态方法------不用传对象
Lua
print(f1(obj, 10))
print(f2(obj, 10.2))
委托和事件
委托是用来装函数的,使用C#中的委托用来装lua函数
Lua
local fun = function( )
print("Lua函数Fun")
end
Lua
--如果第一次往委托中加函数 因为是nil 不能直接+
--所以第一次 要先等=
obj.del = fun
--obj.del = obj.del + fun
obj.del = obj.del + fun
还可以这么写,不过不建议
Lua
obj.del = obj.del + function( )
print("HFUT")
end
其他操作
Lua
obj.del = obj.del - fun
--委托执行
obj.del()
--清空所有存储的函数
obj.del = nil
事件:
Lua
local fun2 = function()
print("事件加的函数")
end
obj:eventAction("+", fun2)
obj:DoEvent()
obj:eventAction("-", fun2)
事件不能直接置空,需要调用c#中包裹的清空函数
二维数组
Lua
for i=0,obj.array:GetLength(0)-1 do
for j=0,obj.array:GetLength(1)-1 do
print(obj.array:GetValue(i,j))
end
end
注意,不能通过[0,0]或者[0][0]访问元素,lua不支持这种索引方式
null 和 nil
nil和null 没法进行==比较
可以采用平替方式:
-
if rig:Equals(nil) then
-
在c#和lua主脚本写一个判空函数
协程
C#中协程启动都是通过继承了Mono的类 通过里面的启动函数StartCoroutine
可以在场景中新建一个空物体 然后挂一个脚本上去 脚本继承mono使用它来开启协程
Lua
fun = function()
local a = 1
while true do
--lua中 不能直接使用 C#中的 yield return
--就使用lua中的协程返回
coroutine.yield(WaitForSeconds(1))
print(a)
a = a + 1
if a > 10 then
--停止协程和C#当中一样
mono:StopCoroutine(b)
end
end
end
不能直接将 lua函数传入到开启协程中,要利用xlua提供的一个工具表
Lua
--一定是要通过require调用之后 才能用
util = require("xlua.util")
如果要把lua函数当做协程函数传入,必须先调用xlua.util中的cs_generator(lua函数)
Lua
b = mono:StartCoroutine(util.cs_generator(fun))
调用泛型函数方法
lua中不支持没有约束的泛型函数,也不支持有约束但是没有参数的泛型函数,还不支持非class的约束(用接口约束的)
如何让上面不支持使用的泛型函数变得能用?
答:得到通用函数,设置泛型类型再使用。
xlua.get_generic_method(类, "函数名")
Lua
local testFun2 = xlua.get_generic_method(CS.Lesson12, "TestFun2")
local testFun2_R = testFun2(CS.System.Int32)
--调用
--成员方法 第一个参数 传调用函数的对象
--静态方法 不用传
testFun2_R(obj, 1)
但是有一定的使用限制
Mono打包:这种方式支持使用
il2cpp打包 :如果泛型参数是引用类型才可以使用;如果泛型参数是值类型,除非C#那边已经调用过了,同类型的泛型参数 lua中才能够被使用
热补丁
lua当中热补丁代码固定写法:xlua.hotfix(类, "函数名", lua函数)
cs
--成员函数 第一个参数 self
xlua.hotfix(CS.HotfixMain, "Add", function(self, a, b)
return a + b
end)
--静态函数 不用传第一个参数
xlua.hotfix(CS.HotfixMain, "Speak", function(a)
print(a)
end)
注意:直接写好代码运 是会报错的,必须做4个非常重要的操作
1.加特性
2.加宏 (第一次开发热补丁需要加)Project Settings->Player->Scripting Define Symbols
3.生成代码
4.hotfix 注入 (注入时可能报错,提示要引入Tools)
只要我们修改了热补丁类的代码,就需要重新执行hotfix 注入(需要重新点击 注入)
多函数替换
xlua.hotfix(类, {函数名 = 函数, 函数名 = 函数....})
Lua
xlua.hotfix(CS.HotfixMain, {
Update = function(self)
print(os.time())
end,
Add = function(self, a, b )
return a + b
end,
Speak = function(a)
print(a)
end
})
构造函数-热补丁固定写法 [".ctor"]
和别的函数不同,构造函数不是替换,而是先调用原逻辑,再调用lua逻辑
析构函数固定写法 Finalize
Lua
xlua.hotfix(CS.HotfixTest, {
[".ctor"] = function()
end,
Speak = function(self,a)
end,
--析构函数
Finalize = function()
end
})
协程函数替换
要在lua中配合C#协程函数,必须使用
Lua
util = require("xlua.util")
如果是为打了Hotfix特性的C#类新加了函数内容,不能只注入 ,必须要先生成代码再注入,不然注入会报错
Lua
xlua.hotfix(CS.HotfixMain, {
TestCoroutine = function(self)
--返回一个正儿八经的 xlua处理过的lua协程函数
return util.cs_generator(function()
while true do
coroutine.yield(CS.UnityEngine.WaitForSeconds(1))
print("Lua打补丁后的协程函数")
end
end)
end
})
属性和索引器替换
属性进行热补丁重定向:
set_属性名 是设置属性 的方法
get_属性名 是得到属性 的方法
Lua
xlua.hotfix(CS.HotfixMain, {
set_Age = function(self, v)
print("Lua重定向的属性"..v)
end,
get_Age = function(self)
return 10;
end
})
索引器进行热补丁重定向:
set_Item 通说索引器设置
get_Item 通过索引器获取
Lua
xlua.hotfix(CS.HotfixMain, {
set_Age = function(self, v)
print("Lua重定向的属性"..v)
end,
get_Age = function(self)
return 101;
end,
set_Item = function(self, index, v )
end,
get_Item = function(self, index)
return 666
end
})
事件的替换
add_事件名 代表着时间加操作
remove_事件名 减操作
Lua
xlua.hotfix(CS.HotfixMain, {
add_myEvent = function(self, del)
print(del)
end,
remove_myEvent = function(self, del )
print(del)
end
})
注意:重定向后,如果使用lua使用C#事件的方法去添加,在事件加减的重定向lua函数中,不要把传入的委托往事件里存,会死循环(会把传入的函数存在lua中)-> self:myEvent("+", del)
泛型类的替换
泛型类中的T是可以变化,但是lua中的替换 要一个类型一个类型的来
Lua
xlua.hotfix(CS.HotfixTest2(CS.System.String), {
Test = function(self, str)
end
})
xlua.hotfix(CS.HotfixTest2(CS.System.Int32), {
Test = function(self, str)
end
})
别忘了成员方法第一个参数是self