一.热更新方案简介

  在Unity游戏工程中,C#代码(编译型语言)资源和Resources文件夹下的资源打包后都不可以更改,因此这部分内容不能进行热更新,而lua代码(解释型语言)逻辑不需要进行预编译再运行,可以在游戏运行过程中进行修改,AB包资源也可以在游戏运行过程中下载解压缩并使用其中的资源。因此客户端可以在启动时检验服务器端的AB包资源是否有更新,如果有更新先下载更新,将lua代码资源和其他更新资源打包为AB包放在服务器端,客户端下载后直接在运行过程中解压缩并使用更新资源,实现了客户端不中断运行即完成更新的目的,也就是热更新。

二.xlua热更新方案简介

  xlua框架提供了C#和lua相互调用的功能及Hotfix热补丁的功能,主要目的是方便我们将纯C#工程在不重做的情况下改造成具备热更新功能的工程。

三.准备工作--说明:使用的Unity版本为2019.4.18f1c1,导入的xlua为2021年4月4日从GitHub上直接clone的工程文件,没有下载release版本。

  1.xlua框架导入

    在GitHub上搜索xlua,找到腾讯的xlua项目,下载项目的压缩包。

    下载完成后解压,发现下载的是一个Unity工程文件:

    在工程文件中,核心代码是Assets目录下的Plugins和XLua这两个文件夹中的内容,将其复制到自己的工程文件中即可。

    将这两个文件夹复制到自己的工程中后稍等一会儿,就会发现在菜单栏中Windows菜单选项左侧出现了XLua菜单选项,没有报错的话说明成功导入。

  2.AB包工具导入

    在Unity中通过PackageManager导入AB包工具,导入方法详见:热更新基础--AssetBundle学习笔记

  3.AB包管理器

    为了方便加载AB包,我们可以制作一个AB包的管理器脚本,脚本详见:热更新基础--AssetBundle学习笔记

