Xlua

article/2025/9/28 2:41:01

有一个项目做完快上线了,不是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.


[cpp]  view plain copy
  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,这个是很有必要的,

[cpp]  view plain copy
  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);  

http://chatgpt.dhexx.cn/article/HMDiAStK.shtml

相关文章

xLua热更新(二)实现热更新

一、环境配置 要实现热更新功能&#xff0c;我们首先需要开启热更新的宏。操作方法是在「File->Build Settings->Player Settings->Player->Other Settings->Scripting Define Symbols」选项中添加HOTFIX_ENABLE 开启后&#xff0c;在xLua的菜单中就出现了「…

Unity 热更新技术 |(六)xLua框架学习最新系列完整教程

🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏制作专栏推荐:游戏制作 🌲Unity实战100例专栏推荐:Unity 实战100例 教程 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬…

xLua(九)——实战

一&#xff1a;使用xLua的步骤 ——导入xLua插件 其实xLua本质就是一个Unity工程&#xff0c;把Asset中的文件导入到Unity工程中就搞定了&#xff08;导入之后编辑器菜单栏会扩展出一个XLua选项&#xff09; ——添加宏File——Build Settings——Player Settings——Other Se…

【XLua】简单使用

文章目录 前言1 配置1.1 配置宏1.2 XLua配置 2 lua和C#相互调用2.1 XLua.CSharpCallLua2.2 XLua.LuaCallCSharp 3 加载器 前言 XLua本质上是为Unity提供了使用lua的能力&#xff0c;实际多用于热更新。 热更新&#xff0c;因为要给代码打标签才能生效&#xff0c;所以需要预测…

xLua介绍

xLua地址&#xff1a;传送门 Xlua是啥&#xff1f; 2016年 腾讯推出的 一种 unity下 lua 编成的解决方案 基本概念介绍&#xff1a; 1.模块 模块就是一个 程序库&#xff0c;可以通过 require 加载&#xff0c;得到了一个表示 table的全局变量 这个table 就像一个命名空间&am…

Lua快速入门篇(XLua教程)(Yanlz+热更新+xLua+配置+热补丁+第三方库+API+二次开发+常见问题+示例参考)

《Lua热更新》 ##《Lua热更新》发布说明&#xff1a; “Lua热更新”开始了&#xff0c;立钻哥哥终于开始此部分的探索了。 作为游戏发布迭代的重要技术&#xff1a;Lua热更新在网络游戏迭代更新中非常重要&#xff0c;特别是对于AppStore这样的平台&#xff0c;我们只需要定…

XLua加载

XLua加载lua文件的方式 LuaEnv.DoString(print("hello world")); //直接执行lua的语句&#xff0c;在函数体内的语句格式要符合lua的语法 LuaEnv.DoString("require byfile")//使用require lua文件名也可在unity中加载lua 但是在unity中需要把文件放置在…

XLua系列讲解_Helloworld

一、XLua简介 XLua是Unity3D下Lua编程解决方案&#xff0c;自2016年初推广以来&#xff0c;已经应用于十多款腾讯自研游戏&#xff0c;因其良好性能、易用性、扩展性而广受好评。现在&#xff0c;腾讯已经将xLua开源到GitHub。 二、Xlua的优点 简洁易用&#xff0c;容易上手可…

Unity XLua Hotfix热更新配置笔记

Unity XLUA Hotfix热更新配置笔记 目录 Unity XLUA Hotfix热更新配置笔记 配置热更新步骤&#xff1a; 下载XLUA下载压缩包解压 复制xlua 和plugins到assets开启热补丁特性 先添加宏 HOTFIX_ENABLE;INJECT_WITHOUT_TOOL报“This delegate/interface must add to CSharpCallLu…

xLua热更新(一)xLua基本使用

一、什么是xLua xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力&#xff0c;借助xLua&#xff0c;这些Lua代码可以方便的和C#相互调用。 xLua是用来实现Lua代码与C#代码相互调用的插件。我们可以借助这个插件来实现热更新方案。 那么为什么要选择Lua实现热更新呢&am…

Bug-CTF-秋名山老司机(正则匹配)

题目: 没有啥思路&#xff0c;意外地刷新了一下页面&#xff0c;发现数值变化了 再刷新一次试试&#xff0c;出来一个提示&#xff0c;大概意思是需要提交结果&#xff0c;这里也不知道该怎么传参&#xff0c;也不晓得怎么写这个脚本&#xff0c;只能参考其他大佬的思路了 解题…

BUGKU------秋名山老司机

看到这个就直接上python吧&#xff0c;用eval计算子式 import requests from bs4 import BeautifulSoup r requests.session() s r.get(http://123.206.87.240:8002/qiumingshan/) soup BeautifulSoup(s.text, "html.parser") a soup.find(div) d {"valu…

bugku秋名山车神

不断的刷新&#xff0c;发现表达式一直在变换&#xff0c;这种必须写脚本&#xff0c;才能跟上速度。直接上代码 import re import requests srequests.session() rs.get("http://123.206.87.240:8002/qiumingshan/") searchObj re.search(r^<div>(.*)\?;<…

【BugkuCTF】Web--秋名山老司机

Description: http://123.206.87.240:8002/qiumingshan/ 是不是老司机试试就知道。 Solution: 打开网页 2秒解决问题真是稳稳的写脚本……但是不知道提交啥&#xff0c;刷新网页看看提示让用POST方式传递一个value变量&#xff0c;构造脚本 import requests import re url htt…

CTF-web-秋名山老司机

前言&#xff1a;小编也是现学现卖&#xff0c;方便自己记忆&#xff0c;写的不好的地方还请包涵&#xff0c;也欢迎各位大佬多多批评指正。 网址&#xff1a;秋名山老司机 1.打开网址&#xff0c;提示需要两秒内计算出数值&#xff0c;手工几乎不可能实现。 2.思路:利用pyt…

秋名山车神

解题思路&#xff1a;看到这种题要在两秒类算出&#xff0c;人工肯定不可能&#xff0c;直接上脚本&#xff0c;由于我不会写python 脚本&#xff0c;直接在网上找了一篇大佬的脚本 import requests #安装requests库 import re url http://114.67.246.176:16847 #改为自己题…

爬虫笔记-Bugku秋名山老司机(入门)

记一次python爬虫笔记 题目&#xff1a;bugku-秋名山老司机 题目要求&#xff1a;两秒内提交一道很长的计算题答案&#xff0c;并且式子每次刷新都会变动 如&#xff1a; 多刷新几次可见题目提示&#xff0c;需要用post传入值&#xff0c;变量名为value 创建py文件&#xf…

ctf靶场-bugku-秋名山老司机,速度要快

页面快速计算(秋名山老司机) 1.靶场网址 http://123.206.87.240:8002/qiumingshan/ 2.脚本实现 import requests import re url"http://123.206.87.240:8002/qiumingshan/" srequests.Session() #储存session rs.get(url) #用此身份执行get请求 searchobjre.sea…

[bugku]-秋名山车神详解

解题 每一次刷新都不一样 post传参value 脚本1 import requests import re url http://114.67.175.224:10053/ s requests.Session() source s.get(url) expression re.search(r(\d[\-*])(\d), source.text).group() result eval(expression) post {value: result} prin…

Bugku_Web18_秋名山车神

1.查看源码 <head> <title>下面的表达式的值是秋名山的车速</title> <meta charset"UTF-8"> </head> <p>亲请在2s内计算老司机的车速是多少</p> <div>373719747-1878154638-1233431774-1476346255*1056350133121800…