xLua热更新解决方案

图中灰色的无法实现热更新,而Lua代码可以打包成AB包,并上传到资源服务器,

当进入游戏检测是否有资源需要更新,需要则会从资源服务器下载。

学习目标

1.导入xLua框架

2.C#调用Lua

3.Lua调用C#

4.xLua热补丁

xLua框架导入和AB包相关准备

xLua导入

去github上下载xlua,将Assets文件夹中的Plugins和Xlua文件夹赋值到Unity的Assets文件夹中,生成XLua代码

AB包导入

Unity Asset Bundle Browser 工具 - Unity 手册,git导入该工具。新版Unity已经将AB包封装在Addressables中

单例模式基类导入

导入资源包中的Base文件夹。

AB包管理器的导入

导入ABMgr.cs

C#调用Lua

使用建议

  1. 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。

  2. 如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。

Lua解析器

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//引用命名空间
using XLua;
public class Lesson1_LuaEnv : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        //Lua解析器 能够让我们在Unity中执行Lua
        //一般情况下 保持它的唯一性
        LuaEnv env = new LuaEnv();

        //执行Lua语言
        //里面写Lua代码,注意别再使用双引号,不然需要转义字符
        //参数二 三 有默认值
        //参数二 报错时输出,可以用来写报错来源
        //参数三 解析器传入 可以得知是哪个解析器出错
        env.DoString("print('hello world')", "Lesson1_LuaEnv");

        //执行一个Lua脚本   Lua知识点: 多脚本执行 require
        //require('脚本名')
        //默认寻找脚本路径的是Resources文件夹下的
        //估计是通过Resources.Load去加载Lua脚本 txt bytes等等可以识别
        //所以Lua脚本后缀要加上 txt
        env.DoString("require('Main')");

        //帮助我们清除Lua中我们没有手动释放的对象 垃圾回收
        //帧更新中定时执行  或者 切场景时执行
        env.Tick();

        //销毁Lua解析器
        env.Dispose();
    }
}

关键点:默认Lua脚本路径在Resources文件夹下

注意调用Lua脚本估计是使用Resources的Load方法,它不能识别lua,所以要加上后缀txt,这样就可以执行

Lua文件加载重定向

使用require加载lua脚本,只能实现加载Resources文件下的脚本,这不能实现热更新。

因此要进行加载重定向

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;
public class Lesson2_Loader : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        LuaEnv env = new LuaEnv();

        //xlua提供的一个 路径重定向 的方法
        //允许我们自定义加载 Lua文件的规则
        //当我们执行Lua语言 require 时 相当于执行一个Lua脚本
        //它就会执行我们自定义 传入的函数
        //参数是一个返回值为byte[] 的委托
        env.AddLoader(MyCustomLoader);
        //最终是去AB包中加载 lua文件,目前还不是

        env.DoString("require('Main')");
        //失败的例子
        env.DoString("require('ttt')");
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    //自动执行
    private byte[] MyCustomLoader(ref string filePath)
    {
        //通过函数中的逻辑 去加载lua文件

        //传入的参数是 require执行的Lua脚本文件名
        Debug.Log(filePath);

        //拼接一个lua文件所在路径
        //Application.dataPath 可以得到Assets路径 
        //记得加上lua后缀
        string path = Application.dataPath + "/Lua/" + filePath + ".lua";
        Debug.Log(path);

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

        return null;
    }
}

require查找顺序,因为委托可以添加多个自定义函数,因此可以依次通过自定义函数找文件

Lua解析器管理器

基本完成

LuaMgr

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

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

    /// <summary>
    /// 初始化解析器
    /// </summary>
    public void Init()
    {
        //已经初始化了 不初始化
        if (luaEnv != null) return;
        //初始化
        luaEnv = new LuaEnv();
        //加载lua脚本 重定向
        luaEnv.AddLoader(MyCustomLoader);
    }

    private byte[] MyCustomLoader(ref string filePath)
    {
        //通过函数中的逻辑 去加载lua文件

        //传入的参数是 require执行的Lua脚本文件名

        //拼接一个lua文件所在路径
        //Application.dataPath 可以得到Assets路径 
        //记得加上lua后缀
        string path = Application.dataPath + "/Lua/" + filePath + ".lua";

        //有路径 就去加载文件
        //File知识点 C#提供的文件读写的类
        //需要 System.IO命名空间
        //判断文件是否存在
        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);

        }
        else
        {
            Debug.Log("MyCutomLoader重定向失败,文件名为" + filePath);
        }


        return null;
    }

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

    /// <summary>
    /// 释放lua垃圾
    /// </summary>
    public void Tick()
    {
        if (luaEnv == null)
        {
            Debug.Log("解析器未初始化");
            return;
        }
        luaEnv.Tick();
    }
    

    /// <summary>
    /// 销毁解析器
    /// </summary>
    public void Dispose()
    {
        if (luaEnv == null)
        {
            Debug.Log("解析器未初始化");
            return;
        }
        luaEnv.Dispose();
        luaEnv = null;
    }
}

