Unity热更之旧项目救星——Xlua热补丁修复

Xlua官方开源地址:

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);

    /// <summary>
    /// 定义一个Delegate,Lua结果将传参回调给该Delegate
    /// </summary>
    public static LuaDelegate LuaFunc;

    /// <summary>
    /// 定义一个Lua虚拟机,建议全局唯一
    /// </summary>
    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);
    }

    /// <summary>
    /// 编写一个Loader,当一个文件被require时,这个loader会被回调,其参数是调用require所使用的参数,如果该loader找到文件,可以将其读进内存
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    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<Type> by_property
    {
        get
        {
            //从程序集中获取全部类信息
            var allTypes = Assembly.Load("Assembly-CSharp").GetTypes();
            var nameSpace = new List<string>();
            
            //遍历所有类筛选符合规则的命名空间
            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<Type>();
            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<Type> 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<FileInfo>();
            allFiles.AddRange(allCS);

            var fileTypes = new HashSet<string>();
            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<Type>();
            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)

最后测试也是成功的

猜你喜欢

转载自blog.csdn.net/RinKas/article/details/81020598
今日推荐