https://github.com/Tencent/xlua
首先先说明下,因为我们的项目是比较旧的项目,所以当时并未使用任何热更技术,所有的代码都是用c#写的,导致到项目后期才忽然想使用热更就非常困难了。当时研究了不少热更框架发现大部分都无法再弥补过去的疏忽,直到出现了Xlua,作者宣称项目可以继续由C#编写,只有在热更修复的时候才是用lua,我觉得有戏就研究了下。
根据方法一提示,要使用Xlua就要在每个类里头打上[Hotfix]标签,刚开始的想法是通过写一个工具遍历所有脚本,通过正则表达式匹配然后再在所有类中打上[Hotfix]标签,直到又仔细品味了下方法二,在一个static类的static字段或者属性里头配置一个列表,就可以通过白名单方式实现配置。
1.我们先从github中下载xula框架,仅仅解压缩Asset和Tools,放到我们Unity项目的根目录下
我们新建一个LuaManager脚本作为Lua虚拟机的管理组件,具体代码如下:
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;
public class LuaManager : MonoBehaviour {
public static LuaManager _instance;
public static LuaManager Instance
{
get
{
return _instance;
}
}
[CSharpCallLua]
public delegate void LuaDelegate(string paras);
///
/// 定义一个Delegate,Lua结果将传参回调给该Delegate
///
public static LuaDelegate LuaFunc;
///
/// 定义一个Lua虚拟机,建议全局唯一
///
public static LuaEnv luaEnv;
void Awake()
{
_instance = this;
}
public void LuaEnvInit()
{
luaEnv = new LuaEnv();
luaEnv.AddLoader(MyLoader);
///lua脚本的主入口
luaEnv.DoString(@"require 'main'");
//获取Lua中全局function,然后映射到delegate
luaEnv.Global.Get("LuaFunc", out LuaFunc);
}
///
/// 编写一个Loader,当一个文件被require时,这个loader会被回调,其参数是调用require所使用的参数,如果该loader找到文件,可以将其读进内存
///
///
///
private byte[] MyLoader(ref string filePath)
{
string absPath = Path.Combine(Application.dataPath, "../LuaScripts/" + filePath + ".lua");
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(absPath));
}
}
同时在项目根目录新建一个LuaScripts文件夹,在文件夹中新建一个main.lua文件,注意用UTF8编码,若使用其他编码可能会出错,这是lua脚本的主入口,编写mian.lua脚本:
function LuaFunc(path)
print("do lua function!")
require(path)
end
然后最重要的一步来了,创建一个Editor文件夹,若不在Editor文件夹中创建会报错,然后在该目录下创建HotficCfg脚本,编写热丁配置文件:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using XLua;
public static class HotfixCfg
{
[Hotfix]
public static List by_property
{
get
{
//从程序集中获取全部类信息
var allTypes = Assembly.Load("Assembly-CSharp").GetTypes();
var nameSpace = new List();
//遍历所有类筛选符合规则的命名空间
foreach (var t in allTypes)
{
if (t.Namespace != null && (t.Namespace.StartsWith("MyGame", StringComparison.CurrentCulture)))
{
if (!nameSpace.Contains(t.Namespace))
{
nameSpace.Add(t.Namespace);
}
}
}
var retList = new List();
var sb = new StringBuilder();
//遍历所有类筛选所有包含该命名空间的Type对象
foreach (var t in allTypes)
{
if (nameSpace.Contains(t.Namespace))
{
retList.Add(t);
sb.AppendLine(t.FullName);
}
}
//输出所有Type信息到项目根目录HotTypes.txt文本中
File.WriteAllText(Path.Combine(Application.dataPath, "../HotTypes.txt"), sb.ToString());
return retList;
}
}
}
同时创建测试脚本TestScript.Cs以及在LuaScripts目录下创建lua热补丁脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace MyGame
{
public class TestScript : MonoBehaviour
{
public string fileName = "test";//这里是引用的lua文件名
void Start()
{
LuaManager.Instance.LuaEnvInit();
}
void Update()
{
Debug.Log("Hello World!");
}
public void DoLuaFunc()
{
LuaManager.LuaFunc(fileName);
}
}
}
Lua热补丁脚本
--Cs.MyGame.TestScript,脚本类名,要求CS.命名空间.类名
--Update,为要替换的方法
--function ( self ),默认写法,self类似C#中的this,
--若有要使用该类中对象的公共方法可用self.定义的对象名:Function(),
--例如:self.gameObject.SetActive(true)等价于this.gameObject.SetActive(true)
--若是静态方法可直接使用".",但必须引入完整命名空间,例如CS.UnityEngine.Debug.Log()
xlua.hotfix(Cs.MyGame.TestScript, 'Update', function ( self )
print("Hello Lua!")
end)
然后在Build Settings->Player Settings中添加宏HOTFIX_ENABLE,等待编译结束Xlua页签中会多一个Hotfix Inject In Editor选项,此时选择Generate Code,等待Log中提示finished就完成了,再选择Hotfix Inject In Editor,等待提示inject finish就结束了。注意:每次编译时要等待右下角转菊花结束,不然可能会出错,此时可选Clear Generated Code,然后重复以上步骤。
然后我们创建个按钮运行测试下
刚开始Update中一直在Debug"Hello World"字符串,当我们执行DoluaFunc函数以后,Update中的代码被test.lua中的代码替换了,到此我们热更新实验就成功了。
那么如果项目并非是用命名空间的呢,能否热更呢?当然是可以的,以下为按照目录设定热更标签的代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using XLua;
public static class HotfixCfg
{
[Hotfix]
public static List by_property
{
get
{
var allTypes = Assembly.Load("Assembly-CSharp").GetTypes();
//获取类名的正则表达式
var pattern = @"class\s+(\w+)";
var reg = new Regex(pattern);
//设置要打标签脚本的根路径
var dir = Application.dataPath + "/Scripts";
var dirInfo = new DirectoryInfo(dir);
var allCS = dirInfo.GetFiles("*.cs", SearchOption.AllDirectories);
var allFiles = new List();
allFiles.AddRange(allCS);
var fileTypes = new HashSet();
foreach (var f in allFiles)
{
var lines = File.ReadAllLines(f.FullName);
foreach (var l in lines)
{
var matchs = reg.Matches(l);
if (matchs.Count > 0)
{
var className = matchs[0].Groups[1].ToString();
if (!fileTypes.Contains(className))
fileTypes.Add(className);
}
}
}
var retList = new List();
var sb = new StringBuilder();
foreach (var t in allTypes)
{
if (fileTypes.Contains(t.Name))
{
retList.Add(t);
sb.AppendLine(t.FullName);
}
}
File.WriteAllText(Path.Combine(Application.dataPath, "../HotTypes.txt"), sb.ToString());
return retList;
}
}
}
然后我们把TestScript的命名空间去掉,修改test.lua文件,去掉命名空间
xlua.hotfix(CS.TestScript, 'Update', function ( self )
print("Hello Lua!")
end)
最后测试也是成功的