测试代码

    void Start()
    {
        //初始化解析器
        LuaMgr.GetInstance().Init();

        LuaMgr.GetInstance().DoString("require('Main')");
    }

彻底完成(实现AB包加载)

将脚本后缀加上.txt(ab包同样不能识别.lua),然后如图打包(路径改为PC因为AB包的管理器设置为PC路径)

build时可能出现大量报错,是xlua生成代码出错

清空生成代码后再build

更新LuaMgr

   public void Init()
    {
        //已经初始化了 不初始化
        if (luaEnv != null) return;
        //初始化
        luaEnv = new LuaEnv();
        //加载lua脚本 重定向
        luaEnv.AddLoader(MyCustomLoader);
        luaEnv.AddLoader(MyCustomABLoader);
    }

    private byte[] MyCustomLoader(ref string filePath)
    {
        //通过函数中的逻辑 去加载lua文件

        //传入的参数是 require执行的Lua脚本文件名

        //拼接一个lua文件所在路径
        //Application.dataPath 可以得到Assets路径 
        //记得加上lua后缀
        string path = Application.dataPath + "/Lua/" + filePath + ".lua";

        //有路径 就去加载文件
        //File知识点 C#提供的文件读写的类
        //需要 System.IO命名空间
        //判断文件是否存在
        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);

        }
        else
        {
            Debug.Log("MycutomLoader重定向失败,文件名为" + filePath);
        }


        return null;
    }

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


        //加载Lua文件 返回
        //因为后缀为txt,补上.lua
        TextAsset tx = ab.LoadAsset<TextAsset>(filePath + ".lua");
        //加载Lua文件 byte数组
        return tx.bytes;*/

        //使用AB包管理器
        //同步加载(不能异步,重定向要求马上返回)

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

        return null;
        
    }

实际开发过程中不会使用加载AB包的方式来测试,只有最终要发布时,测试AB包功能时才会使用。在之后实践中,会写一个Lua文件后缀修改小工具。

优化

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


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

全局变量的获取

更新Main.lua

print("主Lua脚本启动")

--Unity中写lua执行
--xlua帮我们处理
--只要执行lua脚本 都会自动的进入我们的重定向函数中找文件
require("test")

编写test.lua

print("test.Lua")
testNumber = 1
testBool = true
testFloat = 1.2
testString = "123"

--通过C# 没办法直接获取局部变量
local testLocal = 10

CallVariable

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson4_CallVariable : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

        //使用lua解析器luaenv中的Global属性
        int i = LuaMgr.GetInstance().Global.Get<int>("testNumber");
        print("testNumber: "+ i);

        i = 10;
        //不会影响,因为值类型是值拷贝,不会影响原来Lua中的值
         
        
        //改值
        LuaMgr.GetInstance().Global.Set("testNumber",55);
        int i2 = LuaMgr.GetInstance().Global.Get<int>("testNumber");
        print("testNumber_i2: " + i2);


        bool b = LuaMgr.GetInstance().Global.Get<bool>("testBool");
        print("testBool: " + b);

        float f = LuaMgr.GetInstance().Global.Get<float>("testFloat");
        print("testFloat: " + f);

        double d = LuaMgr.GetInstance().Global.Get<double>("testFloat");
        print("testFloat_Double: " + d);

        string s = LuaMgr.GetInstance().Global.Get<string>("testString");
        print("testString: " + s);

        //会报错,无法获得局部变量
        //int local = LuaMgr.GetInstance().Global.Get<int>("testLocal");
        //print("testLocal: " + local);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Lua中只有Number一种类型,但可以根据它具体的值 用对应的C#类型存储。

全局函数的获取

以下取自XLua教程

访问一个全局的function

仍然是用Get方法,不同的是类型映射。

  1. 映射到delegate

    这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码(如果没生成代码会抛InvalidCastException异常)。

    delegate要怎样声明呢? 对于function的每个参数就声明一个输入类型的参数。 多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。

    参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。

    delegate的使用就更简单了,直接像个函数那样用就可以了

  2. 映射到LuaFunction

    这种方式的优缺点刚好和第一种相反。 使用也简单,LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。

更新test.Lua

print("test.Lua")
testNumber = 1
testBool = true
testFloat = 1.2
testString = "123"

--通过C# 没办法直接获取局部变量
local testLocal = 10

--无参无返回
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(a)
	arg = {...}
	for k,v in pairs(arg) do
		print(k,v)
	end
end

无参无返回

//无参无返回值的委托  XLua认识这种委托
public delegate void CustomCall();

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

        //无参无返回值
        //委托
        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提供的 一种函数的方式 少用
        LuaFunction lf = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun");
        lf.Call();


    }

有参有返回

