精华内容
参与话题
问答
  • XLua

    2020-12-02 03:07:36
    <div><p> Unable to read header from archive file: C:/Users/JMVAS/AppData/LocalLow/jmvas/game/Launch/Android/loadingmarry.ab System.Reflection.MethodBase:Invoke(Object, Object[]) ILRuntime.CLR.Method....
  • Xlua

    千次阅读 2018-06-18 09:29:04
    有一个项目做完快上线了,不是lua写的,能热更新的东西就特别少,如果遇到bug也很难在第一时间热修复,所以我就接入了Xlua这个插件点击打开链接 原本只是想热修复一下的,后来领导要求把逻辑系统的C#代码全部换成了Lua,...

    有一个项目做完快上线了,不是lua写的,能热更新的东西就特别少,如果遇到bug也很难在第一时间热修复,所以我就接入了Xlua这个插件点击打开链接

          原本只是想热修复一下的,后来领导要求把逻辑系统的C#代码全部换成了Lua,至于为什么,因为他们习惯了每天都更新和修改的开发模式...所以我们干了一件极其丧心病狂的事情,就是逻辑系统的C#代码全部翻译成了lua代码,全手动翻译...我保证,打死以后也不会再干类似的事情...

         Xlua特别好用,但是在使用过程中,我发现其实并不是那么简单的,有很多值得注意的地方.

    1.接入Xlua

           接入的门槛,说低呢,也不低,因为官方编译的版本,很少集成第三方库,如果你要用proto buffer这种序列化库,就得自己集成自己编译,据我了解,大部分的人都得自己编译,因为proto buffer库的原因.说门槛高呢,也不高,因为作者写了一堆自动编译的脚本,你只需要点击运行.但是有两个值得注意的地方.一是编译工具的版本,尽量用作者指定的,不然出了问题够你折腾,还有就是编译的平台.Windows的库在Windows下面编译,ios的库在mac编译,而安卓的库,可以在linux,也可以在mac下面,我建议在mac编译安卓的库.

    2.LuaBehaviour

         LuaBehaviour是lua和Unity的交互脚本,在lua中也可以像MonoBehaviour脚本一样使用.LuaBehaviour,官方提供了一个例子,但只是告诉你一个实现思路,真要在项目中用起来,有些地方还得改进才行.

    官方例子:

    [csharp] view plain copy
    1. using UnityEngine;  
    2. using System.Collections;  
    3. using System.Collections.Generic;  
    4. using XLua;  
    5. using System;  
    6.   
    7. [System.Serializable]  
    8. public class Injection  
    9. {  
    10.     public string name;  
    11.     public GameObject value;  
    12. }  
    13.   
    14. [LuaCallCSharp]  
    15. public class LuaBehaviour : MonoBehaviour {  
    16.     public TextAsset luaScript;  
    17.     public Injection[] injections;  
    18.   
    19.     internal static LuaEnv luaEnv = new LuaEnv(); //all lua behaviour shared one luaenv only!  
    20.     internal static float lastGCTime = 0;  
    21.     internal const float GCInterval = 1;//1 second   
    22.   
    23.     private Action luaStart;  
    24.     private Action luaUpdate;  
    25.     private Action luaOnDestroy;  
    26.   
    27.     private LuaTable scriptEnv;  
    28.   
    29.     void Awake()  
    30.     {  
    31.         scriptEnv = luaEnv.NewTable();  
    32.   
    33.         LuaTable meta = luaEnv.NewTable();  
    34.         meta.Set("__index", luaEnv.Global);  
    35.         scriptEnv.SetMetaTable(meta);  
    36.         meta.Dispose();  
    37.   
    38.         scriptEnv.Set("self12"this);  
    39.         foreach (var injection in injections)  
    40.         {  
    41.             scriptEnv.Set(injection.name, injection.value);  
    42.         }  
    43.         scriptEnv.Set("transform", transform);  
    44.   
    45.         luaEnv.DoString(luaScript.text, "LuaBehaviour", scriptEnv);  
    46.   
    47.         Action luaAwake = scriptEnv.Get<Action>("awake");  
    48.         scriptEnv.Get("start"out luaStart);  
    49.         scriptEnv.Get("update"out luaUpdate);  
    50.         scriptEnv.Get("ondestroy"out luaOnDestroy);  
    51.   
    52.         if (luaAwake != null)  
    53.         {  
    54.             luaAwake();  
    55.         }  
    56.     }  
    57.   
    58.     // Use this for initialization  
    59.     void Start ()  
    60.     {  
    61.         if (luaStart != null)  
    62.         {  
    63.             luaStart();  
    64.         }  
    65.     }  
    66.       
    67.     // Update is called once per frame  
    68.     void Update ()  
    69.     {  
    70.         if (luaUpdate != null)  
    71.         {  
    72.             luaUpdate();  
    73.         }  
    74.         if (Time.time - LuaBehaviour.lastGCTime > GCInterval)  
    75.         {  
    76.             luaEnv.Tick();  
    77.             LuaBehaviour.lastGCTime = Time.time;  
    78.         }  
    79.     }  
    80.   
    81.     void OnDestroy()  
    82.     {  
    83.         if (luaOnDestroy != null)  
    84.         {  
    85.             luaOnDestroy();  
    86.         }  
    87.         luaOnDestroy = null;  
    88.         luaUpdate = null;  
    89.         luaStart = null;  
    90.         scriptEnv.Dispose();  
    91.         injections = null;  
    92.     }  
    93. }  

    一.lua脚本用TextAsset来保存是不行的,因为这种的话,就会把lua文件打包进prefab里面.lua和prefab需要解耦,那么保存一个lua文件名字是更好的办法.用到的时候,再根据名字加载.

    二.动态挂接这个脚本的问题,在prefab上静态挂接这个脚本没有这个问题,但是如果要在代码中动态挂接这个脚本就有问题,Awake初始化的时候,并没有设置lua脚本的名字,无法加载lua文件.解决办法有两种,一种是先隐藏挂脚本的游戏对象,挂上去后,设置好lua脚本名字再激活,这样的坏处是,隐藏和激活可能会影响脚本逻辑.另外一种完美的办法是,挂脚本后,自动调用的Awake和OnEnable跳过,设置好lua名字后,再手动调用

    [csharp] view plain copy
    1. public void Awake()  
    2.     {  
    3.         // 动态挂接LuaBehaviour,Awake调用的时候luaScriptName还未设置,是null,直接return,我们后续手动调用Awake  
    4.         if (string.IsNullOrEmpty(luaScriptName))  
    5.             return;  
    [csharp] view plain copy
    1. public void OnEnable()  
    2. {  
    3.     // 动态挂接LuaBehaviour,第一次OnEnable调用的时候luaScriptName还未设置,是null,直接return,我们后续手动调用第一次的OnEnable  
    4.     if (string.IsNullOrEmpty(luaScriptName))  
    5.         return;  

    lua代码封装的手动挂接脚本的函数:

    [csharp] view plain copy
    1.   
    function AddLuaBehaviour(go, luaScriptName, dontDestroyOnLoad)
    local behaviour = go:AddComponent(typeof(CS.LuaBehaviour))
    behaviour.luaScriptName = luaScriptName
    behaviour.dontDestroyOnLoad = dontDestroyOnLoad
    if go.activeSelf and go.activeInHierarchy then
    behaviour:Awake()
    behaviour:OnEnable()
    end
    return behaviour
    end
    [csharp] view plain copy
    1.   

    三.重复初始化LuaBehaviour的性能问题

            如果你给10个怪物挂上一个LuaBehaviour,关联的都是同样一个monster.lua的脚本,那么这10个怪物每次初始化的DoString都会编译monster.lua...这会带来没必要的性能开销,其实只需要编译一次.如果只编译一次呢,用LoadString来替代,缓存LoadString返回的LuaFunction,下次重复使用,使用的时候设置一下环境.

    [csharp] view plain copy
    1. // DoString  
    2. LuaFunction func = LoadString(luaScriptName, scriptEnv);  
    3. LuaDataMgr.setfenv(func, scriptEnv);  
    4. func.Call();  

    3.利用名称空间来自动配置属性

    Xlua需要配置属性的地方很多,比如[Hotfix],[LuaCallCSharp]和[CSharpCallLua],对于delegate的配置,我建议自动化,不然以后想用的时候才发现没配置,用不了就尴尬了.

    [csharp] view plain copy
    1. [CSharpCallLua]  
    2. public static List<Type> CSharpCallLua_Luoyinan  
    3. {  
    4.     get  
    5.     {  
    6.         Type[] types = Assembly.Load("Assembly-CSharp").GetTypes();  
    7.         List<Type> list = (from type in types  
    8.                            where type.Namespace == "Luoyinan"   
    9.                            && type.IsSubclassOf(typeof(Delegate))   
    10.                            select type).ToList();  

    4.C#调用lua的接口管理

    所有C#调用Lua的接口应该统一在一个类里面管理,这个类还应该实现一个缓存功能,防止每次调用都去从全局表Get.

    [csharp] view plain copy
    1. [CSharpCallLua]  
    2. public interface IMessageRegister  
    3. {  
    4.     bool HasMessage(int messageId);  
    5.     string GetMessageName(int messageId);  
    6.     void Register(int messageId);  
    7. }  
    [csharp] view plain copy
    1. private static IMessageRegister mIMessageRegister;  
    2. public static IMessageRegister iMessageRegister  
    3. {  
    4.     get  
    5.     {  
    6.         if (mIMessageRegister == null)  
    7.             mIMessageRegister = LuaBehaviour.luaEnv.Global.Get<IMessageRegister>("MessageRegister");  
    8.         return mIMessageRegister;  
    9.     }  
    10. }  

    5.hotfix热修复

    热修复主要遇到两个问题,一个是回调函数的使用,要用一个闭包封装一下,传self.


    1. --用闭包封装一下,用于需要传self的回调  
    2. function handler(obj, method)  
    3.     return function(...)  
    4.         if not method then  
    5.             print("method == nil " .. obj);  
    6.         end  
    7.         return method(obj, ...)  
    8.     end  
    9. end  

    一个是对hotfix函数的统一清除.如果你需要热重载lua,这个是很有必要的,

    1. --封装一下hotfix,增加记录功能,这样我们好统一清除hotfix  
    2. hotfixed = {}  
    3. local org_hotfix = xlua.hotfix  
    4. xlua.hotfix = function(cs, field, func)  
    5.     local tbl = (type(field) == 'table') and field or {[field] = func}  
    6.     hotfixed[cs] = tbl  
    7.     org_hotfix(cs, field, func)  
    8. end  
    9.   
    10. --清除所有hotfix  
    11. function clear_all_hotfix()  
    12.     for k, v in pairs(hotfixed) do  
    13.         for i, j in pairs(v) do  
    14.             xlua.hotfix(k, i, nil)    
    15.             print("clear_all_hotfix : ", i)  
    16.         end  
    17.     end  
    18.     hotfixed = {}  
    19. end  

    6.GC问题

           xlua上手还是很快的,但是要用好就没那么简单,要了解里面一些底层原理,才能避免一些坑,比如GC问题.lua是一门动态语言,函数参数可以任意类型,任意个数,返回值也可以任意类型,任意个数,在C#的接口可能要这么写:object[] Call(params object[] args),用object来转换,就会有boxing了.如何避免这种GC呢,只要明确参数类型和个数就行,一个个参数的压栈,调用完一个个返回值的取,具体来说,就是生成代码.加了[LuaCallCSharp]后,就可以生成代码了,但是你可能没把所有的代码都加上[LuaCallCSharp],这些没生成代码的,也能调用,会走反射调用,然后参数的传递,就是object[]这种.有大量GC.所以如果你有一个没生成代码的类(你觉得很少调用就没生成),但在Update里面每帧都调用了,哪怕只是一个property的访问,都会产生严重的gc.对于这种情况,我们要做的是用编辑器的profiler来查看GC情况,如果发现漏掉的,就赶紧加上[LuaCallCSharp]

         至于其他的调用怎么避免GC,请参考xlua文档.

    7.代码裁剪

          Unity引擎有个代码裁剪的选项,引擎没用到的接口,都会被裁减掉,优化效率.是否裁剪的标准,是看C#里面用到没,如果你lua用到了,但是C#没用到,也会被裁剪掉,因为C#这边不知道你lua用到了.如果是生成了代码的接口,不会被裁剪,因为用到了,但是那些反射调用的就可能会.如果要解决这个问题,可以加上[ReflectionUse],或者你关掉Unity的裁剪优化,我建议关掉裁剪优化,这样你在hotfix的时候,就可以调用引擎任何代码了.

    8.内存泄漏问题

         现在Unity主流的lua解决方案,不管是xlua,ulua,slua,如果使用不当,都潜在严重的内存泄漏风险,这不是危言耸听.这是lua和C#交互的设计原理引起的.

          C#对象在lua侧都是userdata,C#对象Push到lua,是通过dictionary将lua的userdata和C#对象关联起来的,这个dictionary起到一个缓存和查找的作用.只要lua中的userdata没回收,c# object也就会被这个dictionary拿着引用,导致无法回收。最常见的就是gameobject和component,如果lua里头引用了他们,即使你进行了Destroy,也会发现C#侧他们还残留着,这就是内存泄漏。想要立马清理干净,就得先手动调用lua gc,xlua才会把这个引用关系从dictionary里面去掉.

          理论上,lua会定期自动gc,来回收这个userdata吧,底层细节应该不需要我们上层的使用者来操心,但是这个自动gc并不靠谱,因为lua的增量gc是以lua的内存为参考,可能lua的内存只增加很少的情况下,C#那边的内存却增加了几十M.实际的使用情况也证明了这点,导致了大量的内存泄漏.

          所以,我能想到的办法就是手动管理,lua的自动gc不能知道C#侧的内存增量情况,但是我们知道啊,所以应该找一个合适的时机手动调用lua gc,再销毁C#对象,再调用C#的gc,比如切换场景的时候,或者关闭销毁一个UI界面的时候.

          如何发现自己的项目是否存在这种内存泄漏呢?监控这个dictionary就行了,xlua就是的ObjectTranslator类的reverseMap,如果你反复切换场景,这个reverseMap的数量一直在涨,那就发生内存泄漏了.

    9.性能问题

           lua的性能比C#差很多,但是真正影响性能的地方是过多地在lua中调用c#.在lua中引用一个C#对象的代价是昂贵的,如果有必要,可以封装一些接口减少这种调用,比如你在lua侧引用了一堆C#对象,然后计算好一个值,再设置回去.就不如直接封装一个简单的直接设置的接口.一般在lua的每帧调用的update函数中,应该做极致的性能优化,优化方法也会多,核心的优化原则就是减少C#对象的引用和一些参数的传递.比如你要给一个C#服务器对象设置位置,你直接在lua侧引用这个C#对象,再赋值回去,就不如封装一个设置位置的接口,传递serverId和位置x, y,z回去.具体的设置操作就在C#侧完成.

    10.lua加载

         单个lua文件的加载是同步加载,用到再加载和编译,代码相互require关联过多,就可能同时加载多个lua文件,引起卡顿的,因为你的lua文件是文本的,加载比较耗时.所以我们后来放弃这种方式了.

         如果打包成一个lua包,用lz4压缩格式,加载速度就快很多.打成一个lua包以后,还可以对包加密成一个二进制文件,再打包.

    加密包解包的时候,就需要用到AssetBundle.LoadFromMemory函数了

    [csharp] view plain copy
    1. AssetBundle ab = AssetBundle.LoadFromFile(bundlePath);  
    2. TextAsset textAsset = ab.LoadAsset<TextAsset>(BundleManager.luaAbPath.ToLower());  
    3. if (textAsset == null)  
    4. {  
    5.     LogSystem.DebugLog("decrypt. {0}包没这个文件: {1}", BundleManager.luaAbName, BundleManager.luaAbPath.ToLower());  
    6.     return null;  
    7. }  
    8. ab.Unload(false);  
    9. byte[] data = textAsset.bytes;  
    10. data = Util.Decrypt(data);  
    11. LuaBehaviour.mCacheAb = AssetBundle.LoadFromMemory(data);  
    展开全文
  • XLUA

    2018-12-12 21:32:00
    在Unity3D项目中,逻辑代码热更新这一块,现在有很多实现解决方案,基本都是借助Lua来实现的,在这众多之中,最后还是选择xLua,最早了解xLua是在腾讯的手游项目中,腾讯Apollo通用组件中,只是涉及的项目中,并没有...

    原文 转载请注明保留原文链接:http://www.jianshu.com/p/dc4de5612d9e

    作者:Jumbo

    在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年万事如意,身体健康!!!



    作者:JumboWu
    链接:https://www.jianshu.com/p/dc4de5612d9e
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    转载于:https://www.cnblogs.com/hpu001/p/10111168.html

    展开全文
  • xLua

    2017-05-27 19:33:00
    http://www.2cto.com/kf/201701/591817.html 转载于:https://www.cnblogs.com/lilei9110/p/6914242.html

    http://www.2cto.com/kf/201701/591817.html

     

    转载于:https://www.cnblogs.com/lilei9110/p/6914242.html

    展开全文
  • xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。 xLua的突破 xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是: 可以运行时把C#实现...
  • xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。 xLua的突破 xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是: 可以运行时把C#实现...
  • [Unity XLua]热更新XLua入门(一)-基础篇

    万次阅读 多人点赞 2017-01-14 12:26:03
    Aladdin_XLua前言前段时间腾讯开源了一个内部热更框架XLua在Unity开发群里引起一阵热议,也受到广大开发者的热捧,然后我当然也抱着好奇的心去学习学习。后面也会将扩展之后的工程放在git上,大家一起学习交流!在此...

    无意中发现了一个巨牛巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,小白也能学,而且非常风趣幽默,还时不时有内涵段子,像看小说一样,哈哈~我正在学习中,觉得太牛了,所以分享给大家。点这里可以跳转到教程!

    更多精品文章

    http://dingxiaowei.cn/ (手动复制到浏览器)

    Aladdin_XLua

    前言

    前段时间腾讯开源了一个内部热更框架XLua在Unity开发群里引起一阵热议,也受到广大开发者的热捧,然后我当然也抱着好奇的心去学习学习。后面也会将扩展之后的工程放在git上,大家一起学习交流!在此感谢XLua作者创造出这么好用的框架!

    相关链接

    1. XLua源码

    2. C#->Lua开源工具
      可以将C#转化成lua并且具有

    3. 相关介绍文章
      https://www.oschina.net/news/80638/c-net-lua-unity3d

    4. 知乎热议

    更多教程

    http://dingxiaowei.cn

    个人对XLua看法

    1. 简洁易用,容易上手
    2. 可扩展性高,添加自定义的CS模块或者第三方插件非常方便
    3. 大厂维护,可靠
    4. 特色:HotFix
      关于这个HotFix是其他热更lua框架所不具备的,也是他最大的优势和特色之一,原理就是通过特性标记然后在IL逻辑层判断修改逻辑,使程序支持热更的lua逻辑代码而不是走之前的C#逻辑

    自己扩展XLua支持NGUI开发

    现在开源热更Lua框架都很少支持NGUI了,可能现在趋势都是用原生的UGUI,但估计还有一些NGUI粉喜欢用NGUI开发,毕竟NGUI用了很长时间,XLua例子里面已经支持了lua使用UGUI,这里我就自己补充让它支持NGUI开发。后续我也会多添加一些UGUI的例子。先看看扩展的NGUI做的界面效果图,然后下面再讲解怎么让XLua支持第三方插件。

    效果图

    这里写图片描述

    快速上手

    在学习一个东西之前最好先学习一下XLua作者辛辛苦苦写的那些多教程文档,包括案例以及wiki和issu,如果还有什么不明白的地方可以在加入文章最后的群我们一起交流学习。

    1.自定义C#类供Lua访问

    这里可以学习一下作者写的Test中的例子,还是比较详细,但我还是想记录一下我自己尝试的过程。

    (1)特性方式

    XLua支持使用特性标签让自定义的类可供Lua访问
    C#:

    public class CSModelWithAttribute
    {
    	public static void SayHello1()
    	{
    		Debug.Log("Hello Aladdin_XLua, I am static model function");
    	}
    	public void SayHello2()
    	{
    		Debug.Log("Hello Aladdin_XLua, I am model function");
    	}
    	public void SayHello3(string s)
    	{
    		Debug.Log("Hello Aladdin_XLua, I am model function whih param:" + s);
    	}
    
    	public string SayHello4(string s)
    	{
    		Debug.Log("Hello Aladdin_XLua, 我是具有返回值的CS方法:" + s);
    		return "你好,我获得了lua,我是C#";
    	}
    
    	public void SayHelloWithRefParam(ref string s)
    	{
    		Debug.Log("传入的参数是:" + s);
    		s = "Hello 我是C#";
    	}
    
    	public string SayHelloWithRefParamAndReturnString(ref string s)
    	{
    		Debug.Log("传入的参数是:" + s);
    		s = "Hello 我是C#";
    		return "我是返回的字符串";
    	}
    
    	public void SayHelloWithOutParam(out string s)
    	{
    		s = "Hello,我是C#";
    		Debug.Log("Hello Aladdin_XLua, I am model function whih out param:" + s);
    	}
    }
    

    添加上特性标签之后,程序在启动的会自动查找具有特性标记的类然后搜集进入lua栈,使得我们可以用Lua访问自定义的C#类和方法。

    Lua访问:

    using UnityEngine;
    using System.Collections;
    using XLua;
    public class SelfExampleSrc : MonoBehaviour
    {
    	LuaEnv luaenv = new LuaEnv();
    	void Start()
    	{
    		luaenv.DoString(@"
    
    			print('Lua访问特性标记的对象方法')
    			local luaM2 = CS.CSModelWithAttribute
    			local luaO2 = luaM2()
    			luaM2:SayHello1()
    			luaO2:SayHello2()
    			luaO2:SayHello3('我是阿拉丁')  --读者反馈新增一个C#方法 PC不需要重新 Generate,安卓、ios需要
    
    			--测试字符串返回
    			local value = luaO2:SayHello4('你好,我是lua')
    			print(value)                              
    			
    			--测试ref
    			local inputValue = '你好,我是lua'
    			local outputValue = luaO2:SayHelloWithRefParam(inputValue)
    			print(outputValue)                          --lua是通过字符串返回
    
    			local outValue1,outValue2 = luaO2:SayHelloWithRefParamAndReturnString(inputValue)
    			print(outValue1)
    			print(outValue2)
    			
    			--测试out
    			inputValue = '我是测试lua'
    			outputValue = luaO2:SayHelloWithOutParam(inputValue)
    			print(outputValue)
    			
    			local luaM3 = CS.CSModelTest
    			local luaO3 = luaM3()
    			luaO3.TestDelegate('lua中测试委托')
    
    
    			luaO3.onClick = function(obj)
    				print('hello 我是lua')
    				print(obj)
    			end
    			luaO3.onClick('我是lua')
    		");
    	}
    }
    
    
    (2)wrap方式

    如果是我们自己写的C#代码,我们可以通过第一种方式添加特性来让Lua支持也是比较方便,如果是用的开源第三方插件,我们如何快速的让XLua支持,就可以用过Generate Wrap的方式,这一点也是其他lua框架所采取的策略。

    a)有命名空间的类

    C#:

    namespace Aladdin_XLua
    {
    	public class CSModel
    	{
    		public void SayHello()
    		{
    			Debug.Log("Hello Aladdin_XLua");
    		}
    	}
    }
    

    Lua:

    print('Lua访问有命名空间的对象/静态方法')
    local luaModel = CS.Aladdin_XLua.CSModel
    local luaObj = luaModel()
    luaObj:SayHello()
    

    luaModel是C#类,下面luaObj是类生成的对象,最后是访问对象方法,关于Lua冒号调用和点调用的区别:冒号调用默认传入了self参数,不清楚的可以百度相关文章。

    b)无命名空间的类

    C#:

    public class CSModelWidhoutNameSpace
    {
    	public void SayHello()
    	{
    		Debug.Log("Hello Aladdin_XLua without Namespace");
    	}
    }
    

    Lua:

    print('Lua访问无命名空间的对象方法')
    local luaM = CS.CSModelWidhoutNameSpace
    local luaO = luaM()
    luaO:SayHello()
    

    如果没有命名空间的话直接CS后面就是类名,其实CS也是一个更外面一层的命名空间,只不过是作者帮我们分装的。

    3)委托类型

    C#:

    public class CSModelTest
    {
    	public SelfVoidDelegate onClick;
    	public delegate void SelfVoidDelegate(GameObject go);
    	void OnClick() { Debug.Log("测试"); }
    
    	public Action<string> TestDelegate = (param) =>
    	{
    		Debug.Log("TestDelegate in c#:" + param);
    	};
    }
    

    委托其实也是跟Class平级的,委托也是一种类型,所以我们也需要对它像对待类那样处理,通过添加特性标记或者通过Wrap方式处理,这里委托是放在类里面,其实也可以直接放在命名空间下面,.NET库是这样操作的,但我们看NGUI源码会发现,NGUI源码都是这样操作的,比如按钮的onClick事件,看它的委托类型VoidDelegate就会发现也是这样操作的,所以我这里例子也放在类的里面。

    C#

    public class CSModelTest
    {
    	public SelfVoidDelegate onClick;
    	public delegate void SelfVoidDelegate(GameObject go);
    	void OnClick() { Debug.Log("测试"); }
    
    	public Action<string> TestDelegate = (param) =>
    	{
    		Debug.Log("TestDelegate in c#:" + param);
    	};
    }
    

    Lua:

    local luaM3 = CS.CSModelTest
    local luaO3 = luaM3()
    luaO3.TestDelegate('lua中测试委托')
    
    luaO3.onClick = function(obj)
    	print('hello 我是lua')
    	print(obj)
    end
    luaO3.onClick('我是lua')
    
    4)带有ref out 参数的函数如何处理

    因为Lua是弱类型没有C#那么多类型,有时候一些参数可能就不太好处理,比如C#的不同类型参数的重载,lua就不太好处理,这里可以查看XLua中的issues,作者有一个问题的相关解答。下面我举例ref和out参数类型的函数Lua如何访问。
    C#:

    public void SayHelloWithRefParam(ref string s)
    {
    	Debug.Log("传入的参数是:" + s);
    	s = "Hello 我是C#";
    }
    
    public string SayHelloWithRefParamAndReturnString(ref string s)
    {
    	Debug.Log("传入的参数是:" + s);
    	s = "Hello 我是C#";
    	return "我是返回的字符串";
    }
    
    public void SayHelloWithOutParam(out string s)
    {
    	s = "Hello,我是C#";
    	Debug.Log("Hello Aladdin_XLua, I am model function whih out param:" + s);
    }
    
    

    Lua:

    --测试ref
    local inputValue = '你好,我是lua'
    local outputValue = luaO2:SayHelloWithRefParam(inputValue)
    print(outputValue)                          --lua是通过字符串返回
    
    local outValue1,outValue2 = luaO2:SayHelloWithRefParamAndReturnString(inputValue)
    print(outValue1)
    print(outValue2)
    			
    --测试out
    inputValue = '我是测试lua'
    outputValue = luaO2:SayHelloWithOutParam(inputValue)
    print(outputValue)
    

    一开始我测试的时候是本以为lua调用ref传入的参数,也会返回出修改的结果,但出乎我的意料,并没能修改,经过作者提示,lua是通过返回值返回的ref参数,如果函数本身就有返回值,那么最后一个参数是返回的ref或者out参数,这个读者可以尝试一下。

    运行结果

    这里写图片描述

    关于Wrap

    Wrap是C#跟Lua之间的一个桥梁,Lua想要访问C#必须要用过Wrap访问,相信看过其他Lua框架的这一点应该不陌生,XLua对生成Wrap也是非常方便。

    我们只要新建一个类然后继承一个GenConfig的接口,下面是接口内容,关于这几个类型XLua文档中也有介绍,我们只需要把自定义的类添加到LuaCallCSharp集合中即可,然后点击Generate就会自动帮我们生成对应的Wrap文件

    //注意:用户自己代码不建议在这里配置,建议通过标签来声明!!
        public interface GenConfig 
        {
            //lua中要使用到C#库的配置,比如C#标准库,或者Unity API,第三方库等。
            List<Type> LuaCallCSharp { get; }
    
            //C#静态调用Lua的配置(包括事件的原型),仅可以配delegate,interface
            List<Type> CSharpCallLua { get; }
    
            //黑名单
            List<List<string>> BlackList { get; }
        }
    

    当然作者也说了,我们自定的C#代码最好不要通过这种方式,我这里只是演示如何添加,下面会说第三方插件通过这话总方式支持。
    C#:

    public static class AladdinGenConfig
    {
    	//lua扩展第三方或者自定义类库
    	public class LuaCallCSharpExtern : GenConfig
    	{
    
    		public List<Type> LuaCallCSharp
    		{
    			get
    			{
    				return new List<Type>()
    				{
    					typeof(CSModelWidhoutNameSpace),
    					typeof(CSModel),
    					typeof(CSModelTest),
    				}
    			}
    		}
    	}
    }
    

    2.NGUI扩展

    正如上图所示的效果,下面讲述一下我是如何支持NGUI扩展的,也参考了作者UGUI的一个例子修改的。

    a)生成Wrap接口

    这一步上上面说的一样,只要把NGUI的组件类全部都添加到LuaCallCSharp列表中然后Generate一下即可,这里要注意的是组件中委托类型也需要添加进去。

    b)搭建两个UI界面,UI逻辑接口用C#,Lua是调用逻辑调用界面中C#的方法。

    这里写图片描述
    这里写图片描述

    C#:
    购买

    using UnityEngine;
    using System.Collections;
    using XLua;
    public class AsyncBuy : MonoBehaviour
    {
    	LuaEnv luaenv = null;
    
    	void Start()
    	{
    		luaenv = new LuaEnv();
    		luaenv.DoString("require 'async_buy'");
    	}
    
    	// Update is called once per frame
    	void Update()
    	{
    		if (luaenv != null)
    		{
    			luaenv.Tick();
    		}
    	}
    }
    
    

    Panel逻辑:

    using UnityEngine;
    using UnityEngine.UI;
    using XLua;
    using System.Collections.Generic;
    using System;
    using UnityEngine.Events;
    
    public class MessagePanel : MonoBehaviour
    {
    	/// <summary>
    	/// 显示对话弹框
    	/// </summary>
    	/// <param name="message"></param>
    	/// <param name="title"></param>
    	/// <param name="onFinished"></param>
    	public static void ShowAlertPanel(string message, string title, Action onFinished = null)
    	{
    		Debug.Log("显示提示弹框");
    		var rootPanel = GameObject.Find("Panel").transform;
    		var alertPanel = rootPanel.Find("AlertPanel");
    		if (alertPanel == null)
    		{
    			alertPanel = (Instantiate(Resources.Load("AlertPanel")) as GameObject).transform;
    			alertPanel.gameObject.name = "AlertPanel";
    			alertPanel.SetParent(rootPanel);
    			alertPanel.localPosition = Vector3.zero;
    			alertPanel.localScale = Vector3.one;
    		}
    		alertPanel.Find("Title").GetComponent<UILabel>().text = title;
    		alertPanel.Find("Content").GetComponent<UILabel>().text = message;
    		alertPanel.gameObject.SetActive(true);
    		if (onFinished != null)
    		{
    			var buyBtn = alertPanel.Find("BtnBuy").gameObject;
    			buyBtn.SetActive(true);
    			var button = buyBtn.GetComponent<UIButton>();
    			UIEventListener.Get(buyBtn).onClick = go =>
    			{
    				onFinished();
    				alertPanel.gameObject.SetActive(false);
    			};
    		}
    	}
    
    	/// <summary>
    	/// 显示确认弹框
    	/// </summary>
    	/// <param name="message"></param>
    	/// <param name="title"></param>
    	/// <param name="onFinished"></param>
    	public static void ShowConfirmPanel(string message, string title, Action<bool> onFinished = null)
    	{
    		var rootPanel = GameObject.Find("Panel").transform;
    		var confirmPanel = rootPanel.Find("ConfirmPanel");
    		if (confirmPanel == null)
    		{
    			confirmPanel = (Instantiate(Resources.Load("ConfirmPanel")) as GameObject).transform;
    			confirmPanel.gameObject.name = "ConfirmPanel";
    			confirmPanel.SetParent(rootPanel);
    			confirmPanel.localPosition = Vector3.zero;
    			confirmPanel.localScale = Vector3.one;
    		}
    		confirmPanel.Find("Title").GetComponent<UILabel>().text = title;
    		confirmPanel.Find("Content").GetComponent<UILabel>().text = message;
    		confirmPanel.gameObject.SetActive(true);
    		if (onFinished != null)
    		{
    			var confirmBtn = confirmPanel.Find("BtnBuy").GetComponent<UIButton>();
    			var cancelBtn = confirmPanel.Find("CancelBuy").GetComponent<UIButton>();
    
    			UIEventListener.Get(confirmBtn.gameObject).onClick = go =>
    			{
    				onFinished(true);
    				confirmPanel.gameObject.SetActive(false);
    			};
    
    			UIEventListener.Get(cancelBtn.gameObject).onClick = go =>
    			{
    				confirmPanel.gameObject.SetActive(false);
    			};
    		}
    	}
    }
    

    Lua:
    lua文件放在对应的Resources下即可
    async_buy.lua

    
    local util = require 'xlua.util'
    local message_panel = require 'message_panel'
    
    -------------------------async_recharge-----------------------------
    local function async_recharge(num, cb) --模拟的异步充值
        print('requst server...')
        cb(true, num)
    end
    
    local recharge = util.async_to_sync(async_recharge)
    -------------------------async_recharge end----------------------------
    local buy = function()
        message_panel.alert("余额提醒","您余额不足,请充值!")
    	if message_panel.confirm("确认充值10元吗?","确认框" ) then
    		local r1, r2 = recharge(10)
    		print('recharge result', r1, r2)
    		message_panel.alert("提示","充值成功!")
    	else
    	    print('cancel')
    	    message_panel.alert("提示","取消充值!")
    	end
    end
    
    CS.UIEventListener.Get(CS.UnityEngine.GameObject.Find("BtnBuy").gameObject).onClick = util.coroutine_call(buy)
    
    

    message_panel.lua

    
    local util = require 'xlua.util'
    
    local sync_alert = util.async_to_sync(CS.MessagePanel.ShowAlertPanel)
    local sync_confirm = util.async_to_sync(CS.MessagePanel.ShowConfirmPanel) 
    
    --构造alert和confirm函数
    return {
        alert = function(title, message)
    		if not message then
    			title, message = message, title
    		end
    		 sync_alert(message,title)
        end;
    	
    	confirm = function(title, message)
    		local ret = sync_confirm(title,message)
    		return ret == true
        end;
     }
    

    运行的结果就如第一张图所示

    后续计划

    • 添加资源热更
    • 添加一个小游戏Demo
    • 添加UGUI的案例

    欢迎加群交流

    1.unity游戏开发群
    QQ群
    unity3d unity 游戏开发

    2.专门探讨XLua的程序群:437645698


    下载地址

    https://git.oschina.net/dingxiaowei/Aladdin_XLua.git
    Github同步:https://github.com/dingxiaowei/Aladdin_XLua
    关注后续更新请点start或者fork,感谢!

    更多内容 欢迎访问独立博客 http://dingxiaowei.cn

    展开全文
  • XLua_2018最新版

    2018-12-26 18:20:38
    * (必看)[XLua教程](Assets/XLua/Doc/XLua教程.md):教程,其配套代码[这里](Assets/XLua/Tutorial/)。 * (必看)[XLua的配置](Assets/XLua/Doc/configure.md):介绍如何配置xLua。 * [热补丁操作指南](Assets/XLua/...
  • unity代码热更神器xLua

    2020-07-22 10:13:26
    - 将`xlua/Assets/`目录拷贝到工程中的`Assets/`目录 - 将`xlua/Tools`目录拷贝到工程根目录 - 在 Build Settings中添加宏 `HOTFIX_ENABLE` - 项目中如果有更新C#代码,则需要 使用 `XLua/Generate Code`重新生成 - ...
  • 本课程为系列课程的xlua基础部分,本课程主要就xlua的优势、特点、环境搭建、以及lua文件加载、C#调用lua的各种方式、lua调用C#的各种技巧展开讨论与讲解,学后基本掌握xlua中常用的技术基础,为后续课程打下坚实的...
  • XLua_FrameWork

    2018-10-12 10:34:24
    基于XLua,整合ToLua的Proto-gen-lua,以及大部分ToLua作者整合的库,除了Assetbundle模块,大部分游戏逻辑全部用lua实现,目前实现的内容包括:  UI管理模块:使用UGUI,基于MVC架构,View层实现一套组件管理系统...
  • xlua framework

    2019-01-25 22:48:43
    xlua framework及文档介绍,包括excel转xlua工具,转pb工具,pb解析,网络请求,xlua UI框架等完整的游戏框架
  • xLua 简介

    千次阅读 2017-09-09 08:21:38
    xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。 Unity3D 下Lua编程支持 xLua为Unity3D增加Lua脚本能力,进而提供代码逻辑增量更新的可能,当然不仅仅...
  • XLuaXLua项目中的配置

    千次阅读 2018-09-10 14:51:16
    1、将Xlua Assets中的内容copy到目标项目的Assets内 2、将XLua内的Tools复制到项目Assets外 3、打开项目,在项目的Build Settings——Other Settings——Scripting Define Symbols填入:HOTFIX_ENABLE 4、将本地...
  • Xlua 入门实践

    2017-01-15 20:48:08
    xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在 腾讯已经将xLua开源到GitHub。
  • XLua.ObjectTranslator.getDelegate (XLua.DelegateBridgeBase bridge, System.Type delegateType) (at Assets/XLua/Src/ObjectTranslator.cs:376) XLua.ObjectTranslator.CreateDelegateBridge (IntPtr L, System....
  • 前言在xLua没出来之前,开源的lua框架基本都是以界面用Lua开发为主,核心战斗用C#开发,但xLua出来之后主推C#开发,Lua用作HotFix,这里我展示的第一个例子就是基于界面的经典2D小游戏——俄罗斯方块,界面逻辑是用...
  • xLua导出Wrap白名单

    2017-10-17 13:46:02
    在用ulua项目开发的后期,遇到一个很严重的情况: ios对可执行文件的可执行文件 size进行了限制,...针对这种情况,对项目的xlua进行了wrap导出优化,详见说明: http://blog.csdn.net/q725922/article/details/78258754
  • XLua官方教程

    2018-06-08 15:21:20
    xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。
  • xLua框架源码

    2018-09-16 15:52:05
    xLua源码,使用方法 将Assets文件夹导入工程测试 详细请看文件中ReadMe.md
  • xlua-master

    2018-11-04 16:39:12
    xlua的插件,把Assets里面的东西全部复制到工程的Assets下
  • XLua官方文档 Xlua特性

    2020-10-12 11:52:11
    特性 总体 Lua虚拟机支持 Lua5.3 Luajit2.1 Unity3D版本支持 各版本均支持 平台支持 windows 64/32 ...ios 64/32/bitcode ...配置可以多份,按模块划分,也可以直接在目标类型上打Attribute标签 ...Plugins部分采用cmake编译...
  • xLua.zip 压缩包

    2020-06-30 11:50:04
    Unity3D下Lua编程支持 xLua为Unity3D增加Lua脚本编程的能力,进而提供代码逻辑增量更新的可能。当然不仅仅如此,在coco2dx上的实践告诉我们,以Lua为主打语言的游戏客户
  • XLua心得

    万次阅读 2018-04-19 09:49:58
    xlua简介 xlua是由腾讯维护的一个开源项目,除了常规的Lua绑定之外,还有一个比较有特色的功能就是代码热补丁。非常适合前期没有规划使用Lua进行逻辑开发,后期又需要在iOS这种平台获得代码热更新能力的项目。 刚...

空空如也

1 2 3 4 5 ... 20
收藏数 1,826
精华内容 730
关键字:

xlua