四.C#调用lua

  1.lua解析器

  1. void Start()
  2. {
  3. //Lua解析器,能够在Unity中执行Lua
  4. LuaEnv env = new LuaEnv();
  5.  
  6. //执行单行Lua语言,使用DoString成员方法
  7. env.DoString("print('hello world!')");
  8.  
  9. //执行lua脚本,一般都是直接调用lua语言的require关键字执行lua脚本
  10. //默认寻找脚本的路径是在Resources下
  11. //lua后缀Unity不能识别,需要将lua文件添加上.txt以便Unity识别
  12. env.DoString("require('Main')");
  13.  
  14. //清除lua中没有手动释放的对象,相当于垃圾回收,一般在帧更新中定时执行或者切换场景时执行
  15. env.Tick();
  16.  
  17. //销毁lua解析器,但是一般不会销毁,因为最好保持解析器的唯一性以节约性能
  18. env.Dispose();
  19. }

  2.lua文件加载重定向,即更改使用require关键字加载lua文件时寻找lua文件的位置(默认lua脚本在Resources下才能识别,这和热更新的目的冲突)

  1. void Start()
  2. {
  3. LuaEnv env = new LuaEnv();
  4.  
  5. //使用AddLoader方法添加重定向,即自定义文件加载的规则
  6. //参数为一个委托,这个委托有一个ref参数,自动执行传入require执行的脚本文件名,在委托中拼接好完整的路径;委托的返回值为lua文件转化出的字节数组
  7. //添加委托后,委托在执行require语句时自动执行
  8. env.AddLoader(MyCustomLoader);
  9.  
  10. //使用require语句执行lua文件,会自动先调用添加的重定向方法寻找lua文件,如果找不到再到默认路径Resources下寻找
  11. env.DoString("require('Main')");
  12. }
  13.  
  14. /// <summary>
  15. /// 重定向方法
  16. /// </summary>
  17. /// <param name="filePath">文件名</param>
  18. /// <returns></returns>
  19. private byte[] MyCustomLoader(ref string filePath)
  20. {
  21. //拼接完整的lua文件所在路径
  22. string path = Application.dataPath + "/Lua/" + filePath + ".lua";
  23. Debug.Log(path);
  24. //判断文件是否存在,存在返回读取的文件字节数组,不存在打印提醒信息,返回null
  25. if (File.Exists(path))
  26. {
  27. return File.ReadAllBytes(path);
  28. }
  29. else
  30. {
  31. Debug.Log("MyCustomLoader重定向失败,文件名为" + filePath);
  32. }
  33. return null;
  34. }

  3.lua解析器管理器:对lua解析器的进一步封装以方便使用

  1. /// <summary>
  2. /// lua管理器,对lua解析器的进一步封装,保证lua解析器的唯一性
  3. /// </summary>
  4. public class LuaManager
  5. {
  6. //单例模块
  7. private static LuaManager instance;
  8. public static LuaManager Instance
  9. {
  10. get
  11. {
  12. if (instance == null)
  13. instance = new LuaManager();
  14. return instance;
  15. }
  16. }
  17. private LuaManager()
  18. {
  19. //在构造方法中就为唯一的lua解析器赋值
  20. luaEnv = new LuaEnv();
  21. //加载lua脚本重定向
  22. //重定向到lua文件夹下
  23. luaEnv.AddLoader((ref string filePath) =>
  24. {
  25. //拼接完整的lua文件所在路径
  26. string path = Application.dataPath + "/Lua/" + filePath + ".lua";
  27. //判断文件是否存在,存在返回读取的文件字节数组,不存在打印提醒信息,返回null
  28. if (File.Exists(path))
  29. {
  30. return File.ReadAllBytes(path);
  31. }
  32. else
  33. {
  34. Debug.Log("MyCustomLoader重定向失败,文件名为" + filePath);
  35. }
  36. return null;
  37. });
  38. //重定向加载AB包中的lua脚本
  39. luaEnv.AddLoader((ref string filePath) =>
  40. {
  41. /*//加载AB包
  42. string path = Application.streamingAssetsPath + "/lua";
  43. AssetBundle bundle = AssetBundle.LoadFromFile(path);
  44.  
  45. //加载lua文件,返回
  46. TextAsset texts = bundle.LoadAsset<TextAsset>(filePath + ".lua");
  47. //返回加载到的lua文件的byte数组
  48. return texts.bytes;*/
  49.  
  50. /*//使用AB包管理器加载,异步加载
  51. byte[] luaBytes = null;
  52. AssetBundleManager.Instance.LoadResAsync<TextAsset>("lua", filePath + ".lua", (lua) =>
  53. {
  54. if (lua != null)
  55. luaBytes = lua.bytes;
  56. else
  57. Debug.Log("重定向失败,从AB包加载lua文件失败");
  58. });
  59. return luaBytes;*/
  60.  
  61. //使用AB包管理器加载,同步加载
  62. return AssetBundleManager.Instance.LoadRes<TextAsset>("lua", filePath + ".lua").bytes;
  63. });
  64. }
  65.  
  66. //持有一个唯一的lua解析器
  67. private LuaEnv luaEnv;
  68.  
  69. //luaEnv中的大G表,提供给外部调用
  70. public LuaTable Global
  71. {
  72. get
  73. {
  74. //校验一下instance是否是null,避免dispose后无法获取的情况出现
  75. if (instance == null)
  76. instance = new LuaManager();
  77. return luaEnv.Global;
  78. }
  79. }
  80.  
  81. /// <summary>
  82. /// 执行单句lua代码
  83. /// </summary>
  84. /// <param name="luaCodeString"></param>
  85. public void DoString(string luaCodeString)
  86. {
  87. luaEnv.DoString(luaCodeString);
  88. }
  89. /// <summary>
  90. /// 执行lua文件的代码,直接提供文件名即可执行文件,不需要再书写lua的require语句,在方法内部拼接lua语句
  91. /// </summary>
  92. /// <param name="fileName">lua文件名</param>
  93. public void DoLuaFile(string fileName)
  94. {
  95. luaEnv.DoString("require('" + fileName + "')");
  96. }
  97. /// <summary>
  98. /// 释放解析器
  99. /// </summary>
  100. public void Tick()
  101. {
  102. luaEnv.Tick();
  103. }
  104. /// <summary>
  105. /// 销毁解析器
  106. /// </summary>
  107. public void Dispose()
  108. {
  109. luaEnv.Dispose();
  110. //销毁解析器后将lua解析器对象和单例变量都置空,下次调用时会自动调用构造函数创建lua解析器,以免报空
  111. luaEnv = null;
  112. instance = null;
  113. }
  114. }

  4.访问变量

  1. void Start()
  2. {
  3. LuaManager.Instance.DoLuaFile("Main");
  4.  
  5. //使用_G表获取luaenv中的global变量值
  6. Debug.Log(LuaManager.Instance.Global.Get<int>("testNumber"));
  7. Debug.Log(LuaManager.Instance.Global.Get<bool>("testBool"));
  8. Debug.Log(LuaManager.Instance.Global.Get<float>("testFloat"));
  9.  
  10. //使用_G表修改luaenv中的global变量值
  11. LuaManager.Instance.Global.Set("testBool",false);
  12. Debug.Log(LuaManager.Instance.Global.Get<bool>("testBool"));
  13.  
  14. //不能直接获取和设置本地变量
  15. }

  5.访问函数,使用委托接收

  1. //自定义委托,对于有参数和返回值的委托,必须加上特性[CSharpCallLua],否则无法处理,无参无返回值的委托不需要
  2. //特性起作用还需要在Unity中生成脚本
  3. [CSharpCallLua]
  4. public delegate void CustomCall(int a);
  5. //自定义含有out或者ref参数的委托用于接收多返回值函数,out和ref根据需要选择,都可以用于接收多返回值
  6. [CSharpCallLua]
  7. public delegate int CustomCall2(out int a, out int b);
  8. [CSharpCallLua]
  9. public delegate void CustomCall3(params int[] args);
  1. void Start()
  2. {
  3. LuaManager.Instance.DoLuaFile("Main");
  4.  
  5. //获取函数,使用委托存储
  6. UnityAction npnr = LuaManager.Instance.Global.Get<UnityAction>("NoParamNoReturn");
  7. npnr();
  8.  
  9. //xlua提供了获取函数的方法,但是不推荐使用,推荐使用Unity或者C#提供的委托或者自定义委托存储
  10. LuaFunction luaFun = LuaManager.Instance.Global.Get<LuaFunction>("NoParamNoReturn");
  11. luaFun.Call();
  12.  
  13. //有参无返回值
  14. //使用自定义的委托需要声明特性且在Unity中生成代码
  15. CustomCall hpnr = LuaManager.Instance.Global.Get<CustomCall>("HaveParamNoReturn");
  16. hpnr(2);
  17.  
  18. //使用C#提供的委托存储,不用声明特性
  19. Action<int> hpnr2 = LuaManager.Instance.Global.Get<Action<int>>("HaveParamNoReturn");
  20. hpnr2(2);
  21.  
  22. //多返回值
  23. //不能使用系统自带的委托,多返回值需要自定义委托
  24. CustomCall2 mr = LuaManager.Instance.Global.Get<CustomCall2>("ManyReturns");
  25. int m;
  26. int n;
  27. int p = mr(out m, out n);
  28. Debug.Log(m + "-" + n + "-" + p);
  29.  
  30. //变长参数
  31. CustomCall3 vp = LuaManager.Instance.Global.Get<CustomCall3>("VariableParams");
  32. vp(1, 2, 3, 4, 5);
  33. }

  6.表映射为List或者Dictionary

  1. void Start()
  2. {
  3. LuaManager.Instance.DoLuaFile("Main");
  4.  
  5. //得到List
  6. List<int> list = LuaManager.Instance.Global.Get<List<int>>("ListTable");
  7. foreach (int i in list)
  8. {
  9. Debug.Log(i);
  10. }
  11.  
  12. //得到Dictionary
  13. Dictionary<string, int> dic = LuaManager.Instance.Global.Get<Dictionary<string, int>>("DictionaryTable");
  14. foreach (KeyValuePair<string,int> pair in dic)
  15. {
  16. Debug.Log(pair.Key + ":" + pair.Value);
  17. }
  18. }

  7.表映射到类对象

  1. /// <summary>
  2. /// 声明一个类来和lua中的类进行映射,变量名称必须和lua中的对应类一致,但是不必一一对应,映射时会自动丢弃找不到的内容
  3. /// </summary>
  4. public class CallLuaClass
  5. {
  6. public string name;
  7. public int age;
  8. public int sex;
  9. public UnityAction eat;
  10. }
  1. void Start()
  2. {
  3. LuaManager.Instance.DoLuaFile("Main");
  4.  
  5. //获得类对象
  6. CallLuaClass clc = LuaManager.Instance.Global.Get<CallLuaClass>("testClass");
  7. Debug.Log(clc.name + "-" + clc.age + "-" + clc.sex);
  8. clc.eat();
  9. }

  8.表映射到接口

  1. /// <summary>
  2. /// 使用一个接口接收表的映射,但是接口中的变量不允许被赋值,这里使用属性
  3. /// 必须加上特性[CSharpCallLua]
  4. /// </summary>
  5. [CSharpCallLua]
  6. public interface ICSharpCallLua
  7. {
  8. string name { get; set; }
  9. int age { get; set; }
  10. int sex { get; set; }
  11. Action eat { get; set; }
  12. }
  1. void Start()
  2. {
  3. LuaManager.Instance.DoLuaFile("Main");
  4.  
  5. //得到接口对象
  6. ICSharpCallLua icscl = LuaManager.Instance.Global.Get<ICSharpCallLua>("testClass");
  7. Debug.Log(icscl.name + "-" + icscl.age + "-" + icscl.sex);
  8. icscl.eat();
  9. }

  注意:之前实现的所有拷贝都是引用拷贝,也就是c#中的拷贝值发生改变,lua代码不受影响,但是接口的拷贝是引用拷贝,也就是改变C#中的拷贝的值,lua中的值也发生了改变。

  9.映射到luaTable类

  1. void Start()
  2. {
  3. LuaManager.Instance.DoLuaFile("Main");
  4.  
  5. //得到LuaTable,对应lua中的table。
  6. //本质上Global也是LuaTable类型的变量,使用方法和之前通过Global获取各种变量函数等方法相同
  7. //不推荐使用LuaTable和LuaFunction,效率低
  8. //LuaTable的拷贝是引用拷贝
  9. LuaTable table = LuaManager.Instance.Global.Get<LuaTable>("testClass");
  10. Debug.Log(table.Get<int>("age"));
  11. Debug.Log(table.Get<string>("name"));
  12. Debug.Log(table.Get<int>("sex"));
  13. table.Get<LuaFunction>("eat").Call();
  14. }

  LuaTable类对应Lua中的表,本质上Global变量也是LuaTable类型,所以LuaTable的使用方法和之前讲的通过Global获取各种变量的方法相同。LuaTable和LuaFunction使用后记得调用dispose方法释放垃圾,否则容易造成内存泄漏。

