xLua学习笔记——Hotfix热补丁(叁)
五、Hotfix 热补丁
(一)热补丁的概念
热补丁是指在不重启设备的情况下,可以对设备当前软件版本的缺陷进行修复。
Hotfix 是基于 xLua 框架实现的一种代码热更新技术,专门用于在游戏已发布后,无需重新打包或通过应用商店审核,就能动态修复 C# 代码中的 BUG 或更新逻辑。
通俗理解就是将 C# 中的方法替换成 Lua 等脚本语言中的函数来执行。
(二)准备工作
在进行热补丁之前,我们需要准备一些东西,才能进行热补丁
1、现在打包的界面中,在 Scripting Define Symbols 这个位置,添加 HOTFIX_ENABLE 这个宏,表示启用热补丁,之后从菜单栏的 Xlua 中就会多一个 Hotfix Inject In Editor 按钮

2、在 Unity 顶部菜单栏中找到 “XLua/Generate Code” 按钮并单击
3、还记得之前概述提到得 Tools 文件夹吗?现在需要确保 xLua 的 Tools 工具放进 Unity 工程中,Tools 文件夹不要放在 Assets 文件夹里,而是放在和 Assets 同级的文件夹中。
4、注入,点击 Hotfix 注入按钮,如果提示注入完成即可

