热更新应用--热补丁Hotfix学习笔记
一.热补丁简介
热补丁主要是用于将纯C#工程在不重做的情况下通过打补丁的形式改造成具备lua热更新功能工程,主要是让原来脚本中Start函数和Update函数等函数代码块重定向到lua代码。
二.第一个热补丁
1.C#代码端:
1)创建一个脚本,并挂载到游戏中的任意物体上(实际使用过程中一般修改已有脚本,这里测试随意挂载就好)
2)在脚本中定义好测试用的方法,在Start函数中执行Lua文件(LuaManager类及C#调用lua代码的方式详见xlua学习笔记,LuaManager类在:四.C#调用lua-3.lua解析器管理器)
public class HotfixMain : MonoBehaviour { void Start() { LuaManager.Instance.DoLuaFile("Main"); //调用定义的方法,这些方法被lua中的热补丁重新定义了 Debug.Log(Add(1, 2)); Speak("我很帅"); } //预备给热补丁覆盖的成员方法 public int Add(int a,int b) { return 0; } //预备给热补丁覆盖的静态方法 public static void Speak(string str) { Debug.Log("hahaha"); } }
2.lua端代码
1)将lua文件放在LuaManager类能够重定向到的文件夹中,或者添加LuaManager类中的重定向方法使lua文件能被找到,这里放在Assets目录下的Lua文件夹下,LuaManager中已经添加了这个文件的重定向方法。
2).C#代码调用了Main,所以在文件夹中添加Main.lua这个lua文件,这个文件使lua的主入口文件,相当于C#工程中的Main方法,主要用于执行其他lua文件、定义一些通用全局变量、初始化等。这里Main文件中执行Hotfix1文件,代码就一句:
require("Hotfix1")
3)Hotfix1.lua文件中定义第一个热补丁的代码,主要调用方法xlu.hotfix重写C#中的方法:
--热补丁 --lua中的热补丁固定写法 --通过xlua的hotfix函数进行热补丁更新,参数是:类名、"函数名",lua函数 --成员方法将self作为第一个参数传入 xlua.hotfix(CS.HotfixMain,"Add",function(self,a,b) return a + b end) --静态方法不需要传入self参数 xlua.hotfix(CS.HotfixMain,"Speak",function(a) print(a) end) --热补丁还需要在Unity脚本中作以下操作 --加特性、加宏、生成代码、hotfix注入 --热补丁缺陷:只要修改了热补丁的代码,都需要重新做hotfix注入
3.Unity中的操作
1)加特性:在需要被热补丁更新的C#类前面加上[Hotfix]特性,这里给刚才1中创建的脚本加上特性,其他非mono脚本也是一样的做法:
//加上特性以生成热补丁代码 [Hotfix] public class HotfixMain : MonoBehaviour { void Start() { LuaManager.Instance.DoLuaFile("Main"); //调用定义的方法,这些方法被lua中的热补丁重新定义了 Debug.Log(Add(1, 2)); Speak("我很帅"); } //预备给热补丁覆盖的成员方法 public int Add(int a,int b) { return 0; } //预备给热补丁覆盖的静态方法 public static void Speak(string str) { Debug.Log("hahaha"); } }
2)加宏:打开Unity中Edit->ProjectSetting
在Player->otherSetting->Scripting Define Symbols中输入HOTFIX_ENABLE
3)加宏之后生成代码,点击XLua选项下Generate Code选项生成代码。注意:加宏成功后XLua选项下会出现Hotfix Inject In Editor选项,这个是hotfix注入使用的选项,如果没有的话说明刚才的宏没有成功加上。
4)点击Hotfix Inject In Editor进行hotfix注入,如果报错please install the Tools,将xlua工程源文件中的Tools文件夹拷贝到自己的工程中。注意:不是拷贝到Assets目录下,源工程文件夹中Tools文件夹和Assets文件夹同级,所以将Tools文件夹拷贝到自己的工程文件中和Assets文件夹同级文件夹下而不是Assets目录下(三张图片分别是xlua源文件夹中Tools文件夹所在位置、打开工程所在文件夹、拷贝后在自己的工程中Tools文件夹所在位置):
5)运行工程
4.最后:注意每次热补丁的代码修改后,都需要重新生成代码和hotfix注入。
三.hotfix重定向其他内容
1.多函数替换和构造析构函数热补丁(构造函数和析构函数重定向后原代码逻辑会先执行,再执行lua中重定向的代码逻辑,这一点和其他成员函数及静态函数不同)
//加上特性以生成热补丁代码 [Hotfix] public class HotfixMain : MonoBehaviour { void Start() { LuaManager.Instance.DoLuaFile("Main"); //调用定义的方法,这些方法被lua中的热补丁重新定义了 Debug.Log(Add(1, 2)); Speak("我很帅"); } private void Update() { } //预备给热补丁覆盖的成员方法 public int Add(int a,int b) { return 0; } //预备给热补丁覆盖的静态方法 public static void Speak(string str) { Debug.Log("hahaha"); } } [Hotfix] public class HotfixTest { public HotfixTest() { Debug.Log("HotfixTest构造函数"); } public void Speak(string str) { Debug.Log(str); } ~HotfixTest() { } }
--多函数替换 --将多个函数写成一个表作为参数传入 xlua.hotfix(CS.HotfixMain,{ Update = function(self) print(os.time()) end, Add = function(self,a,b) return a + b end, Speak = function(a) print(a) end }) --构造函数热补丁 xlua.hotfix(CS.HotfixTest,{ --构造函数的热补丁固定写法 [".ctor"] = function() print("Lua热补丁构造函数") end, Speak = function(self,a) print("Lua热补丁Speak函数") end, --析构函数的热补丁固定写法 Finalize = function() print("Lua热补丁析构函数") end })
2.协程函数替换
[Hotfix] public class HotfixMain : MonoBehaviour { void Start() { LuaManager.Instance.DoLuaFile("Main"); StartCoroutine(TestCoroutine()); } IEnumerator TestCoroutine() { while (true) { yield return new WaitForSeconds(1f); Debug.Log("c#协程打印一次"); } } }
--协程函数替换 --使用协程必须引入xlua.util util = require("xlua.util") xlua.hotfix(CS.HotfixMain,{ TestCoroutine = function(self) return util.cs_generator(function() while true do coroutine.yield(CS.UnityEngine.WaitForSeconds(1)) print("lua热补丁协程函数") end end) end })
3.索引器和属性替换
[Hotfix] public class HotfixMain : MonoBehaviour { private int[] array = new int[] { 5, 4, 3, 2, 1 }; void Start() { LuaManager.Instance.DoLuaFile("Main"); Debug.Log(this.Age); this.Age = 200; Debug.Log(this[0]); this[2] = 10000; } //定义属性 public int Age { get { return 0; } set { Debug.Log(value); } } //定义索引器 public int this[int index] { get { if (index >= 0 && index < 5) return array[index]; return 0; } set { if (index >= 0 && index < 5) array[index] = value; } } }
xlua.hotfix(CS.HotfixMain,{ --属性热补丁的固定写法 --使用set_属性名替换设置属性的方法,使用get_属性名替换获取属性值的方法 set_Age = function(self,v) print("Lua热补丁设置属性") end, get_Age = function(self) return 10 end, --索引器在类中是唯一的,固定写法和属性类似 --使用set_Item替换索引器的set方法,使用get_Item替换索引器的set方法 get_Item = function(self,index) print("Lua热补丁重定向索引器get") return 1000; end, set_Item = function(self,index,v) print("Lua热补丁重定向索引器set") end })
4.事件替换
[Hotfix] public class HotfixMain : MonoBehaviour { event UnityAction customEvent; void Start() { LuaManager.Instance.DoLuaFile("Main"); StartCoroutine(EventAddRemove()); } private void Update() { if (customEvent != null) customEvent(); } /// <summary> /// 添加到委托中的函数 /// </summary> private void Test() { Debug.Log("event test running"); } /// <summary> /// 使用协程添加和删除委托函数 /// </summary> /// <returns></returns> private IEnumerator EventAddRemove() { customEvent += Test; yield return new WaitForSeconds(5f); customEvent -= Test; } }
--事件热补丁 xlua.hotfix(CS.HotfixMain,{ --add_事件名 代表添加事件 --remove_事件名 代表移除事件 add_customEvent = function(self,del) print(del) print("添加事件函数") --在添加事件时,不要把传入的委托往事件中存,否则会死循环 --self:customEvent("+",del) end, remove_customEvent = function(self,del) print(del) print("移除事件函数") end })
5.泛型类替换
[Hotfix] public class HotfixTest2<T> { public void Test(T str) { Debug.Log(str); } }
void Start() { LuaManager.Instance.DoLuaFile("Main"); new HotfixTest2<string>().Test("movin"); new HotfixTest2<int>().Test(10000); }
--泛型类中泛型T可以变化,所以要一个类型一个类型地替换 --在第一个参数后面加上括号,括号中书写一个类型,代表如果泛型是这个类型时地替换方法 xlua.hotfix(CS.HotfixTest2(CS.System.String),{ Test = function(self,str) print("泛型为string时的热补丁,参数是"..str) end }) xlua.hotfix(CS.HotfixTest2(CS.System.Int32),{ Test = function(self,i) print("泛型为int时的热补丁,参数是"..i) end })