【Lua热更新知识】学习三 XLua学习

发布于:2025-06-12 ⋅ 阅读:(25) ⋅ 点赞:(0)

1、XLua介绍

        XLua 是一个为 Unity 游戏引擎设计的 Lua 脚本编程解决方案,由腾讯公司开源并维护。它主要解决了在 Unity 项目中使用 Lua 进行热更新的需求。

2、C#调用Lua,CSharpCallLua

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

LuaEnv

        //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();

2.2  路径重定向,自定义加载Lua文件的规则

    void Start()
    {
        LuaEnv env = new LuaEnv();

        //xlua提供的一个 路径重定向 的方法
        //允许我们自定义 加载Lua文件的规则
        //当我们执行Lua语言 require 时 相当于执行一个脚本
        //它就会 执行 我们自定义传入的这个函数
        env.AddLoader(MyCustomLoader);
        //最终我们其实 会去AB包中加载 lua文件

        env.DoString("require'Main'");
        env.DoString("require'ttt'");
    }

    //自动执行
    private byte[] MyCustomLoader(ref string filePath)
    {
        //传入的参数是 require执行的lua脚本文件名
        //拼接一个Lua文件所在路径
        string path = $"{Application.dataPath}/Lua/{filePath}.lua";
        Debug.Log(path);

        //有路径 就去加载文件
        //File知识点 C#提供的文件读写的类
        //判断文件是否存在
        if(File.Exists(path))
        {
            return File.ReadAllBytes(path);
        }
        else
        {
            Debug.LogError("MyCustomLoader重定向失败,文件名为:" + filePath);
        }

        //通过函数中的逻辑 去加载 Lua文件
        return null;
    }

2.3 Lua管理器

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;

/// <summary>
/// Lua管理器
/// 提供 lua解析器
/// 保证解析器的唯一性
/// </summary>
public class LuaMgr : BaseMananger<LuaMgr>
{
    //执行lua语言的函数
    //释放垃圾
    //销毁
    //重定向
    private LuaEnv luaEnv;

    /// <summary>
    /// 得到Lua中的 _G
    /// </summary>
    public LuaTable Global
    {
        get
        {
            return luaEnv.Global;
        }
    }

    /// <summary>
    /// 初始化解析器
    /// </summary>
    public void Init()
    {
        //已经初始化了 不用初始化 直接返回
        if (luaEnv != null)
            return;

        //初始化
        luaEnv = new LuaEnv();
        //加载lua脚本重定向
        luaEnv.AddLoader(MyCustomLoader);
        luaEnv.AddLoader(MyCustomABLoader);
    }

    //自动执行
    private byte[] MyCustomLoader(ref string filePath)
    {
        //传入的参数是 require执行的lua脚本文件名
        //拼接一个Lua文件所在路径
        string path = $"{Application.dataPath}/Lua/{filePath}.lua";
        //Debug.Log(path);

        //有路径 就去加载文件
        //File知识点 C#提供的文件读写的类
        //判断文件是否存在
        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);
        }
        else
        {
            Debug.Log("MyCustomLoader重定向失败,文件名为:" + filePath);
        }

        //通过函数中的逻辑 去加载 Lua文件
        return null;
    }

    //lua脚本会放到AB包中
    //最终我们会通过加载AB包再加载其中的Lua脚本资源 来执行
    //AB包中如果要加载文本 后缀还是有一定限制 .lua不能识别
    //打包时 要把lua文件后缀改为txt
    //重定向加载AB包中的lua
    private byte[] MyCustomABLoader(ref string filePath)
    {
        //Debug.Log("进入AB重定向加载函数");
        从AB包中加载文件
        加载AB包
        //string path = $"{Application.streamingAssetsPath}/lua";
        //AssetBundle ab = AssetBundle.LoadFromFile(path);

        加载Lua文件 返回
        //TextAsset txt = ab.LoadAsset<TextAsset>(filePath + ".lua");

        加载Lua文件 byte数组
        //return txt.bytes;

        //通过AB包管理器 加载的lua脚本资源
        TextAsset lua = null;
        try
        {
            lua = ABMgr.Instance.LoadRes<TextAsset>("lua", filePath + ".lua");
        }
        catch (System.Exception)
        {
            Debug.Log("MyCustomABLoader AB重定向函数加载失败,文件名为:" + filePath);
            return null;
        }
        if (lua != null)
            return lua.bytes;
        else
            Debug.Log("MyCustomABLoader AB重定向函数加载失败,文件名为:" + filePath);

        return null;
    }

    /// <summary>
    /// 传入lua文件名 执行lua脚本
    /// </summary>
    /// <param name="fileName"></param>
    public void DoLuaFile(string fileName)
    {
        string str = string.Format("require('{0}')", fileName);
        DoString(str);
    }

    /// <summary>
    /// 执行lua语言
    /// </summary>
    /// <param name="str"></param>
    public void DoString(string str)
    {
        if(luaEnv == null)
        {
            Debug.Log("解析器未初始化,请调用方法Init初始化");
            return;
        }
        luaEnv.DoString(str);
    }

    /// <summary>
    /// 释放lua 垃圾
    /// </summary>
    public void Tick()
    {
        if (luaEnv == null)
        {
            Debug.LogError("解析器未初始化,请调用方法Init初始化");
            return;
        }
        luaEnv.Tick();
    }

    /// <summary>
    /// 销毁解析器
    /// </summary>
    public void Dispose()
    {
        if (luaEnv == null)
        {
            Debug.LogError("解析器未初始化,请调用方法Init初始化");
            return;
        }
        luaEnv.Dispose();
        luaEnv = null;
    }

}

 2.4 获取Lua全局变量

  • LuaEnv中的Global变量的Get方法,获取指定名字的Lua全局变量
  • 值拷贝
  • 想要改变全局变量,使用Global中的Set方法
  • 根据Lua中具体数值,用对应的C#变量类型来存储

