集成XLua给C#打补丁

2019-05-02  本文已影响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在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是:

引入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 热补丁自动化配置-------------------------

}
上一篇下一篇

猜你喜欢

热点阅读