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

1、XLua介绍

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

2、C#调用Lua,CSharpCallLua

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

LuaEnv

cs 复制代码
        //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文件的规则

cs 复制代码
    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管理器

cs 复制代码
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代码

Lua 复制代码
testNumber = 1
testBool = true
testFloat = 1.2
testString = "2131"

C#代码

cs 复制代码
        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代码

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

cs 复制代码
    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代码

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

cs 复制代码
        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 复制代码
--lua当中的一个自定义类
testClass = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("testFun1231313")
	end,

	inClass = {
		testInt = 100
	}
}

C#代码

cs 复制代码
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 复制代码
--lua当中的一个自定义类
testClass = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("testFun1231313")
	end,

	inClass = {
		testInt = 100
	}
}

C#代码

cs 复制代码
[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 复制代码
--lua当中的一个自定义类
testClass = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("testFun1231313")
	end,

	inClass = {
		testInt = 100
	}
}

C#代码

cs 复制代码
    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 复制代码
--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#中的类,取了一个别名

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

local obj3 = GameObject("你好啊")

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

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

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

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

Vector3 = CS.UnityEngine.Vector3

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

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

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

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

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

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

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

3.2 Lua使用C#枚举

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

C#代码

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

Lua代码

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

cs 复制代码
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代码

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对象
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

--在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中创建字典对象
  • 通关键获取和修改值
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#代码

cs 复制代码
//想要在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代码

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

cs 复制代码
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代码

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

cs 复制代码
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代码

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

cs 复制代码
public class Lesson7
{
    //声明委托和事件
    public UnityAction del;

    public event UnityAction eventAction;

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

Lua代码

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

cs 复制代码
public class Lesson8
{
    public int[,] array = new int[2,3] { 
        { 1, 2, 3 }, 
        { 4, 5, 6 } 
    };

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

Lua代码

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

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

Lua代码

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

cs 复制代码
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代码

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

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

cs 复制代码
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代码

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

cs 复制代码
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 复制代码
--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(类, {函数名 = 函数, 函数名 = 函数...})

Lua 复制代码
--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 协程函数替换

Lua 复制代码
--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 索引器和属性替换

Lua 复制代码
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 事件操作替换

Lua 复制代码
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中的替换 要一个类型一个类型的来
Lua 复制代码
--泛型类 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
})
相关推荐
心前阳光2 小时前
Unity-通过Transform类学习迭代器模式
学习·unity·迭代器模式
SlowFeather4 小时前
Apache 反向代理Unity服务器
服务器·unity·apache
惊鸿醉5 小时前
⭐ Unity 实现屏幕涟漪效果:自动生成 \ 点击交互生成涟漪
unity·游戏引擎
WarPigs16 小时前
Unity网络通信笔记
网络·unity
aerror19 小时前
如何正确的用Trae 打开 Unity 3D 项目
3d·unity·游戏引擎
步、步、为营21 小时前
.NET 的IOC框架Unity代码示例
unity·游戏引擎·.net
留待舞人归1 天前
【Unity优化】提高热更新和打包速度
游戏·unity·游戏引擎·unity3d·优化
tealcwu1 天前
【Unity踩坑】Unity 6在Mac平台编译运行时去除‘trial version‘
macos·unity·游戏引擎
为你写首诗ge1 天前
【热更新知识】学习一 Lua语法学习
unity·lua