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.rhkb.cn/news/316389.html

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

相关文章

如何消除浏览器SmartScreen对网站“不安全”提示?

面对互联网时代用户对网站安全性和可信度的严苛要求&#xff0c;网站运营者时常遭遇Microsoft Defender SmartScreen&#xff08;SmartScreen&#xff09;提示网站不安全的困扰。本文将剖析SmartScreen判定网站不安全的原因&#xff0c;并为运营者提供应对策略&#xff0c;以恢…

机器学习:基于Sklearn、XGBoost框架,使用逻辑回归、支持向量机和XGBClassifier来诊断并预测一个人是否患有自闭症

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

二、VLAN原理和配置

vlan不是协议&#xff0c;是一个技术&#xff0c;虚拟局域网技术&#xff0c;基于802.1q协议。 vlan&#xff08;虚拟局域网&#xff09;&#xff0c;将一个物理的局域网在逻辑上划分成多个广播域的技术。 目录 1.冲突域和广播域 概念 范围 2.以太网帧格式 3.以太网帧封装…

Facebook的声音:听见社交媒体的心跳

社交媒体如今已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为其中的佼佼者&#xff0c;承载着数以亿计的用户的交流、分享和连接。在这个信息爆炸的时代&#xff0c;Facebook的声音就像是社交媒体的心跳&#xff0c;传递着无数个体的情感、思想和生活。本文…

Python-VBA函数之旅-object基类(非函数)

目录 一、object基类的常见应用场景 二、object基类使用注意事项 三、如何用好object基类&#xff1f; 1、object基类&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;神奇夜光杯-CSDN博客 一、object基类的…

基于H.264的RTP打包中的组合封包以及分片封包结构图简介及抓包分析

H.264视频流的RTP封装类型分析&#xff1a; 前言&#xff1a; NULL Hearder简介(结构如下)&#xff1a; ---------------|0|1|2|3|4|5|6|7|--------|F|NRI| Type |--------------- F&#xff1a;forbidden_zero_bit&#xff0c; 占1位&#xff0c;在 H.264 规范中规定了这…

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…

2024年武汉东湖高新水测成绩出来了

本次水测通过人员有1016名&#xff0c;通过的人数还是蛮多的&#xff0c;水测其实没有大家想象的那么难&#xff0c;现在职称评审都是水测线下评审的模式进行的。 水平测试分机考&#xff0c;笔试和面试答辩&#xff0c;各区随机安排选其一&#xff0c;机考就相当于考驾照刷题&…

自动化测试web库(元素定位、元素操作、浏览器操作)

按照谷歌浏览器 Chrome &#xff1a;https://googlechromelabs.github.io/chrome-for-testing/ Chrome使用技巧&#xff1a; 1、找到自己想要的标签 打开检查&#xff0c;点击箭头&#xff0c;再点击你想要点击的地方 2、直接在浏览器上查询&#xff0c;看看是否查询成功 可…

【算法刷题 | 贪心算法08】4.29(划分字母区间、合并区间)

文章目录 14.划分字母区间14.1题目14.2解法&#xff1a;贪心14.2.1贪心思路14.2.2代码实现 15.合并区间15.1题目15.2解法&#xff1a;贪心15.2.1贪心思路15.2.2代码实现 14.划分字母区间 14.1题目 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一…

msf练习

一、什么是msfvenom&#xff1f; msfvenom是msf中的一个独立的负载生成器&#xff0c;它可以利用msf中的payloads和encoders来生成各种格式的木马文件&#xff0c;并在目标机上执行&#xff0c;配合meterpreter在本地监听上线。msfvenom是msfpayload和msfencode的结合体&#…

【C++】深入了解C++内存管理

个人主页&#xff1a;救赎小恶魔 欢迎大家来到小恶魔频道 好久不见&#xff0c;甚是想念 今天我们要深入讲述类与对象的初始化列表以及隐式类型转换 目录 1.C的内存分布 2.C/C言中动态内存管理方式 1.C语言的管理方式 2.C的管理方式 new delete 3.operator new与ope…

IntelliJ IDEA - Auto filling Java call arguments 插件教程

首先&#xff0c;安装该插件&#xff0c;下载完毕后重启 IDEA 当 userService 中方法需要参数的时候&#xff0c;我们一般都是自己手动写这些参数&#xff0c;是很费劲的。因此就出现了一个插件解决这类问题 Auto filling Java call arguments 光标点击需要填写参数的位置 Alt …

【酱浦菌-爬虫项目】python爬取彼岸桌面壁纸

首先&#xff0c;代码导入了两个库&#xff1a;requests和parsel。这些库用于处理HTTP请求和解析HTML内容。 然后&#xff0c;它定义了一个变量url&#xff0c;指向网站’樱花2024年4月日历风景桌面壁纸_高清2024年4月日历壁纸_彼岸桌面’。 接下来&#xff0c;设置了一个HTT…

变革 Perplexica:AI驱动的问答搜索引擎

Perplexica是一个开源的人工智能搜索工具&#xff0c;也可以说是一款人工智能搜索引擎&#xff0c;它深入互联网以找到答案。受Perplexity AI启发&#xff0c;它是一个开源选择&#xff0c;不仅可以搜索网络&#xff0c;还能理解您的问题。它使用先进的机器学习算法&#xff0c…

帕累托森林李朝政博士受聘「天工开物开源基金会」专家顾问

导语&#xff1a; 开源铸造了当前最前沿的科技引擎。开源驱动了软件生态&#xff0c;也以指数级速度驱动硬件生态。 3月中旬&#xff0c;天工开物开源基金会授予李朝政博士专家顾问&#xff0c;表彰他积极推动参与中国智能软件生态的建设&#xff0c;期待一起共筑未来新生态。…

Axure实现tab页面切换功能

1. 实现效果 2. 实现原理 创建两个标签&#xff0c;并实现点击时选中状态点击时&#xff0c;设置面板状态 3. 实现步骤 3.1 实现可切换的标签 在页面上拖拽两个矩形作为两个tab标签&#xff0c;并命名 tab1 和 tab2 设置每个矩形的边框显示&#xff0c;只显示下边框即可 …

pyQt5 和 Qt Designer 实现登录注册案例

Qt Designer 设计页面: 通过 PyQt5 手写 1. 先引入用到的库 from PyQt5.QtWidgets import * import sys 2. 创建应用,窗口, 设置窗口 # 创建应用 app QApplication(sys.argv) # 创建窗口 w QWidget()# 设置窗口标题 w.setWindowTitle("注册登录")# 展示 w.sho…

使用STM32CubeMX对STM32F4进行串口配置

目录 1. 配置1.1 Pin脚1.2 RCC开启外部晶振1.3 时钟1.4 串口配置 2. 代码2.1 默认生成代码2.1 开启串口中断函数2.3 接收中断2.4 接收回调函数2.5 增加Printf 的使用 1. 配置 1.1 Pin脚 1.2 RCC开启外部晶振 1.3 时钟 外部使用8MHz晶振 开启内部16MHz晶振 使用锁相环 开启最高…

Android CalendarView助你打造精美的Android日历应用

Android CalendarView助你打造精美的Android日历应用 1. 引言 移动应用中的日历功能对于用户来说至关重要&#xff0c;它不仅是时间管理的工具&#xff0c;还能帮助用户记录重要事件和安排活动。因此&#xff0c;一个高效、易用的日历控件对于移动应用的成功至关重要。 传统…