Lua热更新原理与流程

一、Lua热更新原理

利用第三方Lua热更新库,在Unity中嵌入lua解释器,利用解释器执行lua脚本。需要热更新的时候,从服务器上下载需要更新的代码文件

1、为什么要用lua做热更新,而不用C#?

C#语言本身是一种静态编译语言,它的代码在编译时会被转换成机器码,然后才能被计算机执行。这种静态编译的特性使得C#在运行时无法直接修改已编译的代码。另外,C#语言在运行时使用的是Common Language Runtime (CLR)环境,CLR负责将C#代码转换成可执行的机器码。CLR在运行时会对代码进行各种优化和安全检查,这些优化和检查过程也使得热更新变得困难。
Lua是一种动态脚本语言,它的代码在运行时会被解释器逐行执行,而不需要进行静态编译。这种动态性使得Lua可以在运行时修改已加载的代码,实现热更新。具体来说,Lua的解释器可以在运行时动态加载新的脚本文件,并替换已加载的代码模块。这种动态加载和替换的机制使得Lua可以实现热更新,即在程序运行过程中修改和更新代码,而无需重新启动整个程序。
其次,C#不能打包成AB包,而Lua可以,Lua结合AB包使用起来非常方便。

2、C#能不能实现热更新?

可以,利用反射实现
反射是什么?

C#反射是指在运行时动态地获取和操作程序集、类型、成员等信息的机制。通过反射,可以在运行时通过程序集中的元数据来实例化对象、调用方法、获取和设置属性等,而不需要在编译时明确知道这些类型和成员的具体信息,主要用到Type,Assembly,Activator三个类,这里不过多赘述,详细内容可以自行学习。

C#反射实现热更新
将需要频繁更改的逻辑部分独立出来做成DLL(动态链接库(Dynamic Link Library),可以包含类,函数等资源,让其他程序动态加载使用),通过反射机制加载这些DLL,主模块代码不作修改,热更新时用新的dll替换旧的dll,主模块加载dll就起到了更新的效果。不过c#反射只适用于Android热更新,iOS因为自身的安全机制,新申请的内存空间(用于修改后的代码使用)不允许进行写操作,所以目前c#的反射基本不被商业项目用作热更新。

二、热更新流程

1、在C#调用lua代码

1、下载热更新库(XLua,toLua等),导入Unity项目
2、创建基于Lua解释器的LuaMgr,用lua解释器来解释执行lua代码
3、在Unity中需要使用lua代码的地方用require(“文件名.lua”)的方式调用lua代码

2、更新lua代码

搭建一个ftp服务器,在需要进行更新检查的地方(如游戏主页面),连接到服务器,下载对比文件获取到文件名列表和md5码,与本地的对比文件进行对比
a、如果有新的文件名,则证明有新增文件,下载即可
b、如果文件名相同,md5码不同,则证明文件有改动,需要下载并覆盖本地文件
c、如果文件名和md5码完全一致,则证明不需要更新

3、Hotfix补丁

对于一开始用C#开发的项目,可以用热补丁的形式将C#代码里的方法替换为Lua里的方法

三、XLua中的C#Api

LuaEnv类
object[] DoString(string chunk, string chunkName = “chuck”, LuaTable env = null)
执行一个代码块。

T LoadString(string chunk, string chunkName = “chunk”, LuaTable env = null)
加载一个代码块,但不执行,只返回类型可以指定为一个delegate或者一个LuaFunction

LuaTable Global;
代表lua全局环境的LuaTable

void Tick()
清除Lua的未手动释放的LuaBase对象(比如:LuaTable, LuaFunction),以及其它一些事情。
需要定期调用,比如在MonoBehaviour的Update中调用。

void AddLoader(CustomLoader loader)
增加一个自定义loader

void Dispose()
Dispose该LuaEnv

LuaTable类
T Get(string key)
获取在key下,类型为T的value,如果不存在或者类型不匹配,返回null;

T GetInPath(string path)
和Get的区别是,这个函数会识别path里头的“.”,比如var i = tbl.GetInPath(“a.b.c”)相当于在lua里头执行i = tbl.a.b.c,避免仅为了获取中间变量而多次调用Get,执行效率更高。

void SetInPath(string path, T val)
和GetInPaht对应的setter;

void Get<TKey, TValue>(TKey key, out TValue value)
上面的API的Key都只能是string,而这个API无此限制;

void Set<TKey, TValue>(TKey key, TValue value)
对应Get<TKey, TValue>的setter;

T Cast()
把该table转成一个T指明的类型,可以是一个加了CSharpCallLua声明的interface,一个有默认构造函数的class或者struct,一个Dictionary,List等等。

void SetMetaTable(LuaTable metaTable)
设置metaTable为table的metatable

LuaFunction类
object[] Call(params object[] args)
以可变参数调用Lua函数,并返回该调用的返回值。

object[] Call(object[] args, Type[] returnTypes)
调用Lua函数,并指明返回参数的类型,系统会自动按指定类型进行转换。

void SetEnv(LuaTable env)
相当于lua的setfenv函数。

XLua.CSObjectWrap:用于将C#对象包装为Lua对象的工具类。

Register(Type type, LuaCSFunction creator):注册C#类型到Lua环境中。
CreateDelegate(LuaFunction func, Type delegateType):创建一个委托对象。

XLua.CSharpCallLuaAttribute:用于标记C#调用Lua函数的特性。

LuaCallCSharp:标记Lua函数可以被C#调用。
CSharpCallLua:标记C#函数可以被Lua调用。

四、LuaMgr类参考

其中ABMgr是我自己实现的一个加载AB包的管理器,原理不复杂,可以自行实现

public class LuaMgr : BaseManager<LuaMgr>
{
    private LuaEnv luaEnv;

    public LuaTable Global
    {
        get
        {
            return luaEnv.Global;
        }
    }
    
    public void Init()
    {
        if (luaEnv != null)
        {
            return;
        }

        luaEnv = new LuaEnv();
        luaEnv.AddLoader(MyLoader);
        luaEnv.AddLoader(MyCustomABLoader);
    }

    public void DoLuaFile(string fileName)
    {
        string str = string.Format("require('{0}')", fileName);
        DoString(str);
    }
    //自动执行
    private byte[] MyLoader(ref string filePath)
    {
        string path = Application.dataPath + "/Lua/" + filePath + ".lua";
      

        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);
        }
        else
        {
            Debug.Log("文件重定向失败,文件名:"+filePath);
        }
        
        
        return null;
    }

    private byte[] MyCustomABLoader(ref string filePath)
    {
       TextAsset lua= ABMgr.GetInstance().LoadRes<TextAsset>("lua", filePath + ".lua");
       if (lua != null)
       {
           return lua.bytes;
       }
       else
       {
           Debug.Log("文件重定向失败,文件名:"+filePath);
           return null;
       }

    }
    public void DoString(string str)
    {
        if (luaEnv == null)
        {
            Debug.Log("解析器未初始化");
            return;
        }
        luaEnv.DoString(str);
    }

    public void Tick()
    {
        luaEnv.Tick();
    }

    public void Dispose()
    {
        luaEnv.Dispose();
        luaEnv = null;
    }
}

四、总结

本期文章有点短,本来想再分析下lua的底层原理的,发现有点看不懂(; ̄ー ̄川,就先写这些吧

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拉达哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值