//有参有返回的委托
//该特性在 XLua命名空间中
[CSharpCallLua]
public delegate int CustomCall2(int a);

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


        //有参有返回
        CustomCall2 call2 = LuaMgr.GetInstance().Global.Get<CustomCall2>("testFun1");
        print("有参有返回:"  + call2(10));
        //C#自带泛型委托,方便我们使用
        Func<int,int> sFun = LuaMgr.GetInstance().Global.Get<Func<int,int>>("testFun2");
        print("有参有返回:" + sFun(20));
        //XLua提供的 一种函数的方式 少用
        LuaFunction lf2 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun2");
        print("有参有返回" + lf2.Call(30)[0]);
    }

注意生成XLua代码,因为需要特性,XLua会根据特性生成相应的代码,使这个有参有返回的委托能够被识别。

多返回值

[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);

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

        
        //多返回值
        //使用out 和 ref 来接受
        CustomCall3 call3 = LuaMgr.GetInstance().Global.Get<CustomCall3>("testFun3");
        int b;
        bool c;
        string d;
        int e;
        print("第一个返回值: " + call3(100, out b, out c, out d, out e));
        print("后面的返回值:" + b + "_" + c + "_" + d + "_" + e);

        CustomCall4 call4 = LuaMgr.GetInstance().Global.Get<CustomCall4>("testFun3");
        //使用ref要初始化
        int b1 = 0;
        bool c1 = true;
        string d1 = "";
        int e1 = 0;
        print("第一个返回值: " + call4(200, ref b1, ref c1, ref d1, ref e1));
        print("后面的返回值:" + 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++)
        {
            print("第" + i + "个返回值是:" + objs[i]);
        }
    }

每次都要记得生成XLua代码

变长参数

//object[] args 可以根据是否确定使用哪种类型的参数来改变,比如只传int
//则可以使用 int[] args
[CSharpCallLua]
public delegate void CustomCall5(string a, params object[] args);

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

        //变长参数
        CustomCall5 call5 = LuaMgr.GetInstance().Global.Get<CustomCall5>("testFun4");
        call5("123", 1, 2, 3, 4, 555, 666);

        LuaFunction lf4 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun4");
        lf4.Call("456",1,2,3,114514);
    }

List和Dictionary映射table

更新test.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
}

CallListDic

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

        //同一类型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]);
        }
        //值拷贝 浅拷贝 不会改变lua中的内容
        dic["1"] = 10000;
        Dictionary<string, int> dic2 = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic");
        Debug.Log(dic2["1"]);

        //不指定类型
        Dictionary<object,object> dic3 = LuaMgr.GetInstance().Global.Get<Dictionary<object,object>>("testDic2");
        Debug.Log("********************Dictionary object********************");
        foreach (object item in dic3.Keys)
        {
            Debug.Log(item + "_" + dic3[item]);
        }
    }

都是值拷贝,不会影响lua中的值。

类映射table

更新test.lua

testClass = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("12312312312")
	end,

	testInClass = {
		testInInt = 3
	}
}

CallClass

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

public class CallLuaClass
{
    //在这个类中去声明成员变量
    //名字一定要和 lua中的一样
    //一定得是公共的,私有和保护没法赋值
    //这个自定义中的变量 可以比lua中的更多 也可以更少
    //少了就会忽略  多了也不会赋值 也会忽略
    public int testInt;
    public bool testBool;
    public float testFloat;
    public string testString;
    public CallLuaInClass testInClass;
    public UnityAction testFun;

    public void Test()
    {
        Debug.Log(testInt);
    }
  
}

public class CallLuaInClass
{
    public int testInInt;
}
public class Lesson7_CallClass : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

        print("*************Class*************");
        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.testInClass.testInInt);
        obj.testFun();

        //值拷贝  改变他不会改变lua里的值
        obj.testInt = 100;
        CallLuaClass obj2 = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClass");
        Debug.Log(obj2.testInt);
    }

}

接口映射table

需要在接口上加上[CSharpCallLua],生成xlua代码

 一旦修改接口  都要记得清空xlua代码 再重新生成

接口的拷贝是引用改变,改变值会使lua中也改变。

更新test.lua

testInterface = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("12312312312")
	end

}

CallInterface

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

//接口中不允许有成员变量
//我们用属性来接受
//接口默认public

//嵌套几乎和类一样 无非要遵循接口规则。
[CSharpCallLua]
public interface ICSharpCallInterface
{
    //同样可以 少 或 多
    //一旦修改接口  都要记得清空xlua代码 再重新生成
    int testInt { get; set; }
    string testString { get; set; }
    bool testBool { get; set; }
    float testFloat { get; set; }

    UnityAction testFun { get; set; }

}
public class Lesson8_CallInterface : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

        ICSharpCallInterface obj = LuaMgr.GetInstance().Global.Get<ICSharpCallInterface>("testInterface");
        Debug.Log(obj.testInt);
        Debug.Log(obj.testBool);
        Debug.Log(obj.testFloat);
        Debug.Log(obj.testString);
        obj.testFun();

        //接口拷贝 是引用拷贝 改了值 lua也会改变
        obj.testInt = 10000;
        ICSharpCallInterface obj1 = LuaMgr.GetInstance().Global.Get<ICSharpCallInterface>("testInterface");
        Debug.Log(obj1.testInt);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

