xlua

Lua解析器

Lua解析器能够让我们在Unity中执行Lua

默认的Lua脚本放在Resources文件夹下,并且要加上.txt后缀 才能被执行

1
2
3
4
LuaEnv env = new LuaEnv();
env.DoString("print('你好世界')");
//执行一个lua脚本
env.DoString("require('Main')");

垃圾回收

帮助我们清楚Lua中我们没有手动释放的对象 垃圾回收

帧更新中定时执行 或者 切场景时执行

1
env.Tick();

销毁Lua解析器

1
env.Dispose();

Lua文件加载重定向

xlua提供的一个 路径重定向 的方法

允许我们自定义 加载 Lua文件的规则

当我们执行Lua语言 require时 相当于执行一个lua脚本

它就会 执行 我们自定义传入的这个函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
LuaEnv env = new LuaEnv();
env.AddLoader(MyCustomLoader);
env.DoString("require('Main')");
private byte[] MyCustomLoader(ref string filePath)
{
    //通过函数中的逻辑 去加载 Lua文件 
    //传入的参数 是 require执行的lua脚本文件名
    //拼接一个Lua文件所在路径
    string path = Application.dataPath + "/Lua/" + filePath + ".lua";
    if ( File.Exists(path) )
    {
        return File.ReadAllBytes(path);
    }
    return null;
}

C#调用Lua

获取全局变量

获取大G表

1
2
LuaEnv env = new LuaEnv();
LuaTable global = env.Global;

获取值

1
2
3
4
5
global.Get<int>("number");
global.Get<bool>("sex");
global.Get<float>("testFloat");
global.Get<double>("testDouble");
global.Get<string>("testString");

设置值

1
global.Set("number",12);

获取全局函数

无参无返回的获取

1
2
3
4
5
6
7
8
9
//通过自定义委托获取
public delegate void CustomCall();
CustomCall call = global.Get<CustomCall>("testFun");
//Unity自带委托
UnityAction ua = global.Get<UnityAction>("testFun");
//C#提供的委托
Action ac = global.Get<Action>("testFun");
//Xlua提供的一种 获取函数的方式 少用
LuaFunction lf = global.Get<LuaFunction>("testFun");

有参有返回获取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//通过自定义委托获取
//有参有返回 的委托
//该特性是在XLua命名空间中的
//加了过后 要在编辑器里 生成 Lua代码
[CSharpCallLua]
public delegate int CustomCall2(int a);
CustomCall2 call2 = global.Get<CustomCall2>("testFun2");
//C#自带的泛型委托 方便我们使用
Func<int, int> sFun = global.Get<Func<int, int>>("testFun2");
//Xlua提供的
LuaFunction lf2 = global.Get<LuaFunction>("testFun2");

多返回值获取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//自定义委托 参数中添加out关键字的方式接收
[CSharpCallLua]
public delegate int CustomCall3(int a, out int b, out bool c, out string d, out int e);
CustomCall3 call3 = 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);
//自定义委托 参数中添加ref关键字的方式接收
[CSharpCallLua]
public delegate int CustomCall4(int a, ref int b, ref bool c, ref string d, ref int e);
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 = global.Get<LuaFunction>("testFun3");
//返回值是一个Object数组
object[] objs = lf3.Call(1000);
for( int i = 0; i < objs.Length; ++i )
{
    Debug.Log("第" + i + "个返回值是:" + objs[i]);
}

变长参数

1
2
3
4
5
6
7
8
//自定义委托
[CSharpCallLua]
public delegate void CustomCall5(string a, params int[] args);//变长参数的类型 是根据实际情况来定的
CustomCall5 call5 = global.Get<CustomCall5>("testFun4");
call5("123", 1, 2, 3, 4, 5, 566, 7, 7, 8, 9, 99);
//xlua自带
LuaFunction lf4 = global.Get<LuaFunction>("testFun4");
lf4.Call("456", 6, 7, 8, 99, 1);

List和Dictionary

获取List

