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脚本后缀要加上 txtenv.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 framevoid 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不能被识别//所以还是要改后缀为txtprivate byte[] MyCustomABLoader(ref string filePath){//没有AB包管理器时/*Debug.Log("进入AB包加载重定向函数");//从AB包中加载lua文件//加载AB包string path = Application.streamingAssetsPath + "/lua";AssetBundle ab = AssetBundle.LoadFromFile(path);//加载Lua文件 返回//因为后缀为txt,补上.luaTextAsset 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 framevoid 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) doprint(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");//同一类型ListList<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]);//不指定类型 objectList<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 framevoid 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,setDebug.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 framevoid 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 framevoid 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_MyEnumlocal 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 doprint(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 doprint(obj.list[i])
endprint(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) doprint(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) doprint(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.Lesson5local 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 = funobj.del = obj.del + fun
--不建议这样写 最好先申明再加 ,不然不好移除
obj.del = obj.del + function()print("临时申明的函数")
endobj.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 dofor j=0,obj.array:GetLength(1)-1 doprint(obj.array:GetValue(i,j))end
end

null和nil的比较

有三种可行方法

不可行(也最容易想到)

print("************Lua调用C# null和nil比较 相关知识点*************")--往场景对象上添加一个脚本 如果存在就不加 如果不存在再加
GameObject = CS.UnityEngine.GameObject
Rigidbody = CS.UnityEngine.Rigidbodylocal obj = GameObject("测试加脚本")
--得到身上的刚体组件 如果没有 就加
local rig = obj:GetComponent(typeof(Rigidbody))
print(rig)
--判断空
--nil和null 没法进行==比较
if rig == nil thenprint("第一种") --没有进入if中  失败!!!!rig = obj:AddComponent(typeof(Rigidbody))
end
print(rig)

第一种可行

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

使用了Object中的Equals方法

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

--所以可以再Main函数中添加一个全局方法判断
if isNull(rig) thenprint("第三种") --进入if中rig = obj:AddComponent(typeof(Rigidbody))
end
print(rig)----------------------------------
--Main函数中
function isNull(obj)if obj == nil or obj:Equals(nil) thenreturn trueendreturn 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() thenprint("第四种") --进入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 = 1while true do--lua中 不能直接使用 C#中的 yield return--就使用lua中的协程返回coroutine.yield(WaitForseconds(1))print(a)a = a + 1if a > 10 then--停止协程 和C#中一样mono:StopCorotine(b)endend
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 + bend,Speak = function(a)print(a)end
})xlua.hotfix(CS.HotfixTest,{--构造函数 热补丁固定写法!!!!--他们和别的函数不同 不是替换 是先调用原逻辑 再调用lua的逻辑[".ctor"] = function()print("lua热补丁构造函数")end,Speak = function(self,a)print("阿喆说:"..a)end,--析构函数固定写法FinalizeFinalize = 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 docoroutine.yield(CS.UnityEngine.WaitForSeconds(1))print("lua打补丁后的协程函数")endend)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 10end,--索引器固定写法--set_Item 设置索引器--get_Item 通过索引器获取set_Item = function (self, index, v )print("lua重定向索引器,索引:"..index.."值"..v)end,get_Item = function(self,index)print("lua重定向索引器")return 999end
})

事件替换

更新事件