LuaTable映射table

这种方式好处是不需要生成代码,但也有一些问题,比如慢,比方式2要慢一个数量级,比如没有类型检查。---xLua教程

更新test.lua

testLuaTable = {
	testInt = 2,
	testBool = true,
	testFloat = 1.2,
	testString = "123",
	testFun = function()
		print("12312312312")
	end

}

CallLuaTable

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

public class Lesson9_CallLuaTable : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

        //不建议使用 LuaTable 和 LuaFunction 效率低
        //引用对象   是引用拷贝
        LuaTable table =  LuaMgr.GetInstance().Global.Get<LuaTable>("testLuaTable");
        //Global是个大LuaTable,因此使用方法相同 用get,set
        Debug.Log(table.Get<int>("testInt"));
        Debug.Log(table.Get<float>("testFloat"));
        Debug.Log(table.Get<bool>("testBool"));
        Debug.Log(table.Get<string>("testString"));

        table.Get<LuaFunction>("testFun").Call();

        //改
        table.Set("testInt", 100);
        Debug.Log(table.Get<int>("testInt"));

        LuaTable table2 = LuaMgr.GetInstance().Global.Get<LuaTable>("testLuaTable");
        Debug.Log(table2.Get<int>("testInt"));


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

    // Update is called once per frame
    void Update()
    {
        
    }
}

Lua调用C#

调用类

更新Main

/// <summary>
/// Lua没办法直接访问C# 一定是先从C#调用Lua脚本后
/// 才把核心逻辑 交给Lua编写
/// </summary>
public class Main : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");
    }

}

LuaCallCSharp

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

namespace MrJin
{
    public class Test2
    {
        public void Speak(string str)
        {
            Debug.Log(str);
        }
    }
}
public class LuaCallCSharp : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

更新Main.lua

print("主Lua脚本启动")

--Unity中写lua执行
--xlua帮我们处理
--只要执行lua脚本 都会自动的进入我们的重定向函数中找文件
--require("test")
require("lesson1_CallClass")

创建lesson1_CallClass

print("************Lua调用C#类相关知识点*************")

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

--通过C#中的类 实例化一个对象 lua中没有new 所以我们直接 类名括号就是实例化对象
--默认调用的 相当于就是无参构造
local obj1 = CS.UnityEngine.GameObject()  --场景上产生空对象
local obj2 = CS.UnityEngine.GameObject("azhe") --生成名字叫azhe的空物体

--为了方便使用 并且节约性能 定义全局变量存储 C#中的类
--相当于取了别名
GameObject = CS.UnityEngine.GameObject
local ob3 = GameObject("Azhe1") --生成Azhe1物体

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

--得到对象中的成员变量 直接对象. 即可
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)

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

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

调用C#枚举

Main.lua

print("主Lua脚本启动")

--Unity中写lua执行
--xlua帮我们处理
--只要执行lua脚本 都会自动的进入我们的重定向函数中找文件
--require("test")
--require("lesson1_CallClass)
--不注释上一条 可以使用上一条定义的全局变量,如GameObject别名

require("Lesson2_CallEnum")

Lesson2_CallEnum.lua

print("************Lua调用C#枚举相关知识点*************")

--枚举调用
--调用Unity当中的枚举
--枚举的调用规则 和 类的调用规则一样
--CS.命名空间.枚举名.枚举成员
--也支持取别名 
--Unity自带的枚举,包含Capsule,Cube,Plane等物体
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject
--使用GameObject.CreatePrimitive()方法来测试
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)

更新LuaCallCSharp

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

Lua使用C#数组 list和字典

Main.lua

--require("Lesson2_CallEnum")

require("Lesson3_CallArray")

LuaCallCSharp

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>();
}
#endregion

Lesson3_LuaCallArray

print("************Lua调用C# 数组 List 字典 相关知识点*************")

local obj = CS.Lesson3()

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

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

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

--Lua中创建一个C#的数组 lua中表示数组和List可以用表
--但是要使用C#中的
--数组实际是Array类
--所以要使用Array类中的CreateInstance方法 参数一 类型, 参数二 长度
local array2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32), 10)
print(array2.Length)
print(array2[0])
print(array2[1])

print(array2)
print("************Lua调用C# 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

print(obj.list)

--在lua中创建一个List对象
--老版本
--CS.System.Collections.Generic为List的命名空间
--表示创建1个参数的List泛型里面参数是类型 String
local list2 = CS.System.Collections.Generic["List`1[System.String]"]()
print(list2)
list2:Add("123")
print(list2[0])

--新版本 > v2.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])

print("************Lua调用C# 字典 相关知识点*************")
--使用和C#一致
obj.dic:Add(1,"123")
print(obj.dic[1])

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