1
2
3
4
//指定类型
List<int> list = global.Get<List<int>>("testList");
//不指定类型
List<object> list3 = global.Get<List<object>>("testList2");

获取 Dictionary

1
2
3
4
//指定类型
Dictionary<string, int> dic2 = global.Get<Dictionary<string, int>>("testDic");
//不指定类型
Dictionary<object, object> dic3 = global.Get<Dictionary<object, object>>("testDic2");

类映射Table

  • 类中去声明成员变量名字一定要和 Lua那边的一样

  • 必须是公共的 私有和保护 没办法赋值

  • 这个自定义中的 变量 可以更多也可以更少

    • 如果变量比 lua中的少 就会忽略它
    • 如果变量比 lua中的多 不会赋值 也会忽略
1
2
3
4
5
6
7
public class CallLuaClass
{
    public int testInt;
    public bool testBool;
    public UnityAction testFun;
}
CallLuaClass obj = global.Get<CallLuaClass>("testClass");

接口映射Table

  • 接口中是不允许有成员变量的
  • 我们用属性来接受
  • 接口和类规则一样 其中的属性多了少了 不影响结果 无非就是忽略他们
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[CSharpCallLua]
public interface ICallLuaClass
{
    int testInt
    {
        get;
        set;
    }
    bool testBool
    {
        get;
        set;
    }
    UnityAction testFun
    {
        get;
        set;
    }
}
ICallLuaClass obj = global.Get<ICallLuaClass>("testClass");

注意:接口拷贝 是引用拷贝 改了值 lua表中的值也变了

LuaTable映射Table

不建议使用LuaTable和LuaFunction 效率低

1
2
3
LuaTable table = global.Get<LuaTable>("testClass");
table.Get<int>("testInt")
tables.Set("TestInt",20)

Lua调用C

使用C#的类

lua中使用C#的类非常简单

固定套路

CS.命名空间.类名

Unity的类 比如 GameObject Transform等等 —— CS.UnityEngine.类名

CS.UnityEngine.GameObject

  • 通过C#中的类 实例化一个对象 lua中没有new 所以我们直接 类名括号就是实例化对象

默认调用的 相当于就是无参构造

1
local obj1 = CS.UnityEngine.GameObject()

为了方便使用 并且节约性能 可以定义全局变量存储 C#中的类

相当于取了一个别名

1
2
GameObject = CS.UnityEngine.GameObject
local obj3 = GameObject()

类中的静态对象 可以直接使用.来调用

1
local obj4 = GameObject.Find("GameObject")

得到对象中的成员变量 直接对象 . 即可

1
2
3
obj4.transform.position
Debug = CS.UnityEngine.Debug
Debug.Log(obj4.transform.position)

自定义类 使用方法 相同 只是命名空间不同而已

1
2
3
local t = CS.Test()
-- 成员方法 使用:调用
t:Speak("test说话")

继承了Mono的类 是不能直接new的

xlua提供了一个重要方法 typeof 可以得到类的Type

xlua中不支持 无参泛型函数 所以 我们要使用另一个重载

1
2
local obj5 = GameObject("加脚本测试")
obj5:AddComponent(typeof(CS.LuaCallCSharp))

使用C#枚举

枚举的调用规则 和 类的调用规则是一样的

CS.命名空间.枚举名.枚举成员

也支持取别名

1
2
3
4
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject
-- 创建一个 立方体
local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)

枚举转换

1
2
3
4
--数值转枚举
local a = E_MyEnum.__CastFrom(1)
--字符串转枚举
local b = E_MyEnum.__CastFrom("Atk")

扩展方法

Lua可以使用C#的扩展方法

但是需要在扩展方法对应的静态类加[LuaCallCsharp]特性 然后生成代码

注意:该特性可以提升Lua访问C\#类的性能,所以建议只要Lua中使用的C\#类都加上该特性

C#类实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[LuaCallCsharp]
public static class TestExpand
{
    public static void TestFun(this Test test)
    {
        Debug.log(test.name);
    }
} 
public class Test
{
    public string name; 
}

lua实现:

1
2
local Test = CS.Test()
test:TestFun() -- 使用扩展方法 和使用成员方法一致

使用ref和out函数

ref

ref参数 会以多返回值的形式返回给lua

如果函数存在返回值 那么第一个值 就是该返回值

之后的返回值 就是ref的结果 从左到右一一对应

1
2
3
4
5
--ref参数 需要传入一个默认值 占位置
local a,b,c = obj:RefFun(1, 0, 0, 1)
print(a) --函数返回值
print(b) --第一个ref
print(c) --第二个ref
1
2
3
4
5
6
public int ReFun(int a, ref int b, ref int c,int d)
{
    b = a + d;
    c = a - d;
    return 100;
}

out

out参数 会以多返回值的形式返回给lua

如果函数存在返回值 那么第一个值 就是该返回值

之后的返回值 就是out的结果 从左到右一一对应

out参数 不需要传占位置的值

1
2
3
4
local a,b,c = obj:OutFun(20,30)
print(a)--函数返回值
print(b)--第一个out
print(c)--第二个out
1
2
3
4
5
6
public int OutFun(int a, int b, out int c, out int d)
{
    b = a;
    c = d;
    return 200;
}

混合使用

混合使用时 综合上面的规则

ref需占位 out不用传

第一个是函数的返回值 之后 从左到右依次对应ref或者out

1
2
3
4
<span>local</span> a,b,c = obj:RefOutFun(<span>20</span>,<span>1</span>)
<span>print</span>(a)<span>--300</span>
<span>print</span>(b)<span>--200</span>
<span>print</span>(c)<span>--400</span>
1
2
3
4
5
6
public int RefOutFun(int a, int b, out int c, ref int d)
{
    b = a * 10;
    c = a * 20;
    return 300;
}

使用重载函数

虽然Lua自己不支持写重载函数

但是Lua支持调用C#中的重载函数

1
2
3
local obj = CS.Test()
print(obj:Calc())
print(obj:Calc(15, 1))

Lua虽然支持调用C#重载函数

但是因为Lua中的数值类型 只有Number

对C#中多精度的重载函数支持不好

在使用时 可能出现意想不到的问题

例如:

1
2
3
4
5
6
7
8
public void Calc(int a)
{
    Debug.log(a);
}
public void Calc(float a)
{
    Debug.log(a);
}

解决重载函数含糊的问题

xlua提供了解决方案 反射机制

这种方法只做了解 尽量不要使用

Type是反射的关键类

得到指定函数的相关信息

1
2
local m1 = typeof(CS.Test):GetMethod("Calc", {typeof(CS.System.Int32)})
local m2 = typeof(CS.Test):GetMethod("Calc", {typeof(CS.System.Single)})

通过xlua提供的一个方法 把它转成lua函数来使用

一般我们转一次 然后重复使用

1
2
3
4
5
6
local f1 = xlua.tofunction(m1)
local f2 = xlua.tofunction(m2)
--成员方法 第一个参数传对象
--静态方法 不用传对象
print(f1(obj, 10))
print(f2(obj, 10.2))

委托和事件

委托是用来装函数的

使用C#中的委托 就是用来装lua函数的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//声明委托和事件
public UnityAcion del;
public event UntiyAction eventAction;
public void DoEvent()
{
    eventAction();
}
public void ClearEvent()
{
    eventAction = null;
}

委托添加函数

Lua中没有复合运算符 不能+=

如果第一次往委托中加函数 因为是nil 不能直接+

所以第一次 要先等=

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
local fun = function()
    print("Lua函数Fun")
end
obj.del = fun
-- 添加函数
obj.del = obj.del + fun
-- 添加匿名函数
--不建议这样写 最好最好还是 先声明函数再加
obj.del = obj.del + function( )
    print("临时申明的函数")
end

委托执行

1
obj.del()

委托减函数

1
obj.del = obj.del - fun

清空所有存储的函数

1
obj.del = nil

事件加函数

事件加减函数 和 委托非常不一样

lua中使用C#事件 加函数

有点类似使用成员方 冒号事件名("+", 函数变量)