Lua代码

testNumber = 1
testBool = true
testFloat = 1.2
testString = "2131"

C#代码

        LuaMgr.Instance.Init();

        LuaMgr.Instance.DoLuaFile("Main");

        int local = LuaMgr.Instance.Global.Get<int>("testLocal");
        Debug.Log(local);

        //使用lua解析器luaEnv中 的属性 Glabal属性
        int i = LuaMgr.Instance.Global.Get<int>("testNumber");
        Debug.Log("testNumber:" + i);

        i = 10;
        LuaMgr.Instance.Global.Set("testNumber", 55);
        //值拷贝 不会影响原来Lua中的值
        int i2 = LuaMgr.Instance.Global.Get<int>("testNumber");
        Debug.Log("testNumber_i2:" + i2);

        bool b = LuaMgr.Instance.Global.Get<bool>("testBool");
        Debug.Log("testBool:" + b);

        float f = LuaMgr.Instance.Global.Get<float>("testFloat");
        Debug.Log("testFloat:" + f);

        double d = LuaMgr.Instance.Global.Get<double>("testFloat");
        Debug.Log("testFloat:" + d);

        string s = LuaMgr.Instance.Global.Get<string>("testString");
        Debug.Log("testString:" + s);

2.5 获取Lua全局函数

  • 无参无返回,自定义委托、Action、UnityAction、LuaFuntion
  • 有参有返回,自定义委托、Func、LuaFuntion
  • 多返回,自定义委托(配合Out或Ref)、LuaFuntion
  • 变长参数,自定义委托、LuaFuntion

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#代码

    void Start()
    {
        LuaMgr.Instance.Init();

        LuaMgr.Instance.DoLuaFile("Main");

        //无参无返回的获取
        //委托
        CustomCall call = LuaMgr.Instance.Global.Get<CustomCall>("testFun");
        call();
        //Unity自带委托
        UnityAction ua = LuaMgr.Instance.Global.Get<UnityAction>("testFun");
        ua();
        //C#提供的委托
        Action ac = LuaMgr.Instance.Global.Get<Action>("testFun");
        ac();
        //Xlua提供的一种 获取函数的方式 少用
        LuaFunction lf = LuaMgr.Instance.Global.Get<LuaFunction>("testFun");
        lf.Call();

        //有参又返回
        CustomCall2 call2 = LuaMgr.Instance.Global.Get<CustomCall2>("testFun2");
        int num = call2(2);
        Debug.Log("有参有返回a + 1:" + num);
        //C#自带的泛型委托 方便使用
        Func<int, int> sFun = LuaMgr.Instance.Global.Get <Func<int, int>>("testFun2");
        Debug.Log("有参有返回a + 1:" + sFun(3));
        //Xlua提供的
        LuaFunction lf2 = LuaMgr.Instance.Global.Get<LuaFunction>("testFun2");
        Debug.Log("有参有返回:" + lf2.Call(30)[0]);

        //多返回值
        //使用out 和 ref 来接受
        CustomCall3 call3 = LuaMgr.Instance.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={b},c={c},d={d},e={e}");

        CustomCall4 call4 = LuaMgr.Instance.Global.Get<CustomCall4>("testFun3");
        int b2 = 0;
        bool c2 = true;
        string d2 = "";
        int e2 = 0;
        Debug.Log("第一个参数:" + call4(100, ref b2, ref c2, ref d2, ref e2));
        Debug.Log($"b={b2},c={c2},d={d2},e={e2}");
        //Xlua提供的
        LuaFunction lf3 = LuaMgr.Instance.Global.Get<LuaFunction>("testFun3");
        object[] res = lf3.Call(101);
        int index = 1;
        foreach ( object o in res)
        {
            Debug.Log($"第{index}个参数是:{o}");
            index++;
        }

        //变长参数
        CustomCall5 call5 = LuaMgr.Instance.Global.Get<CustomCall5>("testFun4");
        call5("lalala", 1, 2, 3, 4, 5);

        LuaFunction lf4 = LuaMgr.Instance.Global.Get<LuaFunction>("testFun4");
        lf4.Call("str lalal", 1, 2, 3, 4, 5, 6343);

    }

 2.6 List和Dictionary映射table

  • List 一般用来映射没有自定义索引的表,确定类型指定类型即可,不确定类型用Object
  • Dictionary 一般用来映射有自定义索引的表,确定类型指定类型即可,不确定类型用Object

