xLua热更新解决方案

news2024/11/25 10:42:07

图中灰色的无法实现热更新,而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.coloradmin.cn/o/1633813.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

使用Visual Studio在CMake项目中链接第三方库(OpenCV为例)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 为什么要链接第三方库&#xff1f; 提高效率&#xff1a;使用第三方库可以加速开发过程&#xff0c;因为你不需要从头开始编写所…

2024.4.25 LoadRunner 测试工具详解 —— Controller Analysis

目录 Controller 的使用 创建场景 Controller 快捷方式创建场景 VUG 针对写好脚本创建场景 场景设计 设计初始化 设计启动机制 设计性能测试脚本的执行时间 设计虚拟用户退出机制 场景运行 添加监控指标至图标格区域 Analysis 的使用 汇总报告 测试报表 吞吐量图 …

消灭AI“耗电巨兽”?暴雨服务器推出液冷节能降耗算力方案

在科技飞速发展的今天&#xff0c;人工智能已成为驱动未来的重要力量。随着AI及大模型技术的进一步普及和应用场景的拓宽&#xff0c;相关算力需求呈指数级增长&#xff0c;大规模的AI训练和推理过程均需消耗大量电力&#xff0c;如同一个巨大的电力黑洞&#xff0c;吞噬着海量…

线阵相机和面阵相机简介

线阵相机 线阵相机&#xff0c;顾名思义就是所探测的物体要在一个很长的界面上。线阵相机的传感器只有一行感光像素&#xff0c;所以线阵相机一般具有非常高的扫描频率和分辨率。 线阵相机特点 线阵相机使用的线扫描传感器通常只有一行感光单元&#xff08;少数彩色线阵使用…

密文域可逆信息隐藏技术综述(上)

加密图像可逆信息隐藏是一种加密原始图像后&#xff0c;在密文图像中可逆地隐藏附加数据&#xff0c;并且在数据提取后&#xff0c;原始图像可以被无损重建的技术。RDH-EI的分类如图1所示。 按对图像的加密方法&#xff0c;现有RDH-EI算法可分为对称加密域和非对称(公钥)加密域…

新手向:HTML进阶

一&#xff0c;列表 列表分为有序列表&#xff0c;无序列表&#xff0c;定义列表三种 1.有序列表 ol 嵌套 li&#xff0c;ol 是有序列表&#xff0c;li 是列表条目 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8">…

结构方程模型【SEM】:非线性、非正态、交互作用及分类变量分析

张老师&#xff08;研究员&#xff09;&#xff0c;长期从事R语言结构方程模型、群落生态学、保护生物学、景观生态学和生态模型方面的研究和教学工作&#xff0c;已发表了多篇论文&#xff0c;拥有丰富的科研及实践经验。 利用结构方程模型建模往往遇到很多‘特殊’情况&…

【论文阅读】Multi-Attention Based Ultra Lightweight Image Super-Resolution

Multi-Attention Based Ultra Lightweight Image Super-Resolution 论文地址摘要1 简介2 相关工作3 建议的方法3.1 特征融合组&#xff08;FFG&#xff09;3.2 多注意力块&#xff08;MAB&#xff09; 4 Experimental Setup4.1 消融研究4.2 Comparison with Existing Methods 5…

Rust HashMap

一、HashMap是什么&#xff0c;怎么用 1、HashMap是什么 HashMap 也是 Rust 标准库中提供的集合类型&#xff0c;但是又与动态数组不同&#xff0c;HashMap 中存储的是一一映射的 KV 键值对&#xff0c;并提供了平均时间复杂度为 O(1) 的查询方法。 2、HashMap怎么用 &…

基于Sping Boot集成的websocket实现聊天室

Spring Boot整合WebSocket实现聊天室 Spring Boot 提供了 Websocket 组件 spring-boot-starter-websocket&#xff0c;用来支持在 Spring Boot环境下对Websocket 的使用。 下面我们就以多人在线聊天室为例&#xff0c;演示 Spring Boot 是如何整合Websocket 实现服务端消息推…

Github Action Bot 开发教程

Github Action Bot 开发教程 在使用 Github 时&#xff0c;你可能在一些著名的开源项目&#xff0c;例如 Kubernetes&#xff0c;Istio 中看到如下的一些评论&#xff1a; /lgtm /retest /area bug /assign xxxx ...等等&#xff0c;诸如此类的一些功能性评论。在这些评论出现…

从0开始学习制作一个微信小程序 前端学习部分(7)数据控制操作,修改、判断等

系列文章目录 本系列会从前后端的全面角度讲述制作小程序&#xff0c;从零开始学小程序&#xff0c;跟着本系列就够了&#xff01; 前端学习篇 学习篇第一篇我们讲了编译器下载&#xff0c;项目、环境建立、文件说明与简单操作&#xff1a;第一篇链接 第二、三篇分析了几个重要…

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

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

KKView远程控制2.0版本发布,TeamViewer面临巨大挑战

KKView远程控制2.0版本发布&#xff0c;TeamViewer面临巨大挑战 近日&#xff0c;备受瞩目的远程控制软件KKView发布了其全新2.0版本&#xff0c;KKView以其独特的创新性和用户友好的设计&#xff0c;为远程办公、远程培训等领域提供了更加高效、便捷的解决方案。 KKView远程…

Hive 表定义主键约束

文章目录 1.建表语句2.主键约束3.主键约束的意义参考文献 1.建表语句 先看一下官方给的完整的见表语句&#xff1a; CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name -- (Note: TEMPORARY available in Hive 0.14.0 and later)[(col_name data…

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

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

【银角大王——Django课程——用户表的基本操作】

Django课程——用户表的基本操作 模板的继承用户管理用户列表展示新建用户Django组件原始方法【麻烦&#xff0c;太原始】form组件modelform组件 使用modelsform组件编写添加页面 模板的继承 &#xff08;1&#xff09;先写一个页面模板————这个案例中的模板基本上就是一个…

【Spring AI】09. ETL 管道

文章目录 ETL PipelineAPI 概述入门指南ETL 接口和实现DocumentReaderJsonReaderTextReaderPagePdfDocumentReaderParagraphPdfDocumentReaderTikaDocumentReader DocumentTransformerTextSplitterTokenTextSplitterContentFormatTransformerKeywordMetadataEnricherSummaryMet…

ABAP 数据写入Excel 并保存

参考老白 https://www.cnblogs.com/liaojunbo/archive/2011/09/06/2168552.html 但是缺zcl_excel 。需要从 dotabap要引入abap2xlsx 英文版进入后 尝试了一下 1&#xff09;列的宽度自适应么有找到在哪里&#xff1f; 列宽设置 lo_worksheet->set_column_width( ip_co…