--在lua中创建一个字典对象
--这里直接使用新版本 老版本很复杂
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中创建的字典 直接通过[]是得不到的
print(dic2["123"]) --输出了nil
--该方法多返回值
print(dic2:TryGetValue("123")) -- true (1,0,0)
--如果要通过键获取值,要通过这个固定方法
print(dic2:get_Item("123")) --(1,0,0)
--同理设置值
dic2:set_Item("123",nil)
print(dic2:get_Item("123")) --(0,0,0)

调用C#拓展方法

需要特性[LuaCallCSharp],命名空间xlua

记得生成代码

//想要在Lua中使用扩展方法 一定要在工具类前面加上特性
//建议 lua中要使用的类 都加上该特性 可以提升性能
//如果不加该特性 除了扩展方法对应的类 其他类的使用 都不会报错
//但lua是用反射机制 调用 c# 类 效率低
//加上特性并生成代码后,会生成对应的C#代码,提升效率

仍有问题 自定义类可以加上特性,但非自定义类怎么加呢?在后面特殊问题有解答

更新luaCallCSharp

#region 拓展方法

//想要在Lua中使用扩展方法 一定要在工具类前面加上特性
//建议 lua中要使用的类 都加上该特性 可以提升性能
//如果不加该特性 除了扩展方法对应的类 其他类的使用 都不会报错
//但lua是用反射机制 调用 c# 类 效率低
//加上特性并生成代码后,会生成对应的C#代码,提升效率
[LuaCallCSharp]
public static class Tools
{
    //Lesson4的扩展方法
    public static void Move(this Lesson4 obj)
    {
        Debug.Log(obj.name + "移动");
    }
}
public class Lesson4
{
    public string name = "azhe";
    public void Speak(string str)
    {
        Debug.Log(str);
    }

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

Lesson4_CallFunction

print("************Lua调用C# 扩展方法 相关知识点*************")

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

--成员方法 实例化出来用
local obj = Lesson4()
--成员方法 一定要用冒号
obj:Speak("ahhhhhhh")

--使用扩展方法 和使用成员方法 一致
obj:Move()

调用C# ref和out函数

更新LuaCallCSharp

#region ref和out
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)
    {
        b = a * 10;
        c = a * 20;
        return 300;
    }
}
#endregion

Lesson5_CallFunction

print("************Lua调用C# ref和out方法 相关知识点*************")

Lesson5 = CS.Lesson5

local obj = Lesson5()

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

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

--混合使用
--out省略,ref要占位
--第一个是函数的返回值 之后 从左到右依次对应ref或out
local a,b,c = obj:RefOutFun(20,1)
print(a)
print(b)
print(c)

使用C#重载函数

luaCallCSharp

#region 函数重载
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;
    }

}
#endregion

Lesson6_CallFunction

print("************Lua调用C# 重载函数 相关知识点*************")

local obj = CS.Lesson6()

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

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


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

--得到指定函数的相关信息
local m1 = typeof(CS.Lesson6):GetMethod("Calc",{typeof(CS.System.Int32)})
--得到float的信息
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))

使用C#委托和事件

luaCallCSharp

#region 委托和事件
public class Lesson7
{
    //申明委托和事件
    public UnityAction del;
    public event UnityAction eventAction;
    //事件外部不能直接调用,所以提供一个方法
    public void DoEvent()
    {
        if(eventAction != null)
            eventAction();
    }

    public void ClearEvent()
    {

        eventAction = null;
    }
}
#endregion

Lesson7_CallDel

print("************Lua调用C# 委托 相关知识点*************")

local obj = CS.Lesson7()

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

--lua中没有符合运算符 不能+=
--如果第一次往委托中加函数 因为是nil 不能直接+
--第一次要先 =
--obj.del = obj.del + fun  报错
obj.del = fun

obj.del = obj.del + fun
--不建议这样写 最好先申明再加 ,不然不好移除
obj.del = obj.del + function()
	print("临时申明的函数")
end

obj.del()

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#事件 加函数
--有点类似使用成员方法 冒号事件名("+",函数变量)
obj:eventAction("+",fun2)
--最好别这样写
obj:eventAction("+",function()
	print("事件加的匿名函数")
end)
obj:DoEvent()

obj:eventAction("-",fun2)
obj:DoEvent()

--清事件 不能直接设置为nil 外部不能访问
--可以在C#中写一个方法来置空
obj:ClearEvent()
obj:DoEvent()

使用C#二维数组

#region 二维数组遍历
public class Lesson8
{
    public int[,] array = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };

}
#endregion
print("************Lua调用C# 二维数组 相关知识点*************")

local obj = CS.Lesson8()

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