五.lua调用C#

  1.在Unity中无法直接运行lua,因此需要使用C#脚本作为lua脚本的主入口启动lua脚本的运行,接下来都不再赘述这一步骤,所有的lua代码也都在这个特定的lua脚本中编写。

  1. public class Main : MonoBehaviour
  2. {
  3. private void Start()
  4. {
  5. //在这个脚本中启动特定的lua脚本,接下来的lua代码都在这个脚本中编写
  6. LuaManager.Instance.DoLuaFile("Main");
  7. }
  8. }

  Main.lua这个脚本作为lua脚本的入口,接下来再在这个Main.lua脚本中调用其他脚本。

  1. require("CallClass")

  2.创建类对象

  1. --lua中调用C#脚本
  2.  
  3. --创建类对象
  4. --Unity中的类如GameObjectTransform等类都存储在CS表中
  5. --使用CS.命名空间.类名的方式调用Unity中的类
  6. local obj1 = CS.UnityEngine.GameObject("使用lua创建的第一个空物体")

  1. --lua中调用C#脚本
  2.  
  3. --创建类对象
  4. --Unity中的类如GameObjectTransform等类都存储在CS表中
  5. --使用CS.命名空间.类名的方式调用Unity中的类
  6. --每次都写命名空间太麻烦,可以定义全局变量先把类存储起来,也能节约性能
  7. GameObject = CS.UnityEngine.GameObject
  8. local obj = GameObject("movin")
  9.  
  10. --使用点来调用静态方法
  11. local obj2 = GameObject.Find("movin")
  12.  
  13. --使用.来调用对象中的成员变量
  14. Log = CS.UnityEngine.Debug.Log
  15. Log(obj.transform.position)
  16.  
  17. Vector3 = CS.UnityEngine.Vector3
  18. --使用对象中的成员方法必须使用:调用
  19. obj.transform:Translate(Vector3.right)
  20. Log(obj.transform.position)
  21.  
  22. --自定义类的调用
  23. --直接使用CS点的方式调用
  24. local customClass = CS.CustomClass()
  25. customClass.name = "movin"
  26. customClass:Eat()
  27.  
  28. --有命名空间的类再点一层
  29. local customClassInNamespace = CS.CustomNamespace.CustomClassInNamespace()
  30. customClassInNamespace.name = "movin2"
  31. customClassInNamespace:Eat()
  32.  
  33. --继承了mono的类不能new出来,只能获取组件
  34. --xlua提供了typeof的方法得到类的Type
  35. --自定义的脚本组件直接用CS点出来即可
  36. obj:AddComponent(typeof(CS.Main))
  37. --系统自带的脚本一般在UnityEngine命名空间下
  38. obj:AddComponent(typeof(CS.UnityEngine.Rigidbody))
  1. /// <summary>
  2. /// 自定义类
  3. /// </summary>
  4. public class CustomClass
  5. {
  6. public string name;
  7. public void Eat()
  8. {
  9. Debug.Log(name + "在吃饭");
  10. }
  11. }
  12.  
  13. /// <summary>
  14. /// 自定义类,包裹在命名空间中
  15. /// </summary>
  16. namespace CustomNamespace
  17. {
  18. public class CustomClassInNamespace
  19. {
  20. public string name;
  21. public void Eat()
  22. {
  23. Debug.Log(name + "在吃饭");
  24. }
  25. }
  26. }

  3.使用枚举

  1. --调用枚举
  2.  
  3. --调用Unity提供的枚举
  4. --Unity提供的枚举一般在UnityEngine
  5.  
  6. PrimitiveType = CS.UnityEngine.PrimitiveType
  7. GameObject = CS.UnityEngine.GameObject
  8.  
  9. local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)
  10.  
  11. --调用自定义的枚举
  12. E_CustomEnum = CS.E_CustomEnum
  13.  
  14. Log = CS.UnityEngine.Debug.Log
  15. Log(E_CustomEnum.Idle)
  16.  
  17. --使用_CastFrom方法进行枚举类型转换,可以从数字转换成枚举或者字符串转换成枚举
  18. Log(E_CustomEnum.__CastFrom(1))
  19. Log(E_CustomEnum.__CastFrom("Atk"))

  4.使用List和Dictionary

  1. local CustomClass = CS.CustomClass
  2. local Log = CS.UnityEngine.Debug.Log
  3.  
  4. --调用数组,使用C#的数组相关API,不要使用lua的方法
  5. obj = CustomClass();
  6. Log(obj.array.Length)
  7.  
  8. --遍历数组,注意从0length-1,按照C#的下标遍历不是lua的
  9. for i=0,obj.array.Length-1 do
  10. Log(obj.array[i])
  11. end
  12.  
  13. Log("******************")
  14. --创建数组,利用数组类ArrayCreateInstance静态方法创建数组
  15. --参数意思:创建数组中存储元素的类型、创建的数组的长度
  16. local arr = CS.System.Array.CreateInstance(typeof(CS.System.Int32),5)
  17. Log(arr.Length)
  18. Log(arr[1])
  19.  
  20. Log("******************")
  21. --调用List,调用成员方法用:
  22. obj.list:Add('M')
  23. for i = 0,obj.list.Count-1 do
  24. Log(obj.list[i])
  25. end
  26.  
  27. Log("******************")
  28. --创建List
  29. --老版,方法很麻烦
  30. local list2 = CS.System.Collections.Generic["List`1[System.String]"]()
  31. list2:Add("abcde")
  32. Log(list2[0])
  33. --新版 版本>v2.1.12 先创建一个类,再实例化出来list
  34. local List_String = CS.System.Collections.Generic.List(CS.System.String)
  35. local list3 = List_String()
  36. list3:Add("aaaaaaaaaa")
  37. Log(list3[0])
  38.  
  39. Log("******************")
  40. --调用dic
  41. obj.dic:Add(1,"abc")
  42. obj.dic:Add(2,"def")
  43. --遍历
  44. for k,v in pairs(obj.dic) do
  45. Log(k.."--"..v)
  46. end
  47.  
  48. Log("******************")
  49. --创建dic
  50. local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
  51. local dic2 = Dic_String_Vector3()
  52. dic2:Add("abc",CS.UnityEngine.Vector3.right)
  53. dic2:Add("def",CS.UnityEngine.Vector3.up)
  54.  
  55. Log(dic2["abc"]) --在lua中创建的字典使用这种方式得不到值,这句代码打印出的结果是空值
  56. Log(dic2:get_Item("abc")) --在lua中自己创建的字典使用get_Item方法得到值
  57. dic2:set_Item("abc",CS.UnityEngine.Vector3.left) --同样地,通过set_Item方法设置字典地值
  58. Log(dic2:get_Item("abc"))
  59. print(dic2:TryGetValue("abc")) --也可以通过TryGetValue方法获取值
  60.  
  61. for k,v in pairs(dic2) do
  62. print(k,v)
  63. end
  1. /// <summary>
  2. /// 自定义类
  3. /// </summary>
  4. public class CustomClass
  5. {
  6. public string[] array = { "a","b","c","d","e","f","g","h" };
  7. public List<char> list = new List<char>{ 'A','B','C','D','E','F','G','H','I','J' };
  8. public Dictionary<int, string> dic = new Dictionary<int, string>();
  9. }

  5.使用C#拓展方法

  1. CustomClass = CS.CustomClass
  2.  
  3. --使用成员方法
  4. local customClass = CustomClass()
  5. customClass.name = "movin"
  6. customClass:Eat()
  7.  
  8. --使用拓展方法,拓展方法一定是静态方法,但是调用时和成员方法一样的调用方式
  9. --在定义拓展方法的工具类前一定加上特性[LuaCallCSharp],并且生成代码
  10. customClass:Move()
  1. /// <summary>
  2. /// 自定义类
  3. /// </summary>
  4. public class CustomClass
  5. {
  6. public string name;
  7. public void Eat()
  8. {
  9. Debug.Log(name + "在吃饭");
  10. }
  11. }
  12. /// <summary>
  13. /// 工具类,定义拓展方法
  14. /// </summary>
  15. [LuaCallCSharp]
  16. public static class Tools
  17. {
  18. public static void Move(this CustomClass cc)
  19. {
  20. Debug.Log(cc.name + "在移动");
  21. }
  22. }

  

  建议:要在lua中使用的C#类都可以加上[LuaCallCSharp]特性,这样预先将代码生成,可以提高Lua访问C#类的性能。

  6.使用含有ref和out参数的函数

  1. CustomClass = CS.CustomClass
  2. local obj = CustomClass()
  3.  
  4. --ref参数,使用多返回值形式接收
  5. --如果函数有返回值,这个返回值是多返回值的第一个
  6. --参数数量不够,会默认使用默认值补位
  7. local a,b,c = obj:RefFun(1,0,0,1)
  8. print(a,b,c)
  9.  
  10. --out参数,还是以多返回值的形式接收
  11. --out参数不需要传递值
  12. local a,b,c = obj:OutFun(23,24)
  13. print(a,b,c)
  14.  
  15. --综合来看
  16. --从返回值上看,refout都会以多返回值的形式返回,原来如果有返回值的话原来的返回值是多返回值中的第一个
  17. --从参数看,ref参数需要传递,out参数不需要传递
  18. local a,b,c = obj:RefOutFun(12,23)
  19. print(a,b,c)
  1. /// <summary>
  2. /// 自定义类
  3. /// </summary>
  4. public class CustomClass
  5. {
  6. public int RefFun(int a ,ref int b,ref int c,int d)
  7. {
  8. b = a + d;
  9. c = a - d;
  10. return 100;
  11. }
  12. public int OutFun(int a,out int b,out int c,int d)
  13. {
  14. b = a;
  15. c = d;
  16. return 200;
  17. }
  18. public int RefOutFun(int a,out int b,ref int c)
  19. {
  20. b = a * 10;
  21. c = a * 20;
  22. return 200;
  23. }
  24. }

  7.使用重载函数

  1. CustomClass = CS.CustomClass
  2. local customClass = CustomClass()
  3.  
  4. --使用重载函数
  5. --lua支持调用C#的重载函数
  6. --lua中的数值类型只有number,所以对C#中多精度的重载函数支持不好,使用时可能出现问题
  7. --如第四个重载函数调用结果为0(应该是11.4),所以应避免这种情况
  8. print(customClass:Calc())
  9. print(customClass:Calc(1))
  10. print(customClass:Calc(2,3))
  11. print(customClass:Calc(1.4))
  12.  
  13. --解决重载函数含糊的问题(效率低,仅作了解)
  14. --运用反射
  15. local m1 = typeof(CustomClass):GetMethod("Calc",{typeof(CS.System.Int32)})
  16. local m2 = typeof(CustomClass):GetMethod("Calc",{typeof(CS.System.Single)})
  17. --通过xlua提供的tofunction方法将反射得到的方法信息转化为函数
  18. local f1 = xlua.tofunction(m1)
  19. local f2 = xlua.tofunction(m2)
  20. --再次调用函数,非静态方法需要传入对象
  21. print(f1(customClass,10))
  22. print(f2(customClass,1.4))
  1. /// <summary>
  2. /// 自定义类
  3. /// </summary>
  4. public class CustomClass
  5. {
  6. public int Calc()
  7. {
  8. return 100;
  9. }
  10. public int Calc(int a,int b)
  11. {
  12. return a + b;
  13. }
  14. public int Calc(int a)
  15. {
  16. return a;
  17. }
  18. public float Calc(float a)
  19. {
  20. return a + 10;
  21. }
  22. }

  8.委托和事件

  1. local customClass = CS.CustomClass()
  2.  
  3. --委托中存储的是函数,声明函数存储到委托中
  4. local fun = function()
  5. print("函数fun")
  6. end
  7.  
  8. --委托中第一次添加函数使用=添加
  9. customClass.action = fun
  10. --委托中第二次添加函数使用+=,lua中不支持+=运算符,需要分开写
  11. customClass.action = customClass.action + fun
  12. --委托中也可以添加匿名函数
  13. customClass.action = customClass.action + function()
  14. print("临时函数")
  15. end
  16.  
  17. --使用点调用委托还是冒号调用委托都可以调用,最好使用冒号
  18. customClass:action()
  19.  
  20. print("********************")
  21.  
  22. --事件和委托的使用方法不一致(事件不能在外部调用)
  23. --使用冒号添加和删除函数,第一个参数传入加号或者减号字符串,表示添加还是修改函数
  24. --事件也可以添加匿名函数
  25. customClass:eventAction("+",fun)
  26. --事件不能直接调用,必须在C#中提供调用事件的方法,这里已经提供了DoEvent方法执行事件
  27. customClass:DoEvent()
  28. --同样地,事件不能直接清空,需要在C#中提供对应地方法
  1. /// <summary>
  2. /// 自定义类
  3. /// </summary>
  4. public class CustomClass
  5. {
  6. public UnityAction action;
  7. public event UnityAction eventAction;
  8. public void DoEvent()
  9. {
  10. if (eventAction != null)
  11. eventAction();
  12. }
  13. }

  9.特殊问题

  1. local customClass = CS.CustomClass()
  2.  
  3. --特殊问题一:得到二维数组指定位置元素的值
  4. --获取二维数组的长度
  5. print("行:"..customClass.array:GetLength(0))
  6. print("行:"..customClass.array:GetLength(1))
  7.  
  8. --不能通过C#的索引访问元素(array[0,0]或array[0][0])
  9. --使用数组提供的成员方法GetValue访问元素
  10. print(customClass.array:GetValue(0,0))
  11.  
  12. --遍历
  13. for i=0,customClass.array:GetLength(0)-1 do
  14. for j=0,customClass.array:GetLength(1)-1 do
  15. print(customClass.array:GetValue(i,j))
  16. end
  17. end
  18.  
  19. print("***********************")
  20.  
  21. --特殊问题二:lua中空值nilC#中空值null的比较
  22.  
  23. --往场景对象上添加一个脚本,存在就不加,不存在再加
  24. GameObject = CS.UnityEngine.GameObject
  25. Rigidbody = CS.UnityEngine.Rigidbody
  26.  
  27. local obj = GameObject("测试nil和null")
  28. local rigidbody = obj:GetComponent(typeof(Rigidbody))
  29. print(rigidbody)
  30. --校验空值,看是否需要添加脚本
  31. --nilnull并不相同,在lua中不能使用==进行判空,一定要使用Equals方法进行判断
  32. --这里如果rigidbody为空,可能报错,所以可以自己提供一个判空函数进行判空
  33. --这里为了笔记方便将函数定义在这里,这个全局函数最好定义在lua脚本启动的主函数Main
  34. function IsNull(obj)
  35. if obj == nil or obj:Equals(nil) then
  36. return true
  37. end
  38. return false
  39. end
  40. --使用自定义的判空函数进行判断
  41. if IsNull(rigidbody) then
  42. rigidbody = obj:AddComponent(typeof(Rigidbody))
  43. end
  44. print(rigidbody)
  45.  
  46. print("***********************")
  47.  
  48. --特殊问题三:让lua和系统类型能够相互访问
  49.  
  50. --对于自定义的类型,可以添加CSharpCallLuaLuaCallCSharp这两个特性使Lua和自定义类型能相互访问,但是对于系统类或第三方代码库,这种方式并不适用
  51. --为系统类或者第三方代码库加上这两个特性的写法比较固定,详情见C#代码
  1. /// <summary>
  2. /// 自定义类
  3. /// </summary>
  4. public class CustomClass
  5. {
  6. public int[,] array = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
  7.  
  8. //实现为系统类添加[CSharpCallLua]和[LuaCallCSharp]特性
  9. [CSharpCallLua]
  10. public static List<Type> csharpCallLuaList = new List<Type>()
  11. {
  12. //将需要添加特性的类放入list中
  13. typeof(UnityAction<float>),
  14. };
  15. [LuaCallCSharp]
  16. public static List<Type> luaCallCsharpList = new List<Type>()
  17. {
  18. typeof(GameObject),
  19. };
  20. }

  10.使用协程

  1. --xlua提供了一个工具表,要使用协程必须先调用这个工具表
  2. util = require("xlua.util")
  3.  
  4. GameObject = CS.UnityEngine.GameObject
  5. WaitForSeconds = CS.UnityEngine.WaitForSeconds
  6.  
  7. local obj = GameObject("Coroutine")
  8. local mono = obj:AddComponent(typeof(CS.LuaCallCSharp))
  9.  
  10. --被开启的协程函数
  11. fun = function()
  12. local a = 1
  13. while true do
  14. --lua中不能直接使用C#中的yield return返回
  15. --使用lua中的协程返回方法
  16. coroutine.yield(WaitForSeconds(1))
  17. print(a)
  18. a = a + 1
  19. if a>10 then
  20. --协程的关闭,必须要将开启的协程存储起来
  21. mono:StopCoroutine(startedCoroutine)
  22. end
  23. end
  24. end
  25.  
  26. --启动协程
  27. --写法固定,必须使用固定表的cs_generate方法把xlua方法处理成mono能够使用的协程方法
  28. startedCoroutine = mono:StartCoroutine(util.cs_generator(fun))

  11.使用泛型函数

    lua中没有泛型语法,对于C#中的泛型方法,可以直接传递参数(因为lua中不需要声明类型),但是这种写法并不是所有的泛型方法都支持,xlua只支持有约束且泛型作为参数的泛型函数,其他泛型函数不支持。如果要在lua中调用泛型函数,可以使用特定的语法。

  1. local tank = CS.UnityEngine.GameObject.Find("Tank")
  2.  
  3. --xlua提供了得到泛型函数的方法get_generic_method,参数第一个为类名,第二个为方法名
  4. local addComponentFunc = xlua.get_generic_method(CS.UnityEngine.GameObject,"AddComponent")
  5. --接着调用这个泛型方法,参数为泛型的类,得到一个新方法
  6. local addComponentFunc2 = addComponentFunc(CS.MonoForLua)
  7. --调用,第一个参数是调用的对象,如果有其他参数在后面传递
  8. addComponentFunc2(tank)

    使用限制:打包时如果使用mono打包,这种方式支持使用;如果使用il2cpp打包,泛型参数需要是引用类型或者是在C#中已经调用过的值类型。

