集成XLua给C#打补丁

最近项目内测出现了许多问题,但是由于项目纯C#开发。导致客户端这边的bug需要重新出包才能解决,看着bug在那毫无办法。项目已经到了中后期使用Lua重写游戏几乎不可能,而且工程量巨大。看了XLua相关介绍,觉得是一个不错的解决方案。所以基于这个问题引入了XLua来临时解决部分线上bug。

XLua简介

此次引用XLua官方说明。XLua (Github地址)

C#下Lua编程支持

xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。

xLua的突破

xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是:

  • 可以运行时把C#实现(方法,操作符,属性,事件等等)替换成lua实现;
  • 出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc;
  • 编辑器下无需生成代码,开发更轻量;
  • 更详细的特性、平台支持介绍请看这里。

引入XLua

1、下载XLua工程把Assets和Tools拷贝到工程目录里面。


XLua到项目.png

2、创建一个LuaManager.cs 代码如下:

using UnityEngine;
using XLua;
using System.IO;
public class LuaManager{
    private static LuaManager uniqueLuaManager;
    private static LuaEnv luaState;
    private LuaManager(){
    }
    public static LuaManager GetInstance(){
        if (uniqueLuaManager == null)
        {
            uniqueLuaManager = new LuaManager();
            luaState = new LuaEnv();
        }
        return uniqueLuaManager;
    }

    public static void Init(){
        LuaManager.GetInstance();
        luaState.AddLoader(CustomLoader);
        luaState.DoString("require 'Main'");
    }
    public static void DoString(string str){
        if (luaState == null)
        {
            Init();
        }
        object[] rets = luaState.DoString(str);
        foreach(var o in rets){
            Debug.Log(o);
        }
    }
    static byte[] CustomLoader(ref string fileName){
        string luaPath = Application.dataPath + "/Resources/Lua/" + fileName + ".lua";
        string strLuaContent = File.ReadAllText(luaPath);
        byte[] block = null;
        block = System.Text.Encoding.UTF8.GetBytes(strLuaContent);
        return block;
    }
    public static void Destory(){
        luaState.Dispose();
    }
}

调用LuaManager.Init()初始化。这个初始化可以放到刚进入游戏的时候初始化的时候。"Assets/Resources/"下创建Lua文件夹,里面新建Main.lua这个为Lua启动的第一个文件。后续Lua相关的代码可以从这里开始。这之后Lua就可以正常使用了。在其他地方也可以使用LuaManager.DoString()来运行一段Lua代码。

3、由于我们游戏内是需要使用热更CS功能。在上面之前CS已经可以运行一段Lua代码。所以采用的方案是把需要热更的代码放到CDN上。每次进入游戏的时候去CDN拉取热更的Lua代码,通过Lua代码来HotFix我们的CS代码。

Lua Hotfix Cs

1、在XLua Github上,作者已经写了关于如何引入Hotfix(热补丁操作指南)。大体照着这个步骤来即可。这里需要特别注意的一个地方是,在有任何修改CS代码导致重新编译会使得之前Lua里面的xlua.hotfix失效。所以,每次有修改的时候都需要重新generate Code和hotfix inject in Editor。建议把这两个步骤放到每次出包的时候主动调用这两个,重新注入一下代码。

2、关于热更新文档里面说的标识要热更新的类型有两种方式,建议大家用第二种。第一种的话需要对要热更新的类都做[Hotfix]标签。在开发阶段我们并不知道哪些类会出现bug。所以采用第二种之后不需要对类做Hotfix标签了。第二种需要在Assets/Editor文件夹下创建HotfixCfg.cs(注意,一定要在Editor目录下)。代码如下:

    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("这里填你的命名空间", StringComparison.CurrentCulture)))
                {
                    if (!nameSpace.Contains(t.Namespace))
                    {
                        nameSpace.Add(t.Namespace);
                    }
                }
            }

            var retList = new List<Type>();
            var sb = new StringBuilder();
            foreach (var t in allTypes)
            {
                if (nameSpace.Contains(t.Namespace))
                {
                    retList.Add(t);
                    sb.AppendLine(t.FullName);
                }
            }
            File.WriteAllText(Path.Combine(Application.dataPath, "../HotTypes.txt"), sb.ToString());

            return retList;
        }
    }
}

3、最后,每次热更之前可以把代码先放到Main.Lua测试其正确性,测试无误之后放到CDN上的Hotfix.lua里面。进游戏的时候加载Hotfix.lua里面的内容,调用LuaManager.DoString()运行这段热更代码即可。