--获取元素
--不能通过[0,0] 或[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

null和nil的比较

有三种可行方法

不可行(也最容易想到)

print("************Lua调用C# null和nil比较 相关知识点*************")

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

local obj = GameObject("测试加脚本")
--得到身上的刚体组件 如果没有 就加
local rig = obj:GetComponent(typeof(Rigidbody))
print(rig)
--判断空
--nil和null 没法进行==比较
if rig == nil then
	print("第一种") --没有进入if中  失败!!!!
	rig = obj:AddComponent(typeof(Rigidbody))
end
print(rig)

第一种可行

if rig:Equals(nil) then
	print("第二种") --进入if中
	rig = obj:AddComponent(typeof(Rigidbody))
end
print(rig)
--但这样也不够好,万一rig就是nil,则无法调用方法,会报错

使用了Object中的Equals方法

第二种方法(解决第一种的缺陷)

--所以可以再Main函数中添加一个全局方法判断
if isNull(rig) then
	print("第三种") --进入if中
	rig = obj:AddComponent(typeof(Rigidbody))
end
print(rig)


----------------------------------
--Main函数中
function isNull(obj)
	if obj == nil or obj:Equals(nil) then
		return true
	end
		return false
end

第三种方法(在C#中解决)

#region 判空
//为 object 扩展一个方法
[LuaCallCSharp]
public static class Lesson9 
{ 
    //扩展一个为object判空的方法 主要给lua用 lua没法用null和nil比较
    public static bool IsNull(this Object obj)
    {
        return obj == null;
    }
}

#endregion
--还有一种方法 在 C#中为Object提供一个扩展方法来判空
if rig:IsNull() then
	print("第四种") --进入if中
	rig = obj:AddComponent(typeof(Rigidbody))
end
print(rig)

lua和系统类型或委托相互使用

系统类型不能主动添加CSharpCallLua或LuaCallCSharp这样的特性

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)
--以上会报错说需要CSharpCallLua的特性,但是系统类是无法更改的

更新luacallCsharp

#region 系统类型加特性
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)
    };
}
#endregion

记得生成xlua代码

使用C#协程

Lesson10_Coroutine

print("************Lua调用C# 协程 相关知识点*************")
--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
			--停止协程 和C#中一样
			mono:StopCorotine(b)
		end
	end
end
--我们不能直接将 lua函数传入到 开启协程中!!!!!!
--mono:StartCoroutine(fun) -- 会报错
--如果要把lua函数当作协程函数传入
--必须 先调用 xlua.util中的cs_generator(lua函数)
b = mono:StartCoroutine(util.cs_generator(fun))

使用泛型函数

LuaCallCsharp

#region 调用泛型方法
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("有参数有约束, 但是约束不是类");
    }
}
#endregion

Lesson12_T

print("************Lua调用C# 泛型函数相关知识点*************")

local obj = CS.Lesson12()

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

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

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

--不支持 有约束 但是没有参数的泛型函数
--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)

关于LuaCallCSharp和CSharpCallLua特性

CSharpCallLua 适用于委托 接口

LuaCallCsharp 适用于 扩展方法  建议每个被lua调用的类都加 可以提升性能

热补丁

热补丁,可以让原本用C#写的内容不再被执行,而去执行新写的lua代码的逻辑。

第一个热补丁

第一步 加特性

第二步  加宏

注意第一次使用热补丁,要在项目的File-Build Settings - Player Settings - Player - Other Settings - Scripting Define Symbols加上热补丁的宏HOTFIX_ENABLE

第三-四步 生成代码 注入热补丁

生成代码后,点击Hotfix Inject In Editor(加入宏后出现的),但这样还不够,点击它会报错提示需要下载tools,在xlua文件夹下存在Tools文件夹

将其导入进项目文件夹下,注意要与Assets同级

这样就可以注入成功

详细代码

HotfixMain.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
[Hotfix]
public class HotfixMain : MonoBehaviour
{
    // Start is called before the first frame update 
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

        print(Add(10, 20));
        Speak("阿喆不想学习");
    }

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

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

Hotfix_Lesson1.lua

print("*********第一个热补丁***********")

--直接写好代码 运行 是会报错的
--需要4个操作
--1.加特性
--2.加宏
--3.生成代码
--4.hotfix 注入   在注入时可能报错,提示你要引入Tools

--热补丁的缺点:只要我们修改了热补丁类的代码,就要重新注入

--lua当中 热补丁代码固定写法
--xlua.hotfix(类,"函数名",lua函数)
--成员函数 记得把自己传进来
xlua.hotfix(CS.HotfixMain,"Add",function(self,a,b)
	return a + b
end)
--静态函数不用传自己
xlua.hotfix(CS.HotfixMain,"Speak",function(str)
	print(str)
end)

注意热补丁的缺点:只要我们修改了热补丁类的代码,就要重新注入

注意如果是在类中新增加函数,而不是单纯改原函数逻辑,还要生成代码。

多函数替换(构造、析构)

构造函数热补丁特点

先调用了原逻辑,后调用lua逻辑

HotfixMain.cs

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

//没继承Mono的类
[Hotfix]
public class HotfixTest
{
    public HotfixTest()
    {
        Debug.Log("HotfixTest构造函数");
    }
    public void Speak(string str)
    {
        Debug.Log(str);
    }