Lua代码

--List
testList = {1,2,3,4,5,6}
testList2 = {"123","456",true,1,2,3.4}


--Dictionary
testDic = {
	["k1"] = 1,
	["k2"] = 2,
	["k3"] = 3,
	["k4"] = 4,
	["k5"] = 5
}

testDic2 = {
	["1"] = 1,
	[true] = 2,
	[false] = true,
	["123"] = false
}

C#代码

        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");

        //同一类型List
        List<int> list = LuaMgr.Instance.Global.Get<List<int>>("testList");
        Debug.Log("*******List*********");
        for (int i = 0; i < list.Count; i++)
        {
            Debug.Log(list[i]);
        }
        list[0] = 100;

        List<int> list2 = LuaMgr.Instance.Global.Get<List<int>>("testList");
        Debug.Log("list2[0]");
        Debug.Log(list2[0]);

        List<object> list3 = LuaMgr.Instance.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.Instance.Global.Get<Dictionary<string, int>>("testDic");
        foreach (string item in dic.Keys)
        {
            Debug.Log($"key={item}_value={dic[item]}");
        }

        Debug.Log("*******Dictionary Object*********");
        Dictionary<object, object> dic2 = LuaMgr.Instance.Global.Get<Dictionary<object, object>>("testDic2");
        foreach (object item in dic2.Keys)
        {
            Debug.Log($"key={item}_value={dic2[item]}");
        }

2.7 类映射table

  • 如果要将Lua中的表映射到C#中的类,在C#中声明一个自定义类,其中的成员变量命名要和Lua中表的自定义索引一致,可多可少,多或者少的会被忽略
  • 值拷贝,改变实例化对象的值,不会影响Lua中的表
  • 支持嵌套

Lua代码

--lua当中的一个自定义类
testClass = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("testFun1231313")
	end,

	inClass = {
		testInt = 100
	}
}

C#代码

public class CallLuaClass
{
    //在这个类中去声明成员变量
    //名字一定要和 Lua那边的一样
    //公共的 私有和保护没办法赋值
    //这个自定义中的 变量 可以更多也可以更少
    public int testInt;
    public bool testBool;
    public float testFloat;
    public string testString;

    public UnityAction testFun;

    public CallLuaInClass inClass;

    public int i;
    public void TestFun()
    {

    }

}

public class CallLuaInClass
{
    public int testInt;
}

    void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");

        CallLuaClass obj = LuaMgr.Instance.Global.Get<CallLuaClass>("testClass");
        Debug.Log(obj.testInt);
        Debug.Log(obj.testBool);
        Debug.Log(obj.testFloat);
        Debug.Log(obj.testString);
        obj.testFun();

        Debug.Log(obj.inClass.testInt);
    }

2.8 接口映射table

  • 如果要通关接口获取Lua表的内容,自定义一个接口,接口中的变量都有属性声明,多或少都会忽略
  • 接口前面一点要加特性,然后XLua生成代码,接口结构改变需要先清除在生成代码
  • 引用拷贝,改变了接口对象中的值,对应Lua表中的值也会改变

Lua代码

--lua当中的一个自定义类
testClass = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("testFun1231313")
	end,

	inClass = {
		testInt = 100
	}
}

C#代码

[CSharpCallLua]
public interface CSharpCallInterface
{
    int testInt { get; set; }
    bool testBool { get; set; }
    float testFloat { get; set; }
    string testString { get; set; }
    UnityAction testFun { get; set; }
}

    void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");

        CSharpCallInterface obj = LuaMgr.Instance.Global.Get<CSharpCallInterface>("testClass");

        Debug.Log(obj.testInt);
        Debug.Log(obj.testBool);
        Debug.Log(obj.testFloat);
        Debug.Log(obj.testString);
        obj.testFun();

        //接口拷贝 是引用拷贝 改了值 Lua表中的值也改变了
        obj.testInt = 1000;
        CSharpCallInterface obj2 = LuaMgr.Instance.Global.Get<CSharpCallInterface>("testClass");
        Debug.Log(obj2.testInt);
    }

2.9 LuaTable映射table

  • 通过LuaTable,XLua提供的类,获取表,得和改通过Get和Set
  • 引用设置
  • 用完要Dispose
  • 不建议使用

Lua代码

--lua当中的一个自定义类
testClass = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("testFun1231313")
	end,

	inClass = {
		testInt = 100
	}
}