这里只用到了Lua热更Cs代码。个人还是比较建议项目前期的时候规划这些。用了几年的CS+Lua的方式开发项目,个人认为用Lua来写界面逻辑比较适合游戏这种版本迭代快的项目。至于性能方面不管是XLua、Slua和Tolua都已经优化的不错了,而且现在手机的性能一直攀升。这方面其实问题不是特别大,有些特别耗性能的可以放到CS那边做,这些部分也不会经常改动。其他UI相关的还是用Lua比较好,而且可以小版本迭代,不需要更新整包。

-------------------------------------------------分割线------------------------------------------------------------------------

看了下XLua的配置,他们已经给出了CS热更的推荐配置。如下xLua的配置

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using XLua;
using System.Linq;
public static class HotfixCfg
{   
    /***************热补丁可以参考这份自动化配置***************/
    [Hotfix]
    static IEnumerable<Type> HotfixInject
    {
       get
       {
           return (from type in Assembly.Load("Assembly-CSharp").GetExportedTypes()
                              where type.Namespace == null || !type.Namespace.StartsWith("XLua")
                              select type);
       }
    }
    //--------------begin 热补丁自动化配置-------------------------
    static bool hasGenericParameter(Type type)
    {
       if (type.IsGenericTypeDefinition) return true;
       if (type.IsGenericParameter) return true;
       if (type.IsByRef || type.IsArray)
       {
           return hasGenericParameter(type.GetElementType());
       }
       if (type.IsGenericType)
       {
           foreach (var typeArg in type.GetGenericArguments())
           {
               if (hasGenericParameter(typeArg))
               {
                   return true;
               }
           }
       }
       return false;
    }

    static bool typeHasEditorRef(Type type)
    {
       if (type.Namespace != null && (type.Namespace == "UnityEditor" || type.Namespace.StartsWith("UnityEditor.")))
       {
           return true;
       }
       if (type.IsNested)
       {
           return typeHasEditorRef(type.DeclaringType);
       }
       if (type.IsByRef || type.IsArray)
       {
           return typeHasEditorRef(type.GetElementType());
       }
       if (type.IsGenericType)
       {
           foreach (var typeArg in type.GetGenericArguments())
           {
               if (typeHasEditorRef(typeArg))
               {
                   return true;
               }
           }
       }
       return false;
    }

    static bool delegateHasEditorRef(Type delegateType)
    {
       if (typeHasEditorRef(delegateType)) return true;
       var method = delegateType.GetMethod("Invoke");
       if (method == null)
       {
           return false;
       }
       if (typeHasEditorRef(method.ReturnType)) return true;
       return method.GetParameters().Any(pinfo => typeHasEditorRef(pinfo.ParameterType));
    }

    // 配置某Assembly下所有涉及到的delegate到CSharpCallLua下,Hotfix下拿不准那些delegate需要适配到lua function可以这么配置
    [CSharpCallLua]
    static IEnumerable<Type> AllDelegate
    {
       get
       {
           BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
           List<Type> allTypes = new List<Type>();
           var allAssemblys = new Assembly[]
           {
               Assembly.Load("Assembly-CSharp")
           };
           foreach (var t in (from assembly in allAssemblys from type in assembly.GetTypes() select type))
           {
               var p = t;
               while (p != null)
               {
                   allTypes.Add(p);
                   p = p.BaseType;
               }
           } 
           allTypes = allTypes.Distinct().ToList();
           var allMethods = from type in allTypes
                            from method in type.GetMethods(flag)
                            select method;
           var returnTypes = from method in allMethods
                             select method.ReturnType;
           var paramTypes = allMethods.SelectMany(m => m.GetParameters()).Select(pinfo => pinfo.ParameterType.IsByRef ? pinfo.ParameterType.GetElementType() : pinfo.ParameterType);
           var fieldTypes = from type in allTypes
                            from field in type.GetFields(flag)
                            select field.FieldType;
           return (returnTypes.Concat(paramTypes).Concat(fieldTypes)).Where(t => t.BaseType == typeof(MulticastDelegate) && !hasGenericParameter(t) && !delegateHasEditorRef(t)).Distinct();
       }
    }
    //--------------end 热补丁自动化配置-------------------------

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

推荐阅读更多精彩内容

  • 暗了下来 静了下来 城市结束一整日的喧嚣和忙碌 面具也被一一摘下 灵魂迫不及待逃离虚伪 夜 释放了压抑 释放了情绪...
    王洋葱阅读 251评论 0 1
  • 定了早晨6点的闹钟,却没有几天能按时起床,买的网课好久没有听过,说好的锻炼一拖再拖,工作完成慢,质量又差,感觉每天...
    城汇玩a阅读 341评论 0 4
  • 文/达佚 壹 2008年十六岁,初三。会在夏天午休的时候后偷偷溜出来跑到小卖部买小布丁;会在冬天下自习后去泡...
    达佚阅读 439评论 0 2