(三)热补丁基础用法
我们先在 C# 定义要使用热补丁的类,在类上边加上 [Hotfix] 特性,当然每次更改后需要重新点击注入按钮
1、C# 代码如下:
[Hotfix]
public class HotfixTest
{
public HotfixTest()
{
Debug.Log("HotfixTest构造函数");
}
~HotfixTest()
{
Debug.Log("析构");
}
public void Speak(string str)
{
Debug.Log(str);
}
}
2、单函数替换
Lua 使用热补丁很简单,使用 xlua.hotfix(class, [method_name], fix) 这个方法即可,第一个参数是类名,第二个参数是方法名,第三个参数是要替换的函数,我们先替换 Speak 方法
Lua 代码如下:
注意 Speak 是成员方法哦,因此要加个 self 参数
xlua.hotfix(CS.HotfixTest,"Speak",function(self,str)
print(str)
end)
这样,我们在 C# 中调用的 Speak 方法,都会被替换成 Lua 的这个方法
3、多函数替换
有时我们希望一次性替换多个函数,而不是一个个替换。使用的方法还是xlua.hotfix,不过参数不太一样了,我们用一张表表示所有要替换的函数。下面举个例子,我们一次性替换 C# 的 3 个函数吧:
Lua 代码如下:
xlua.hotfix(CS.HotfixTest,{
[".ctor"] = function()
print("lua热补丁构造函数")
end,
Speak = function(self,str)
print("你好!"..str)
end,
Finalize = function()
print("lua热补丁析构函数")
end
})
可以看到,构造函数的替换和析构函数的替换比较特殊,必须得用特定的名称,构造函数就是 [".ctor"],析构函数就是 Finalize
(四)协程函数替换
1、C# 代码如下:
IEnumerator TestCoroutine()
{
while (true)
{
yield return new WaitForSeconds(1f);
Debug.Log("协程启动一次");
}
}
2、Lua 代码如下:
替换的方法就是用一个 Lua 函数替换 C# 协程函数,在这个 Lua 函数里再返回一个处理过的 Lua 协程。就相当于包了一层
util = require("xlua.util") -- 获取util工具
xlua.hotfix(CS.HotFixMain,{
TestCoroutine = function(self)
-- 替换c#协程:返回一个xlua处理过的lua协程函数
return util.cs_generator(function()
while true do
coroutine.yield(CS.UnityEngine.WaitForSeconds(1))
print("Lua热补丁协程")
end
end)
end
})
(五)索引器和属性替换
1、C# 代码如下:
[Hotfix]
public class HotfixTest
{
public int[] array = new int[3] { 1, 2, 3 };
// 属性
public int Age
{
get
{
return 0;
}
set
{
Debug.Log(value);
}
}
// 索引器
public int this[int index]
{
get
{
if (index < 0 || index >= array.Length)
return 0;
return array[index];
}
set
{
array[index] = value;
}
}
}
2、Lua 代码如下:
属性的 get 和 set 方法的替换就是在属性名前面加上 set_和 get_
索引器的 get 和 set 固定是 get_Item 和 set_Item
xlua.hotfix(CS.HotFixMain,{
set_Age = function(self,value)
print("lua重定向设置属性"..value)
end,
get_Age = function(self)
return 10
end,
set_Item = function(self,index,value)
print("lua重定向设置索引器".." "..index.." "..value)
end,
get_Item = function(self,index)
return 10
end
})
当然,你可能会问如果属性名也是 Item 会怎么样,其实当你属性改成 Item 时,C# 里就先会冲突,因为索引器默认名字就是 Item,如果通过一些方法将 C# 属性改成了 Item,还是需要将 Lua 内的代码修改一番,比较麻烦,因此一般不会把属性命名为 Item
(六)事件操作替换
我们可以对事件操作进行函数替换,当监听到事件的 + 操作或 - 操作时,就会调用 Lua 替换的方法,主要是为了修复事件的 bug 或者增加一些额外功能
1、C# 代码如下:
[Hotfix]
public class HotfixTest
{
public event UnityAction myEvent;
}
在Unity主入口的类中,创建一个方法 Test (),并在主函数中调用事件,即添加和删除事件的逻辑:
HotfixTest test = new HotfixTest();
test.myEvent += Test;
test.myEvent -= Test;
2、Lua 代码如下:
只需要使用 add_事件名和 remove_事件名,即可对其进行替换,我们可以记录下添加 / 删除的委托或者弄些其他逻辑
xlua.hotfix(CS.HotFixMain,{
-- add_事件名 事件加操作
-- remove_事件名,事件减操作
add_myEvent = function(self,delegate)
print(delegate)
print("添加事件")
end,
remove_myEvent = function(self,delegate)
print(delegate)
print("移除事件")
end
})
(七)泛型类替换
我们声明一个泛型类,就可以开始啦
1、C# 代码如下:
[Hotfix]
public class HotfixTest2<T>
{
public void Test(T str)
{
Debug.Log(str);
}
}
2、Lua 代码如下:
只需要类的括号内带上泛型的类型即可
xlua.hotfix(CS.HotfixTest2(CS.System.Int32),{
Test = function(self,str)
print("lua中的泛型类int")
end
})
六、其他知识点
(一)XLua 的配置
有时我们无法直接给一个类型打标签,比如系统 api,没源码的库,或者实例化的泛化类型,这时你可以在一个静态类里声明一个静态字段,如静态列表,然后为这字段加上标签,这样列表中的内容都会被打上这个特性的标签,我们举例子来说明一下,当然具体使用可参照 Doc 下《XLua 的配置.doc》
先声明一个静态类,里面我们放上各种各样的静态列表,用于配置:
public static class GenConfig
{
// 配置相关
}
下面就来配置我们之前学过的 C# 调用 Lua,Lua 调用 C# 和热补丁相关的
1、[CSharpCallLua]
一般只需要配置要使用的委托和接口即可
//C#静态调用Lua的配置(包括事件的原型),仅可以配delegate,interface
[CSharpCallLua]
public static List<Type> CSharpCallLua = new List<Type>()
{
typeof(Action),
typeof(Func<double, double, double>),
typeof(Action<string>),
typeof(Action<double>),
typeof(Action<bool>),
typeof(UnityEngine.Events.UnityAction),
typeof(System.Collections.IEnumerator)
// 其他配置...
};
2、[LuaCallCSharp]
一般配置 C# 的标准库 或者 Unity 的 API 等
//lua中要使用到C#库的配置,比如C#标准库,或者Unity API,第三方库等
[LuaCallCSharp]
public static List<Type> LuaCallCSharp = new List<Type>()
{
typeof(System.Object),
typeof(UnityEngine.Object),
typeof(Vector2),
typeof(Vector3),
typeof(Light),
typeof(Mathf)
// 其他配置...
};
3、[Hotfix]
热补丁也可以通过静态列表的方式进行配置
[Hotfix]
public static List<Type> Hotfix = new List<Type>()
{
typeof(HotFixSubClass), // 热补丁的类
typeof(GenericClass<>), // 热补丁泛型类
// 其他要热补丁的类...
};
当然,还有其他 xlua 的特性,比如 [BlackList] 黑名单等等,这里只举了常用的,其他要用到的时候再去查
(二)Lua 一键转换工具
我们之前说过,AB 包加载时,无法识别.lua 文件,因此需要添加后缀 txt 转换成文本文件。但是每次修改时手动修改过于麻烦,因此需要一个自动转 txt 的工具,代码如下:
该代码的功能是将 Lua 文件夹下的 .lua 文件复制到 LuaTxt 文件夹下,并统一添加 .txt 后缀,再自动分类到 ab 包的 lua 分类中
public class LuaCopyEditor : Editor
{
[MenuItem("XLua/Copy Lua To Txt")]
public static void CopyLuaToTxt()
{
// 原始文件夹
string path = Application.dataPath + "/Lua/";
if (!Directory.Exists(path))
return;
string[] oldFiles = Directory.GetFiles(path, "*.lua");
// 目标文件夹
string newPath = Application.dataPath + "/LuaTxt/";
if (!Directory.Exists(newPath))
Directory.CreateDirectory(newPath);
else
{
// 删除目标文件夹中旧的文件
string[] files = Directory.GetFiles(newPath, "*.txt");
foreach (string file in files)
{
File.Delete(file);
}
}
// 将修改后的文件复制到新文件夹
List<string> newFiles = new List<string>();
foreach (string oldFile in oldFiles)
{
string newFile = newPath + oldFile.Substring(oldFile.LastIndexOf("/") + 1) + ".txt";
newFiles.Add(newFile);
File.Copy(oldFile, newFile);
}
// 刷新
AssetDatabase.Refresh();
// 自动归类到AB包
foreach (string newFile in newFiles)
{
// 该路径必须是相对与Assets的
AssetImporter importer = AssetImporter.GetAtPath(newFile.Substring(newFile.IndexOf("Assets")));
if (importer != null)
{
importer.assetBundleName = "lua";
}
}
AssetDatabase.Refresh();
}
}