C#代码

    void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");

        //不建议使用LuaTable和LuaFunction效率低
        //引用对象
        LuaTable table = LuaMgr.Instance.Global.Get<LuaTable>("testClass");
        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", 55);
        Debug.Log(table.Get<int>("testInt"));
        LuaTable table2 = LuaMgr.Instance.Global.Get<LuaTable>("testClass");
        Debug.Log(table2.Get<int>("testInt"));

        table.Dispose();
        table2.Dispose();

    }

3、Lua调用C#,LuaCallCSharp

3.1 Lua使用C#类

CS.命名空间.类名

--lua中使用C#的类非常简单
--固定套路
--CS.命名空间.类名
--Unity的类 比如 GameObject Transform等待 -- CS.UnityEngine.类名
--CS.UnityEngine.GameObject

--通过C#中的类 实例化一个对象 lua中没有new 所有我们直接 类名括号就是实例化对象
--默认调用的 相当于就是无参构造
local obj = CS.UnityEngine.GameObject()
local obj2 = CS.UnityEngine.GameObject("ZTT")

了方便使用 并且节约性能 定义全局变量存储 C#中的类,取了一个别名

--为了方便使用 并且节约性能 定义全局变量存储 C#中的类
--相当于取了一个别名
GameObject = CS.UnityEngine.GameObject
print(type(GameObject))

local obj3 = GameObject("你好啊")

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

--类中的静态对象 可以直接使用.来调用
local obj4 = GameObject.Find("ZTT")

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

--得到对象中的成员变量 直接对象 . 即可
print(obj4.transform.position)
Debug = CS.UnityEngine.Debug
Debug.Log(obj4.transform.position)

Vector3 = CS.UnityEngine.Vector3

 如果使用对象中的 成员方法!!! 一定要用:冒号调用

--如果使用对象中的 成员方法!!! 一定要加:
obj4.transform:Translate(Vector3.right)
Debug.Log(obj4.transform.position)

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

--自定义类 使用方法 相同 只是命名空间不同而已
local t = CS.Test()
t:Speak("test说话")

local t2 = CS.ZTT.Test2()
t2:Speak("test2说话")

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

--继承了Mono的类
--继承了Mono的类 是不能直接new
local obj5 = GameObject("加脚本测试")
--通过GameObject的 AddComponent添加脚本
--xlua提供了一个重要方法 typeof 可以得到类的Type
--xlua中不支持 无参泛型函数 所有 我们要使用另一个重载
obj5:AddComponent(typeof(CS.LuaCallCSharp))

3.2 Lua使用C#枚举

  • 枚举的调用规则 和 类的调用规则是一样的
  • CS.命名空间.枚举名.枚举成员
  • 也支持取别名

C#代码

/// <summary>
/// 自定义测试枚举
/// </summary>
public enum E_Myenum
{
    Idle,
    Move,
    Atk,
}

Lua代码

--枚举调用
--调用Unity当中的枚举
--枚举的调用规则 和 类的调用规则是一样的
--CS.命名空间.枚举名.枚举成员
--也支持取别名
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject

local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)

--自定义枚举 使用方法一样 只是注意命名空间即可
E_Myenum = CS.E_Myenum

local c = E_Myenum.Idle  
print(c)
--枚举转换相关
--数值转枚举
local a = E_Myenum.__CastFrom(1)
print(a)

--字符串转枚举
local b = E_Myenum.__CastFrom("Atk")
print(b)

3.3 Lua使用C# 数组 List和字典

  • lua使用C#数组相关知识
  • C#怎么用 lua就怎么用 不能使用#去获取长度
  • 遍历要注意 虽然lua中索引从1开始,是数组是C#那边的规则 所有 还是得按C#的来,注意最大值 一定要减1

C#代码

public class Lesson3
{
    public int[] array = new int[5] {1,2,3,4,5 };
    public List<int> list = new List<int>();
    public Dictionary<int,string> dic = new Dictionary<int,string>();
}

Lua代码

local obj = CS.Lesson3()

--lua使用C#数组相关知识
--长度 userdata
--C#怎么用 lua就怎么用 不能使用#去获取长度
print(obj.array.Length)

--访问元素
print(obj.array[0])

--遍历要注意 虽然lua中索引从1开始
--但是数组是C#那边的规则 所有 还是得按C#的来
--注意最大值 一定要减1
for i=0,obj.array.Length-1 do
	print(obj.array[i])
end

--lua中创建一个C#的数组 Lua中表示数组和List可以用表
--但是我要使用C#中???
--创建C#中的数组 使用 Array类中的静态方法即可
local array2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32), 10)
print(array2.Length)
3.3.1 Lua调用C# list 相关知识点
  • 添加元素
  • 获取长度
  • 遍历
  • Lua中创建List对象
--调用成员方法 用冒号
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

--在Lua中创建一个List对象
--老版本
local list2 = CS.System.Collections.Generic["List`1[System.String]"]()
print(list2)
list2:Add("123")
print(list2[0])

