Unity3D 使用xLua实现热更新补丁修复

版权声明:本文为Jumbo原创文章,采用[知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议],转载前请保证理解此协议
原文出处:http://www.jianshu.com/p/dc4de5612d9e

在Unity3D项目中,逻辑代码热更新这一块,现在有很多实现解决方案,基本都是借助Lua来实现的,在这众多之中,最后还是选择xLua,最早了解xLua是在腾讯的手游项目中,腾讯Apollo通用组件中,只是涉及的项目中,并没有xLua的源码及文档相关的说明。可能当时xLua还在改进当中。直到2017年1月3日,腾讯Github开源项目中,出现xLua的身影。xLua优势:1、集成快 (几乎不用改变原本项目的代码) 2、专职人员负责维护(企鹅还是有这个实力·~~·),说再多也不如自己切身体验一番。下面就帮大家,快速入门下。

一、xLua源码: https://github.com/Tencent/xLua/ 没有帐号或者下载不了的同学,可以邮箱留言,发一份给大伙;

二、获取到源码后,首先认真阅读README.md内容,可以更好的帮大家了解xLua,是否项目中遇到的问题,可以通过xLua来帮忙处理?;

三、根据提供的示例:
01_Helloworld: 快速入门的例子。
02_U3DScripting: 展示怎么用lua来写MonoBehaviour。
03_UIEvent: 展示怎么用lua来写UI逻辑。
04_LuaObjectOrented: 展示lua面向对象和C#的配合。
05_NoGc: 展示怎么去避免值类型的GC。
06_Coroutine: 展示lua协程怎么和Unity协程相配合。
07_AsyncTest: 展示怎么用lua协程来把异步逻辑同步化。
08_Hotfix: 热补丁的示例(需要开启热补丁特性,如何开启请看指南)。
配合文档:
XLua教程.doc:教程,其配套代码这里
XLua的配置.doc:介绍如何配置xLua。
XLua增加删除第三方lua库.doc:如何增删第三方lua扩展库。
XLua API.doc:API文档。
可以更好的了解xLua具备那些特性,如何便捷使用。

四、最重要的功能:热补丁
xLua支持热补丁,这意味着你可以:
1、开发只用C#;
2、运行也是C#,性能可以秒杀lua;
3、出问题了才用Lua来改掉C#出问题的部位,下次整体更新时换回正确的C#;能做到用户不重启程序fix bug;

如果你仅仅希望用热更新来fix bug,这是强烈建议的做法。这里是使用指南。
这个文档说明一定要仔细阅读https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/hotfix.md

很多刚开始接触的同学,因为没有认真看文档说明,容易出现一些问题,这边列举下:
注意:a、HOTFIX_ENABLE宏是否在Unity3D的File->Build Setting->Scripting Define Symbols下添加
b、Mono.Cecil.是否拷贝:OSX命令行 cp /Applications/Unity/Unity.app/Contents/Managed/Mono.Cecil. Project/Assets/XLua/Src/Editor/

Win命令行 copy UnityPath\Editor\Data\Managed\Mono.Cecil.* Project\Assets\XLua\Src\Editor\

c、菜单栏中,执行xLua/Generat Code, 会在新建Gen文件夹,下面生成一些wrap文件


具体生成那些类型对应的wrap文件,需要自己配置,详见:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Examples/ExampleGenConfig.cs
在项目中集成,最好自己写一份配置,替换ExampleGenConfig,比如取名CustomGenConfig.cs,加入了[Hotfix]标签

  //需要热更新的类型
    [Hotfix]
    public static List<Type> by_field = new List<Type>()
    {

    };

d、要等打印了hotfix inject finish!后才运行例子,否则会类似xlua.access, no field __Hitfix0_Update的错误

五、整合xLua进项目
1、


2、

3、

先贴上代码(myHotfix.lua 编写fixbug逻辑):

using UnityEngine;
using System.Collections;
using XLua;

/// <summary>
/// 先判断更新热补丁文件,然后再执行Fixing
/// </summary>
public class xLuaInstance : MonoSingleton<xLuaInstance>{

    LuaEnv luaenv;

    const string fixFile = "myHotfix.lua";

    public string FixFilePath
    {
        get
        {
            return FileUtils.GetFullPath(null, fixFile, FileUtils.StorageType.Persistent);
        }
    }