    ~HotfixTest() { }
}


[Hotfix]
public class HotfixMain : MonoBehaviour
{
    HotfixTest hotTest;
    // Start is called before the first frame update 
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

        print(Add(10, 20));
        Speak("阿喆不想学习");

        hotTest = new HotfixTest();
        hotTest.Speak("哈哈哈哈");
    }

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

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

Hotfix_Lesson2.lua

print("*********多函数替换***********")
--lua当中 热补丁代码固定写法
--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,{
	--构造函数 热补丁固定写法!!!!
	--他们和别的函数不同 不是替换 是先调用原逻辑 再调用lua的逻辑
	[".ctor"] = function()
		print("lua热补丁构造函数")
	end,
	Speak = function(self,a)
		print("阿喆说:"..a)
	end,
	--析构函数固定写法Finalize
	Finalize = function()
		print("lua热补丁析构函数")
	end
})

协程函数替换

更新HotfixMain

[Hotfix]
public class HotfixMain : MonoBehaviour
{
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

        StartCoroutine(TestCoroutine());
    }

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

}

Hotfix_Lesson3.lua

print("*********协程函数替换***********")

--要在lua中配合C#协程函数 必使用它
util = require("xlua.util")

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

--如果为打了Hotfix特性的C#类中新增加函数
--不能只注入 必须要先生成代码 再注入 不然注入会报错

索引器和属性替换

新增的原C#索引器与属性

[Hotfix]
public class HotfixMain : MonoBehaviour
{
    
    public int[] array = new int[] { 1, 2, 3 };

    //属性
    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 
        { 
            array[index] = value; 
        }
    }
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

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

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

Hotfix_Lesson4

print("*********属性和索引器替换***********")

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
})

事件替换

更新事件



[Hotfix]
public class HotfixMain : MonoBehaviour
{
  
    //事件
    event UnityAction myEvent;
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");

    
        myEvent += TestTest;
        myEvent -= TestTest;    
    }

    private void TestTest()
    {

    }

}

Hotfix_Lesson5

print("*********事件加减替换***********")

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

})

泛型类替换

更新泛型类

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

[Hotfix]
public class HotfixMain : MonoBehaviour
{
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");


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

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


}

Hotfix_Lesson6

print("*********泛型类的替换***********")

--泛型类 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,str)
		print("lua中打的补丁"..str)
	end
})

注意事项

1.成员方法记得加self

2.事件加减替换 不要把传入的委托往事件里存

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/583746.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是网络安全等级保护测评(等保测评)?

什么是网络安全等级保护测评&#xff08;等保测评&#xff09;呢&#xff1f;今天永恒无限就为大家介绍下网络安全等级保护测评&#xff08;等保测评&#xff09; 网络安全等级保护测评&#xff08;等保测评&#xff09;是指对信息和信息系统按照重要性等级进行的保护测评。它…

爱普生晶振在物联网LoRa通讯中的应用

LoRa 是LPWAN通信技术中的一种&#xff0c;是美国Semtech公司采用和推广的一种基于扩频技术的超远距离无线传输方案。这一方案改变了以往关于传输距离与功耗的折衷考虑方式&#xff0c;为用户提供一种简单的能实现远距离、长电池寿命、大容量的系统&#xff0c;进而扩展传感网络…

C语言:项目实践(贪吃蛇)

前言&#xff1a; 相信大家都玩过贪吃蛇这款游戏吧&#xff0c;贪吃蛇是久负盛名的游戏&#xff0c;它也和俄罗斯方块&#xff0c;扫雷等游戏位列经典游戏的行列&#xff0c;那贪吃蛇到底是怎么实现的呢&#xff1f; 今天&#xff0c;我就用C语言带着大家一起来实现一下这款游戏…

Golang Colly爬取图片gorm存储数据

语言:Golang 库:Iris/Colly/gorm 运行结果 text/html; charset=utf-8 It is image 20240429222029_0_0.jpg Saved file: images\20240429222029_0_0.jpg text/html; charset=utf-8 It is image 20240429222030_1_0.jpg Saved file: images\20240429222030_1_0.jpg It is ima…

String类1⃣️

目录 预备知识 1.string成员函数 1.string() 2.string (const char* s); 3.string (size_t n, char c); 4.string (const string& str);&#xff08;拷贝构造&#xff09; 2.string类对象的容量操作 1.size length 2.max_size 3.resize 4.capacity 5.empty 6…