--新版本 >2.1.12
--相当于得到了一个 List<string> 的一个类 需要再实例化
local List_String = CS.System.Collections.Generic.List(CS.System.String)
local list3 = List_String()
list3:Add("5555")
print(list3[0])
3.3.2 Lua调用C# dictionary相关知识点 
  • 添加元素
  • 遍历
  • Lua中创建字典对象
  • 通关键获取和修改值
--使用和C#一致
obj.dic:Add(1, "123")

--遍历
for k,v in pairs(obj.dic) do
	print(k,v)
end

--在Lua中创建一个字典对象
----相当于得到了一个 Dictionary<string, Vector3> 的一个类 需要再实例化
local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String, CS.UnityEngine.Vector3)
local dic2 = Dic_String_Vector3()
dic2:Add("123", CS.UnityEngine.Vector3.right)

for k,v in pairs(dic2) do
	print(k,v)
end
--在Lua中创建的字典 直接通过键中括号得 得不到 是nil
print(dic2["123"])
print(dic2:TryGetValue("123"))
--如果要通过键获取值 要通过这个固定方法
print(dic2:get_Item("123"))
dic2:set_Item("123", nil)
print(dic2:get_Item("123"))

3.4 Lua使用C#拓展方法

  • 想要在Lua中使用拓展方法 一定要在工具类前面加上特性
  • 建议 Lua中要使用的类 都加上该特性 可以提升性能
  • 如果不加该特性 除了拓展方法对应的类 其他类的使用 都不会报错
  • 但是Lua是通过反射的机制去调用的C#类 效率较低

C#代码

//想要在Lua中使用拓展方法 一定要在工具类前面加上特性
//建议 Lua中要使用的类 都加上该特性 可以提升性能
//如果不加该特性 除了拓展方法对应的类 其他类的使用 都不会报错
//但是Lua是通过反射的机制去调用的C#类 效率较低
[LuaCallCSharp]
public static class Tools
{
    //lesson4的拓展方法 
    public static void Move(this Lesson4 obj)
    {
        Debug.Log(obj.name + "移动");
    }
}

public class Lesson4
{
    public string name = "ZTT";

    public void Speak(string str)
    {
        Debug.Log(str);
    }

    public static void Eat()
    {
        Debug.Log("吃东西");
    }
}

Lua代码

Lesson4 = CS.Lesson4
--使用静态方法
--CS.命名空间.类名.静态方法名()
Lesson4.Eat()

--成员方法 实例化处理用
local obj = Lesson4()
--成员方法 一定用冒号
obj:Speak("hahahahah")

--使用拓展方法
--要调用 C#中的某个类的拓展方法 那一定要在拓展方法的静态类前面加上LuaCallCSharp特性
obj:Move()

3.5 Lua使用C# ref和out函数

  • ref参数 会以多返回值的形式返回给lua
  • 如果函数存在返回值 那么第一个值 就是该返回值
  • 之后的返回值 就是ref的结果 从左到右一一对应
  • ref参数 需要传入一个默认值 占位置
  • out参数 会以多返回值的形式返回给lua
  • 如果函数存在返回值 那么第一个值 就是该返回值
  • 之后的返回值 就是out的结果 从左到右一一对应
  • out参数 不需要传占位置的值
  • 混合使用时 综合上面的规则
  • ef需占位 out不要传
  • 第一个是函数的返回值 之后 从左到右依次对应ref或者out

C#代码

public class Lesson5
{
    public int RefFun(int a, ref int b, ref int c, int d)
    {
        b = a + d;
        c = a - d;
        return 100;
    }

    public int OutFun(int a, out int b, out int c, int d)
    {
        b = a;
        c = d;
        return 200;
    }

    public int RefOutFun(int a, out int b, ref int c, int d)
    {
        b = a * 10;
        c = d * 10;
        return 300;
    }

}

Lua代码

--ref参数 会以多返回值的形式返回给lua
--如果函数存在返回值 那么第一个值 就是该返回值
--之后的返回值 就是ref的结果 从左到右一一对应
--ref参数 需要传入一个默认值 占位置
--a 相当于 函数返回值
--b 第一个ref
--c 第二个ref
local a,b,c = obj:RefFun(1, 0, 0, 1)
print(a)
print(b)
print(c)

--out参数 会以多返回值的形式返回给lua
--如果函数存在返回值 那么第一个值 就是该返回值
--之后的返回值 就是out的结果 从左到右一一对应
--out参数 不需要传占位置的值
local a,b,c = obj:OutFun(20,30)
print(a)
print(b)
print(c)


--混合使用时 综合上面的规则
--ref需占位 out不要传
--第一个是函数的返回值 之后 从左到右依次对应ref或者out
local a,b,c = obj:RefOutFun(20,0,50)
print(a)--300
print(b)--200
print(c)--500