    /// <summary>
    /// 热补丁修复中。。。
    /// </summary>
    public void Fixing()
    {

        byte[] bytes = System.IO.File.ReadAllBytes(FixFilePath);

        if (bytes != null)
        {
            luaenv = new LuaEnv();
            luaenv.AddLoader((ref string filename) =>
            {
                if (filename == "myHotfix")
                {
                    filename = FixFilePath;
                    return bytes;
                }

                return null;
            });

            luaenv.DoString(@"require 'myHotfix'");
        }
    }

    void Update()
    {
        if (luaenv != null)
            luaenv.Tick();
    }

    void OnDestroy()
    {
        if (luaenv != null)
            luaenv.Dispose();
    }
}

4、大致流程
i、游戏一开始启动时,检查远程服务器上是否有新的myHotfix.lua文件(例如:md5比对,自己加解密),有的话,下载下来,放在指定目录,没有的话,读取本地已有的myHotfix.lua文件,若文件不存在,则说明不需要热修复,一般这种情况是在项目刚发布的早起,没有进行打补丁;

ii、项目发布版本前,需要在CustomGenConfig.cs中 加入需要添加[Hotfix]标签的类型,想要更灵活使用的话,可以自己写个配置表,读取配置中的类型,自动添加,***只有加入[Hotfix]标签的类型,才可以调用xlua.hotfix()



,不然会报错,切记!!!

iii、如果你的项目是自动化发布版本,需要在调用打包之前,生成wrap, 调用这个CSObjectWrapEditor.Generator.GenAll();

MonoSingleton.cs

//非线程安全
using UnityEngine;

    /// <summary>
    ///     基类继承树中有MonoBehavrour类的单件实现,这种单件实现有利于减少对场景树的查询操作
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class MonoSingleton<T> : MonoBehaviour where T : Component
    {
        // 单件子类实例
        private static T _instance;

        // 在单件中,每个物件的destroyed标志设计上应该分割在不同的存储个空间中,因此,忽略R#的这个提示
        // ReSharper disable once StaticFieldInGenericType
        private static bool _destroyed;

        /// <summary>
        ///     获得单件实例,查询场景中是否有该种类型,如果有存储静态变量,如果没有,构建一个带有这个component的gameobject
        ///     这种单件实例的GameObject直接挂接在bootroot节点下,在场景中的生命周期和游戏生命周期相同,创建这个单件实例的模块
        ///     必须通过DestroyInstance自行管理单件的生命周期
        /// </summary>
        /// <returns>返回单件实例</returns>
        public static T Instance
        {
            get{

                 if (_instance == null && !_destroyed)
                 {
                     _instance = (T) FindObjectOfType(typeof (T));
                     if (_instance == null)
                    {
                       GameObject go = new GameObject(typeof (T).Name);
                       _instance =  go.AddComponent<T>();

                        DontDestroyOnLoad(_instance);

                       var singletonRootGo = GameObject.Find("MonoSingletonRoot");
                       if (singletonRootGo != null)
                       {
                           go.transform.parent = singletonRootGo.transform;
                         }
                    }
                 }
                return _instance;
            }
           
         }

        /// <summary>
        ///     删除单件实例,这种继承关系的单件生命周期应该由模块显示管理
        /// </summary>
        public static void DestroyInstance()
        {
            if (_instance != null)
                Destroy(_instance.gameObject);

            _destroyed = true;
            _instance = null;
        }

        /// <summary>
        ///     Awake消息,确保单件实例的唯一性
        /// </summary>
        protected virtual void Awake()
        {
            if (_instance != null && _instance.gameObject != gameObject) Destroy(gameObject);
            else if(_instance == null)
                _instance = GetComponent<T>();

            DontDestroyOnLoad(_instance);
        }

        /// <summary>
        ///     OnDestroy消息,确保单件的静态实例会随着GameObject销毁
        /// </summary>
        protected virtual void OnDestroy()
        {
            if (_instance != null && _instance.gameObject == gameObject)
            {
                _instance = null;                
            }
        }

        /// <summary>
        ///     Have Instance
        /// </summary>
        /// <returns></returns>
        public static bool HaveInstance()
        {
            return _instance != null;
        }
    }

如何调试Lua脚步,接下来,我会写一篇关于ZeroBrane这个工具。

在此感谢xLua作者:车雄生 chexiongsheng!!!
期待大家的支持!!!
祝大家在17年万事如意,身体健康!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容