美文网首页
集成XLua给C#打补丁

集成XLua给C#打补丁

作者: 吴少年 | 来源:发表于2019-05-02 17:24 被阅读0次

    最近项目内测出现了许多问题,但是由于项目纯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 热补丁自动化配置-------------------------
    
    }
    

    相关文章

      网友评论

          本文标题:集成XLua给C#打补丁

          本文链接:https://www.haomeiwen.com/subject/lvnnnqtx.html