1
2
3
4
5
obj:eventAction("+", fun2)
-- 添加匿名函数 最好不要这样写
obj:eventAction("+", function()
    print("事件加的匿名函数")
end)

事件减函数

1
obj:eventAction("-", fun2)

事件执行

lua中不可以直接调用事件 需要在C#中实现对应的方法调用

事件清空

lua中不可以直接清空事件 可以在C#中实现对应的方法调用

二维数组

1
public int[,] array = new int[2,3]{{1,2,3},{4,5,6}};

lua中调用

1
2
3
local obj = CS.Test()
-- 不可以通过[0][0]来调用
obj.array:GetValue(0,0)

携程

C# 中携程启动都是通过继承了Mono的类 通过里面的启动函数 StartCoroutine

开启和关闭携程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UntiyEngine.WaitForSeconds
-- Xlua提供了一个工具表
-- 里面有很多方法供我们使用
util = require("xlua.util")
-- 在场景中新建一个空物体 然后挂一个脚本上去
local obj = GameObject("携程")
local mono = obj:AddComponent(typeof(CS.Test))
-- 希望被开启的携程函数
fun = function()
    local a = 1
    while true do
        coroutine.yield(WaitForSecond(1))
        print(a)
        a = a+1
        if a > 10 
            --停止携程和C#中一样
            mono:StopCoroutine(b)
    end
end
-- 不可以直接将lua函数传入到携程开启的方法中
-- mono:StartCoroution(fun)
-- 需要使用xlua.util中的cs_generator(lua函数)
b = mono:StartCoroution(util.cs_generator(fun))

调用泛型方法

  • lua支持 有约束 有参数 并且参数是泛型的泛型函数
  • lua中不支持 没有约束的泛型函数
  • lua中不支持有约束 但是没有参数的泛型函数
  • lua中不支持 非class的约束

xlua提供了 让上面不支持使用的泛型函数 变得能用

得到通用函数 设置泛型类型 再使用

xlua.get_generic_method(类,函数名)

注意:

有一定的限制

  • Mono打包 这种方式支持
  • il2cpp打包 泛型参数是引用类型 才可以使用
  • il2cpp打包 如果泛型参数是值类型 除非C#那边已经调用过了 同类型的泛型参数 lua中才可以被使用

C#类:

1
2
3
4
5
6
7
8
9
public class Test
{
    //lua可以直接调用
    public void TestFun1<T>(T a) where T:GameObject {Debug.Log("有参数有约束")}
    //lua不可以直接调用的
    public void TestFun2<T>(T a){Debug.Log("有参数 没有约束")}
    public void TestFun3<T>() where T:GameObject {Debug.Log("无参数 有约束")}
    public void TestFun4<T>(T a) where T:IList {Debug.Log("有参数和约束 但不是类")}
}

lua类:

1
2
3
4
local obj = CS.Test()
local testFun2 = xlua.get_generic_method(CS.Test,"TestFun2")
local testFun2_R = testFun(CS.System.Int32) --指定泛型类型
testFun2_R(obj,2) -- 第一个参数传调用函数的对象 第二个参数传入参数

特殊问题

lua中的nil和C#null 不是相等的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
GameObject = CS.UnityEngine.GameObject
Rigidbody = CS.UnityEngine.Rigibody

local obj = GameObject("Test")
local rig = obj:GatComponent(typeof(Rigidbody))
print(rig) --null

if rig == nil then
    rig = obj:AddComponent(typeof(Rigidbody))
end
print(rig) --null

if rig:Equals(nil) then  -- 调用Object中的方法Equals进行比较 前提是对象是一个Object
    rig = obj:AddComponent(typeof(Rigidbody))
end

保险的判断方式

1
2
if rig == nil or rig:Equals(nil) then
end

让系统类型和Lua互相访问

在lua中为Slider添加事件 会报错

因为 AddListener中的参数类型为UnityAction

UnityAction是系统提供的委托 我们无法直接为系统类添加[CSharpCallLua]特性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GameObject = CS.UnityEngine.GameObject
UI = CS.UnityEngine.UI