热更新解决方案--xlua学习笔记的更多相关文章

  1. 热更新解决方案--tolua学习笔记

    一.tolua使用准备工作:从GitHub上下载tolua(说明:这篇笔记使用的Unity版本是2019.4.18f1c1,使用的tolua是2021年4月9日从GitHub上Clone的tolua工 ...

  2. 热更新基础--AssetBundle学习笔记

    一.简介 AssetBundle简称AB包,特定平台的资产压缩包(包括模型.贴图.预设体.音效.材质球等资产). 作用:Resources下的资源只读且打包后不可修改,而AB包存储位置自定,后期可以动 ...

  3. 热更新语言--lua学习笔记

    一.lua安装和编程环境搭建 lua语言可以在官网:http://luadist.org/下载安装包安装,编程IDE之前学习使用的是SciTE(https://www.cnblogs.com/movi ...

  4. 【腾讯Bugly干货分享】手游热更新方案xLua开源:Unity3D下Lua编程解决方案

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/2bY7A6ihK9IMcA0bOFyB-Q 导语 xL ...

  5. Bugly 多渠道热更新解决方案

    作者:巫文杰 Gradle使用productFlavors打渠道包的痛 有很多同学可能会采用配置productFlavors来打渠道包,主要是它是原生支持,方便开发者输出不同定制版本的apk,举个例子 ...

  6. unity3d热更新插件uLua学习整理

    前言 IOS不能热更新,不是因为不能用反射,是因为System.Reflection.Assembly.Load 无法使用System.Reflection.Emit 无法使用System.CodeD ...

  7. 手游热更新方案xLua开源:Unity3D下Lua编程解决方案

    C#下Lua编程支持 xLua为Unity. .Net. Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用. xLua的突破 xLua在功能.性能.易用 ...

  8. 《精通CSS:高级Web标准解决方案》学习笔记(上)

    鉴于国产CSS书籍基本都是辣鸡的现状,我在半年前动用某工作室的购书资金采购了一些技术书籍,这本广受好评的<精通CSS>也在其中.但是在阅读过后我深深的感觉到,如果说CSS本来已经是一种很琐 ...

  9. 【持续更新】 | OpenCV 学习笔记

    本文地址:http://www.cnblogs.com/QingHuan/p/7365732.html,转载请注明出处 ######################################## ...

随机推荐

  1. 智能合约稳定币USDN的价值在哪里?

    近几年来,区块链和数字货币市场快速发展,客观上需要价格相对稳定的交易媒介和贮藏手段,从而推动以链上资产或链下资产抵押型稳定币和算法型稳定币出现,以实现币价相对稳定的数字货币.市场上开始出现了诸如USD ...

  2. django学习-12.访问不同url/接口地址实现对指定数据的增删改查功能

    1.前言 通过前面博客[django学习-10.django连接mysql数据库和创建数据表]里的操作,我们已经成功在数据库[hongjingsheng_project]里创建了一张数据表[hello ...

  3. 死磕Spring之IoC篇 - 深入了解Spring IoC(面试题)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  4. Java并发包源码学习系列:同步组件CyclicBarrier源码解析

    目录 CyclicBarrier概述 案例学习 类图结构及重要字段 内部类Generation及相关方法 void reset() void breakBarrier() void nextGener ...

  5. HTML认知

    <!DOCTYPE html>的作用 1.定义 DOCTYPE是一种标准通用标记语言的文档类型的声明,目的是告诉标准通用标记语言解析器,该用什么方式解析这个文档. <!DOCTYPE ...

  6. .NET gRPC 核心功能初体验,附Demo源码

    gRPC是高性能的RPC框架, 有效地用于服务通信(不管是数据中心内部还是跨数据中心). 由Google开源,目前是一个Cloud Native Computing Foundation(CNCF)孵 ...

  7. 看完我的笔记不懂也会懂----bootstrap

    目录 Bootstrap笔记 知识点扫盲 容器 栅格系统 源码分析部分 外部容器 栅格系统(盒模型)设计的精妙之处 Bootstrap笔记 写在开始: 由于我对AngulaJS的学习只是一个最浅显的过 ...

  8. MySQL数据库插入数据出现 ERROR 1526 (HY000): Table has no partition for value xxx

    MySQL数据库插入数据出现ERROR 1526 (HY000): Table has no partition for value xxx工作的时候发现无法插入数据,报错:ERROR 1526 (H ...

  9. POJ-1797(最短路变形-dijkstra)

    Heavy Transportation POJ-1797 这题是最短路题型的变形,该题不是求起点到终点的最短路,而是求路径中的最小边的最大值. 这题的求解思路是:将原来dijkstra中的松弛方程改 ...

  10. 基于Hi3559AV100 RFCN实现细节解析-(3)系统输入VI分析一 :

    下面随笔系列将对Hi3559AV100 RFCN实现细节进行解析,整个过程涉及到VI.VDEC.VPSS.VGS.VO.NNIE,其中涉及的内容,大家可以参考之前我写的博客: Hi3559AV100的 ...