3.6 Lua使用C#重载函数

  • 虽然Lua自己不支持写重载函数
  • 但是Lua支持调用C#中的重载函数
  • Lua虽然支持调用C#重载函数
  • 但是因为Lua中的数值类型 只有Number
  • 对C#中多精度的重载函数支持不好 会分不清
  • 在使用时 可能出现意想不到的问题
  • 解决重载函数含糊的问题
  • xlua提供了解决方案 反射机制
  • 这种方法只做了解 尽量别用
  • Type是反射的关键类

C#代码

public class Lesson6
{
    public int Calc()
    {
        return 100;
    }

    public int Calc(int a, int b)
    {
        return a + b;
    }

    public int Calc(int a)
    {
        return a;
    }

    public float Calc(float a)
    {
        return a;
    }
}

Lua代码

local obj = CS.Lesson6()

--虽然Lua自己不支持写重载函数
--但是Lua支持调用C#中的重载函数
print(obj:Calc())
print(obj:Calc(15, 1))

--Lua虽然支持调用C#重载函数
--但是因为Lua中的数值类型 只有Number
--对C#中多精度的重载函数支持不好 傻傻分不清
--在使用时 可能出现意想不到的问题
print(obj:Calc(10))
print(obj:Calc(10.2))


--解决重载函数含糊的问题
--xlua提供了解决方案 反射机制
--这种方法只做了解 尽量别用
--Type是反射的关键类

--得到指定函数的相关信息
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)

--成员方法 第一个参数传对象
--静态方法 不用传对象
print(f1(obj, 10))
print(f2(obj, 10.2))

3.7 Lua使用C#委托和事件

  • Lua中没有复合运算符 不能+=
  • 如果第一次往委托中加函数 因为是nil 不能直接+
  • 所有第一次 要先等=
  • 事件加减函数 和 委托非常不一样
  • lua中使用C#事件 加函数
  • 有点类似使用成员方法 冒号:事件名("+", 函数变量)

C#代码

public class Lesson7
{
    //声明委托和事件
    public UnityAction del;

    public event UnityAction eventAction;

    public void DoEvent()
    {
        if(eventAction != null)
        {
            eventAction();
        }
    }
    public void ClearEvent()
    {
        eventAction = null;
    }
}

Lua代码

local obj = CS.Lesson7()

--委托时用装函数的
--使用C#中的委托 就是用来装lua函数的
local fun = function()
	print("Lua函数Fun")
end

--Lua中没有复合运算符 不能+=
--如果第一次往委托中加函数 因为是nil 不能直接+
--所有第一次 要先等=
print("**************开始加函数***************")
obj.del = fun
--obj.del = obj.del + fun
obj.del = obj.del + fun
--不建议这样写 最好还是 先声明函数再加
obj.del = obj.del + function()
	print("临时申明的函数")
end
--委托执行
obj.del()
print("**************开始减函数***************")
obj.del = obj.del - fun
obj.del = obj.del - fun
--委托执行
obj.del()
--情况所有存储的函数
obj.del = nil
--清空过后得先等
obj.del = fun
--调用
obj.del()

print("**************Lua调用C# 事件相关知识点***************")
local fun2 = function()
	print("事件加的函数")
end

--事件加减函数 和 委托非常不一样
--lua中使用C#事件 加函数
--有点类似使用成员方法 冒号事件名("+", 函数变量)
print("**************事件加函数***************")
obj:eventAction("+", fun2)
obj:eventAction("+", fun2)
--最好不要这样写
obj:eventAction("+", function()
	print("事件加的匿名函数")
end)

obj:DoEvent()

print("**************事件减函数***************")
obj:eventAction("-", fun2)
obj:DoEvent()

print("**************事件清除***************")
--obj.eventAction = nil
obj:ClearEvent()
obj:DoEvent()

3.8 Lua使用C#二维数组

  • 获取长度GetLength(0)
  • 获取元素,不能通过[0,0]或者[0][0]访问元素 会报错
  • 通过方法GetValue(0,0)获取

C#代码

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

    public Lesson8()
    {
        //array.GetLength
    }
}

Lua代码

local obj = CS.Lesson8()

--获取长度
print("行:"..obj.array:GetLength(0))
print("列:"..obj.array:GetLength(1))

--获取元素
--不能通过[0,0]或者[0][0]访问元素 会报错
--print(obj.array[0][0])
print(obj.array:GetValue(0,0))
print(obj.array:GetValue(1,0))

print("**********************")
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

3.9 Lua使用C#的null和nil比较

  • 方法1 使用C#中的Equals
  • 方法2 在Lua中写一个判空函数
  • 方法3 在C#中使用拓展方法为Object写一个判空函数

C#代码

//为Objet拓展方法
[LuaCallCSharp]
public static class Lesson9
{
    //拓展一个为Object判空的方法 主要是给Lua用 Lua没法用null和nil比较
    public static bool IsNull(this UnityEngine.Object obj)
    {
        return obj == null;
    }
}

Lua代码