【leetcode面试经典150题】78.二叉树中的最大路径和(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

元数据管理在态势感知系统的应用

在当今信息爆炸的时代&#xff0c;数据量呈指数级增长&#xff0c;如何高效地管理和利用这些数据成为了各行各业所面临的重要问题。在网络安全领域&#xff0c;态势感知系统作为一种重要的安全防御工具&#xff0c;承担着及时发现、分析和应对安全威胁的重任。 然而&#xff0c…

网络层 --- IP协议

目录 1. 前置性认识 2. IP协议 3. IP协议头格式 3.1. 4位版本 3.2. 4位首部长度 3.3. 8位服务类型 3.4. 16位总长度 3.5. 8位生存时间 TTL 3.6. 8位协议 3.7. 16位首部检验和 3.8. 32位源IP和32位目的IP 4. 分片问题 4.1. 为什么要分片 4.2. 分片是什么 4.2.1. …

助力企业部署国产云原生数据库 XSKY星辰天合与云猿生完成产品互兼容认证

近日&#xff0c;北京星辰天合科技股份有限公司&#xff08;简称&#xff1a;XSKY 星辰天合&#xff09;与杭州云猿生数据有限公司&#xff08;简称“云猿生”&#xff09;完成了产品互兼容认证&#xff0c;星辰天合企业级分布式统一数据平台 XEDP 与云猿生的开源数据库管控平台…

JAVA系列 小白入门参考资料 继承

目录 1. 为什么需要继承 2. 继承的概念 3. 继承的语法 4. 父类成员访问 4.1 子类中访问父类的成员变量 1. 子类和父类不存在同名成员变量 2. 子类和父类成员变量同名 4.2 子类中访问父类的成员方法 1. 成员方法名字不同 2. 成员方法名字相同 ​5. super关键字 …

《ElementPlus 与 ElementUI 差异集合》el-dialog 显示属性有差异

ElementPlus 用属性 v-model ElementUI 用属性 visible 其实也是 Vue2/Vue3 的差异&#xff1a;v-model 指令在组件上的使用已经被重新设计&#xff0c;替换掉了 v-bind.sync

自己手写了一个大模型RAG项目-05.基于知识库的大模型问答

大家好&#xff0c;我是程序锅。 github上的代码封装程度高&#xff0c;不利于小白学习入门。 常规的大模型RAG框架有langchain等&#xff0c;但是langchain等框架源码理解困难&#xff0c;debug源码上手难度大。 因此&#xff0c;我写了一个人人都能看懂、人人都能修改的大…

力扣HOT100 - 79. 单词搜索

解题思路&#xff1a; 深度优先搜索&#xff08;DFS&#xff09; 剪枝。 class Solution {public boolean exist(char[][] board, String word) {char[] words word.toCharArray();for(int i 0; i < board.length; i) {for(int j 0; j < board[0].length; j) {if (df…

Springboot+MybatisPlus入门案例(postman测试)

一、项目框架 pom.xml依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apac…

微软如何打造数字零售力航母系列科普04 - 微软联合Adobe在微软365应用程序中工作时推出新的生成式AI功能

微软和Adobe正在合作&#xff0c;将情境营销见解和工作流程引入微软Copilot&#xff0c;以提供生成的人工智能功能&#xff0c;使营销人员和营销团队能够在自然的工作流程中实现更多目标。 这些新的集成功能将在生产力和协作工具&#xff08;如Outlook、Teams和Word&#xff0…

【保姆级教程】用IDEA2023版本给RuoYi-Vue添加子模块

文章目录 前言添加子模块新建子模块新建子模块界面&#xff1f;新建子模块界面&#xff01; 修改pom依赖配置RuoYiApplication添加测试接口配置接口权限测试 前言 若依前后端分离框架能够极大方便当前开发任务&#xff0c;并且使用的技术栈也相当丰富&#xff0c;但是目前只提…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(六)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 12 - 13节&#xff09; P12《11.ArkUI组件-循环控制》 forEach() 方法的使用方式&#xff1a; 在预览界面点击红框的按钮&#xf…

煤矿综合自动化智能监控系统

系统概述 建设煤矿井上下工业环网、工业数据集成平台、排水、供电、运输、通风、压风、瓦斯抽放、采掘、智能洗煤厂等智能自动化控制系统&#xff0c;利用多种软硬件接口(OPC协议、驱动通讯、数据库、文本文件、DDE/NETDDE、子网等)&#xff0c;构建全矿井统一、稳定、高效的数…

vue2 实现echarts图表进入可视区域后再加载动画,以及 使用了resize之后,动画失效问题解决

Intersection Observer API 是一个现代的浏览器 API&#xff0c;用于监测一个或多个目标元素与其祖先元素或视窗&#xff08;viewport&#xff09;之间的交叉状态&#xff08;intersection&#xff09;的变化。它可以有效地监听元素是否进入或离开可视区域&#xff0c;从而实现…

使用OkHttp 缓存 API 调用提高Android应用性能

使用OkHttp 缓存 API 调用提高Android应用性能 坦率地说&#xff0c;我们都遇到过这样的情况——焦急地刷新应用&#xff0c;看着加载图标不停地旋转&#xff0c;等待那个至关重要的 API 响应。这样的等待我们已经是炉火纯青了&#xff0c;是吧&#xff1f;手指有节奏地轻敲屏…
最新文章