local slider = GameObject.Find("Slider")
print(slider)
local sliderScript = slider:GetComponent(typeof(UI.Slider))
print(sliderScript)
sliderScript.onValueChanged:AddListener(function(f)
    print(f)
end)

为系统类型添加特性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static class Test
{
    [CSharpCallLua]
    public static List<Type> csharpCallLuaList = new List<Type>()
    {
        typeof(UnityAction<float>)
    };
    [LuaCallCSharp]
    public static List<Type> LuaCallCSharpList = new List<Type>()
    {
        typeof(GameObject),typeof(Rigidbody)
    };
}

注意:一定要是静态类 和静态List 并且要重新生成代码

热补丁

  1. 在C#脚本中添加特性[Hotfix]

  2. 添加宏 第一次开发热补丁时需要加
    Project Settings->Player->Scritping Define Symbols->添加HOTFIX_ENABLE

  3. 生成代码

  4. hotfix 注入 注入时可能报错 提示要引入tools

    将Tools文件夹 拷贝到 Asset文件夹同级的目录下(工程文件夹根目录)

热补丁的缺点:只要我们修改了热补丁类的代码,我们就需要重新执行第4步

替换函数

lua当中 热补丁代码固定写法

1
xlua.hotfix(, "函数名", lua函数)--如果是成员函数 在参数中添加self

替换构造函数

构造函数和别的函数不同 不是替换原有逻辑 而是先调用原有逻辑 在调用lua逻辑

1
2
3
4
5
6
7
8
9
xlua.hotfix(CS.HotfixTest,{-- 替换多个函数 通过表
        [".ctor"] = function()
            print("lua热补丁构造函数")
        end,
        -- 析构函数
        Finalize = function()
            print("lua热补丁析构函数")
        end
})

替换携程函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 如果要使用携程函数 就要使用Xlua提供的工具表
util = require("xlua.util")
xlua.hotfix(CS.HotfinxTest,{
    TestCoroution = function(self) --携程函数也算是成员属性
        return util.cs_generator(function()
            while true do
                coroutine.yield(CS.UnityEngine.WaitForSeconds(1)) --等1秒
                print("lua替换c#的携程函数")
            end
        end)
    end
})

替换成员属性

如果是属性进行热补丁重定向

  • set_属性名 设置属性的方法
  • get_属性名 得到属性的方法
1
2
3
4
5
6
7
8
9
xlua.hotfix(CS.HotfinxTest,
{
    set_Age = function(self,v)
        print("lua重定向的属性"..v)
    end,
    get_Age = function(self)
        return 10
    end
})

替换索引器

  • set_Item 索引器设置
  • get_Item 索引器获取
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
xlua.hotfix(CS.HotfinxTest,
{
    set_Item = function(self,index,v)
        print("设置索引器索引:"..index.."值"..v)
    end,
    get_Item = function(self,index,v)
        print("获取索引器索引:"..index.."值"..v)
        return 99
    end
})

替换事件操作

一般不会使用

  • add_事件名 代表事件添加操作
  • remove_事件名 代表事件减操作
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
xlua.hotfix(CS.HotfinxTest,
{
    add_myEvent = function(self,del)
        print(del)
        print("添加事件函数")
        -- 不要把传入的函数往委托事件里存 不然会死循环
        -- self:myEvent("+",del)
        -- 要把传入的函数存入lua中
    end,
    remove_myEvent = function(self,del)
        print(del)
        print("减事件函数")
    end
})

替换泛型类

lua中替换泛型类 要一个类型一个类型的来

1
2
3
4
5
6
7
8
[Hotfix]
public class HotfixTest2<T>
{
    public void Test(T str)
    {
        Debug.Log(str);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
xlua.hotfix(CS.HotfixTest(CS.System.String),
    {
        Test = function(self, str)
            print("lua中打的补丁"..str)
        end
    })
xlua.hotfix(CS.HotfixTest(CS.System.Int32),
    {
        Test = function(self, str)
            print("lua中打的补丁"..str)
        end
    })