[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.mzph.cn/pingmian/5088.shtml

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

相关文章

Bert基础(二十)--Bert实战:机器阅读理解任务

一、机器阅读理解任务 1.1 概念理解 机器阅读理解&#xff08;Machine Reading Comprehension, MRC&#xff09;就是给定一篇文章&#xff0c;以及基于文章的一个问题&#xff0c;让机器在阅读文章后对问题进行作答。 在机器阅读理解领域&#xff0c;模型的核心能力体现在对…

Flink checkpoint 源码分析

序言 最近因为工作需要在阅读flink checkpoint处理机制&#xff0c;学习的过程中记录下来&#xff0c;并分享给大家。也算是学习并记录。 目前公司使用的flink版本为1.11。因此以下的分析都是基于1.11版本来的。 在分享前可以简单对flink checkpoint机制做一个大致的了解。 …

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

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

JENKINS 安装,学习运维从这里开始

Download and deployJenkins – an open source automation server which enables developers around the world to reliably build, test, and deploy their softwarehttps://www.jenkins.io/download/首先点击上面。下载Jenkins 为了学习&#xff0c;从windows开始&#x…

preg_match详解(反向引用和捕获组)

在讲preg_match函数之前&#xff0c;我们先了解一下什么是php可变变量 php可变变量 在PHP中双引号包裹的字符串中可以解析变量&#xff0c;而单引号则不行 也就是在php中&#xff0c;双引号里面如果包含有变量&#xff0c;php解释器会将其替换为变量解释后的结果&#xff1b…

AI系列:大语言模型的RAG(检索增强生成)技术(上)

前言 大型语言模型&#xff08;LLM&#xff09;虽然在生成文本方面表现出色&#xff0c;但仍然存在一些局限性&#xff1a;数据是静态的&#xff0c;而且缺乏垂直细分领域的知识。为了克服这些限制&#xff0c;有时候会进行进一步的模型训练和微调。在实际应用中&#xff0c;我…

基于深度学习检测恶意流量识别框架(80+特征/99%识别率)

基于深度学习检测恶意流量识别框架 目录 基于深度学习检测恶意流量识别框架简要示例a.检测攻击类别b.模型训练结果输出参数c.前端检测页面d.前端训练界面e.前端审计界面&#xff08;后续更新了&#xff09;f.前端自学习界面&#xff08;自学习模式转换&#xff09;f1.自学习模式…

【嵌入式Linux】阻塞与非阻塞IO为何能降低CPU使用率

本文主要记录嵌入式Linux内核中阻塞与非阻塞IO访问的应用&#xff0c;以及解释了为何二者可以降低CPU使用率 阻塞与非阻塞IO为何能降低CPU使用率 0. 授权须知1. 通俗解释2. 场景描述3. 阻塞IO之———等待队列使用详解4. 非阻塞IO之———poll select4.1 poll 访问4.2 selct 访…

华为L410终端及麒麟KOS上如何安装安卓应用

原文链接&#xff1a;华为L410终端及麒麟KOS上如何安装安卓应用 Hello&#xff0c;大家好啊&#xff01;随着移动应用的普及&#xff0c;越来越多的用户希望在个人电脑上运行安卓应用&#xff0c;以便更好地整合工作和生活中的信息。特别是在华为L410终端和麒麟KOS操作系统上&a…

在线教程|零门槛部署 Llama 3,70B 版本只占 1.07G 存储空间,新用户免费体验 8B 版本

4 月 18 日&#xff0c;Meta 宣布开源 Llama 3&#xff0c;这个号称「迄今为止最好的开源大模型」一经发布&#xff0c;立刻引爆科技圈&#xff01; 发布当天恰逢斯坦福大学教授、AI 顶尖专家吴恩达的生日&#xff0c;作为 AI 开源倡导者&#xff0c;他激动地发文表示&#xff…

亿图图示使用教程

亿图图示是一款强大的图形绘制工具&#xff0c;可以用于创建流程图、思维导图、组织结构图等多种类型的图表。下面是一些基本的使用教程&#xff1a; 下载和安装&#xff1a;首先&#xff0c;你需要在官方网站上下载亿图图示的安装包&#xff0c;然后按照提示进行安装。 新建项…

Tesla P4终于在DL580 Gen9上面跑起来了!

正文共&#xff1a;666 字 11 图&#xff0c;预估阅读时间&#xff1a;1 分钟 跌跌撞撞&#xff0c;从Tesla M4终于走到了Tesla P40&#xff0c;显存从4 GB到8 GB&#xff0c;最后再到24 GB&#xff0c;真是不容易。 回顾一下&#xff0c;Tesla M4是最早开始搞的&#xff0c;经…

CI/CD:基于kubernetes的Gitlab搭建

1. 项目目标 &#xff08;1&#xff09;熟悉使用k8s环境搭建Gitlab &#xff08;2&#xff09;熟练应用Gitlab基本配置 2. 项目准备 2.1. 规划节点 主机名 主机IP 节点规划 k8s-master 10.0.1.1 kube_master k8s-node1 10.0.1.2 kube_node k8s-node2 10.0.1.3 k…

【AI心理咨询测评】一年后,AI心理咨询的路还有多远?——5例AI模型心理咨询能力测评对比

前言 随着GPT横空出世&#xff0c;AI心理健康的市场开始逐渐被开拓。有人联想到线上以GPT作为基础&#xff0c;开发可线上心理咨询的AI&#xff0c;例如国内的聆心智能。然而&#xff0c;这一想法也遭到了无数人的质疑&#xff1a;“连聊天都尚不能很好完成&#xff0c;去做心…

第⑰讲:Ceph集群各组件的配置参数调整

文章目录 1.Ceph集群各组件的配置文件1.1.Ceph各组件配置方式1.2.ceph临时查看、修改配置参数的方法 2.调整Monitor组件的配置参数删除Pool资源池2.1.临时调整配置参数2.2.永久修改配置参数 1.Ceph集群各组件的配置文件 1.1.Ceph各组件配置方式 Ceph集群中各个组件的默认配置…

【Jenkins】持续集成与交付 (一):深入理解什么是持续集成?

🟣【Jenkins】持续集成与交付 (一):深入理解什么是持续集成? 1、软件开发生命周期与持续集成2、 持续集成的流程3、持续集成的好处4、Jenkins的应用实践5、结语💖The Begin💖点点关注,收藏不迷路💖 1、软件开发生命周期与持续集成 软件开发生命周期(SDLC)是指软…

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

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

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

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

【事业单位专场】联考、省市统考、单独招考

一、考编概述 1、事业单位类别 事业单位是指由国家出资或委托管理的公共机构&#xff0c;其主要职能是为社会提供公共服务。在中国&#xff0c;事业单位覆盖了科研、教育、文化和卫生等多个领域&#xff0c;并且有着不同的类型。以下是一些主要的分类&#xff1a; 教育事业单…

NLP(10)--TFIDF优劣势及其应用Demo

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 TF*IDF&#xff1a; 优势&#xff1a; 可解释性好 可以清晰地看到关键词 即使预测结果出错&#xff0c;也很容易找到原因 计算速度快 分词本身占耗时最多&#xff0c;其余为简单统计计算 对标注数据依赖小 可以使用无标注语…