--判断全局函数
function IsNull(obj)
	if obj == nil or obj:Equals(nil) then
		return true
	end
	return false
end


--往场景对象上添加一个脚本 如果存在就不加 如果不存在再加
GameObject = CS.UnityEngine.GameObject
Rigidbody = CS.UnityEngine.Rigidbody

local obj = GameObject("测试加脚本")
--得到身上的刚体 如果没有 就加 有就不管
local rig = obj:GetComponent(typeof(Rigidbody))
print(rig)
--判断空
--if rig == nil then
--第一种方法
--if rig:Equals(nil) then
--第二种方法
--if IsNull(rig) then

--第三种方法
if rig:IsNull() then 
	print("123")
	rig = obj:AddComponent(typeof(Rigidbody))
end

print(rig)

3.10 Lua和系统类或委托相互使用

  • 在静态类中声明一个List<Type>的静态变量,为其添加CSharoCallLua或LuaCallCSharp特性

C#代码

public static class Lesson10
{
    [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),
    };
}

Lua代码

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)

3.11 Lua使用C#协程

Lua代码

--xlua提供的一个工具表
--一定是要通过require调用之后 才能用
util = require("xlua.util")
--C#中协程启动都是通过继承了Mono的类 通过里面的启动函数StartCoroutine

GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds

--在场景中新建一个空物体 然后挂载一个脚本上去 脚本继承mono使用它来开启协程
local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.LuaCallCSharp))

--希望用来被开启的协程函数
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
			mono:StopCoroutine(b)
		end
	end
end
--我们不能直接将 Lua函数传入到开启协程中!!
--如果要把Lua函数当做协程函数传人
--必须 先调用 xlua.util中的cs_generator(lua函数)
b = mono:StartCoroutine(util.cs_generator(fun))

3.12 Lua使用C#泛型函数

  • 支持有约束有参数的泛型函数
  • lua中不支持 没有约束的泛型函数
  • lua中不支持 有约束 但是没有参数的泛型函数
  • lua中不支持 非class的约束
  • 有一定的使用限制
  • Mono打包 这种方式支持使用
  • il2cpp打包 如果泛型参数是引用类型才可以使用
  • il2cpp打包 如果泛型参数是值类型,除非C#那边已经调用过了 同类型的泛型参数 lua中才能被使用
  • 补充知识 让上面 不支持使用的泛型函数 变得能用
  • 得到通用函数
  • 设置泛型函数再使用

C#代码

public class Lesson12
{
    public interface ITest
    {

    }

    public class TestFather
    {

    }

    public class TestChild : TestFather, ITest
    {

    }

    public void TestFun1<T>(T a, T b) where T : TestFather
    {
        Debug.Log("有参数有约束的泛型方法");
    }

    public void TestFun2<T>(T a)
    {
        Debug.Log("有参数 没有约束的");
    }

    public void TestFun3<T>() where T : TestFather
    {
        Debug.Log("有约束 但是没有参数的泛型函数");
    }

    public void TestFun4<T>(T a) where T : ITest
    {
        Debug.Log("有约束有参数,但是约束不是类");
    }
}

Lua代码

local obj = CS.Lesson12()

local child = CS.Lesson12.TestChild()
local father = CS.Lesson12.TestFather()

--支持有约束有参数的泛型函数
obj:TestFun1(child, father)
obj:TestFun1(father, child)

--lua中不支持 没有约束的泛型函数
--obj:TestFun2(child)

--lua中不支持 有约束 但是没有参数的泛型函数
--obj:TestFun3()

--lua中不支持 非class的约束
--obj:TestFun4(child)


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

--补充知识 让上面 不支持使用的泛型函数 变得能用
--得到通用函数
--设置泛型函数再使用
--xlua.get_generic_method(类, "函数名")
local testFun2 = xlua.get_generic_method(CS.Lesson12, "TestFun2")
local testFUn2_R = testFun2(CS.System.Int32)
--调用
--成员方法 第一个参数 传调用函数的对象
testFUn2_R(obj, 1)

4、热补丁,Hotfix

  • 直接写好代码 运行 是会报错的
  • 我们必须做4个非常重要的操作
  • 1.加特性
  • 2.加宏
  • 3.生成代码
  • 4.hotfix 注入 --注入时可能报错 提示你要引入Tools

C#代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using XLua;
using XLuaTest;

[Hotfix]
public class HotfixTest
{
    public HotfixTest()
    {
        Debug.Log("HotfixTest构造函数");
    }

    public void Speak(string str)
    {
        Debug.Log(str);
    }

    ~HotfixTest()
    {

    }
}

[Hotfix]
public class HotfixTest2<T>
{
    public void Test(T str)
    {
        Debug.Log(str);
    }
}

[Hotfix]
public class HotfixMain : MonoBehaviour
{
    HotfixTest hotfixTest;

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

    //属性
    public int Age
    {
        get
        {
            return 0;
        }
        set
        {
            Debug.Log(value);
        }
    }

