图中灰色的无法实现热更新,而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
使用建议
-
访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
-
如果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方法,不同的是类型映射。
映射到delegate
这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码(如果没生成代码会抛InvalidCastException异常)。
delegate要怎样声明呢? 对于function的每个参数就声明一个输入类型的参数。 多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。
delegate的使用就更简单了,直接像个函数那样用就可以了
映射到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.事件加减替换 不要把传入的委托往事件里存