    //索引器
    public int this[int index]
    {
        get
        {
            if(index >= array.Length || index < 0)
            {
                Debug.Log("索引不正确");
                return 0;
            }
            return array[index];
        }
        set
        {
            if (index >= array.Length || index < 0)
            {
                Debug.Log("索引不正确");
                return;
            }
            array[index] = value;
        }
    }

    event UnityAction myEvent;

    // Start is called before the first frame update
    void Start()
    {
        LuaMgr.Instance.Init();
        LuaMgr.Instance.DoLuaFile("Main");

        Debug.Log(Add(10, 20));
        Speak("当老师是个好人");

        hotfixTest = new HotfixTest();
        hotfixTest.Speak("hhhhhhh");

        //StartCoroutine(TestCoroutine());

        this.Age = 100;
        Debug.Log(this.Age);

        this[99] = 100;
        Debug.Log(this[999]);

        myEvent += TestTest;
        //myEvent();
        myEvent -= TestTest;

        HotfixTest2<string> t1 = new HotfixTest2<string>();
        t1.Test("131");

        HotfixTest2<int> t2 = new HotfixTest2<int>();
        t2.Test(1000);
    }

    public void TestTest()
    {
        print("TestTest");
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyUp(KeyCode.Space))
        {
            Debug.Log("触发事件myEvent");
            myEvent?.Invoke();
        }
    }

    IEnumerator TestCoroutine()
    {
        while (true)
        {
            yield return new WaitForSeconds(1);
            Debug.Log("C#协程函数打印一层");
        }
    }

    public int Add(int a, int b)
    {
        return 0;
    }

    public static void Speak(string str)
    {
        Debug.Log("哈哈哈哈");
    }
}

4.1 单函数替换

xlua.hotfix(类, "函数名", lua函数)

--lua当中 热补丁代码固定写法
--xlua.hotfix(类, "函数名", "lua函数")

--成员函数 第一个参数 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.2 多函数替换

xlua.hotfix(类, {函数名 = 函数, 函数名 = 函数...})

--xlua.hotfix(类, {函数名 = 函数, 函数名 = 函数...})
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
})


xlua.hotfix(CS.HotfixTest, {
	--构造函数 [".ctor"] 热补丁固定写法!!!
	--它们和别的函数不同 不是替换 是先调用原逻辑 再调用lua逻辑
	[".ctor"] = function()
		print("lua热补丁构造函数")
	end,
	Speak = function(self, a)
		print("ZTT说的"..a)
	end,
	--析构函数固定写法Finalize
	Finalize = function()
		
	end
})

4.3 协程函数替换

--xlua.hotfix(类, {函数名 = 函数, 函数名 = 函数...})
--要在lua中配合C#协程函数 那么必须使用它
util = require("xlua.util")
xlua.hotfix(CS.HotfixMain, {
	TestCoroutine = function(self)
		--返回一个正二八经的 xlua处理过的协程函数
		return util.cs_generator(function()
			while true do
				coroutine.yield(CS.UnityEngine.WaitForSeconds(1))
				print("Lua打补丁后的协程函数")
			end
		end)
	end
})

4.4 索引器和属性替换

xlua.hotfix(CS.HotfixMain, {
	--如果是属性进行热补丁重定向
	--set_属性名 是设置属性 的方法
	--get_属性名 是得到属性 的方法
	set_Age = function(self, v)
		print("Lua重定向的属性"..v)
	end,
	get_Age = function(self)
		return 10;
	end,

	--索引器固定写法
	--set_Item 通关索引器设置
	--get_Item 通关索引器获取
	set_Item = function(self, index, v)
		print("Lua重定向索引器,索引:"..index.."值"..v)
	end,
	get_Item = function(self, index)
		print("Lua重定向索引器")
		return 999
	end
})

4.5 事件操作替换

xlua.hotfix(CS.HotfixMain, {
	--add_事件名 代表着事件加操作
	--remove_事件名 减操作
	add_myEvent = function(self, del)
		print(del)
		print("添加事件函数")
		--会去尝试使用lua使用C#事件的方法去添加
		--在事件加减的重定向lua函数中
		--千万不用把传入的委托往事件里存
		--否则会死循环
		--会把传入的 函数 存在lua中
		--self:myEvent("+", del)
	end,
	remove_myEvent = function(self, del)
		print(del)
		print("移除事件函数")
	end
})

4.6 泛型类替换

  • 泛型类 T是可以变化
  • ua中的替换 要一个类型一个类型的来
--泛型类 T是可以变化 那lua中应该如何替换呢?
--lua中的替换 要一个类型一个类型的来

xlua.hotfix(CS.HotfixTest2(CS.System.String), {
	Test = function(self, str)
		print("lua中打的补丁:"..str)
	end
})

xlua.hotfix(CS.HotfixTest2(CS.System.Int32), {
	Test = function(self, num)
		print("lua中打的补丁:"..(1 + num))
	end
})