找回密码
 立即注册
查看: 385|回复: 0

XLua接入笔记

[复制链接]
发表于 2022-3-15 12:17 | 显示全部楼层 |阅读模式
近期项目升级到Unity2018之后,需要使用IL2CPP进行打包,导致之前使用的直接替换 .dll 的更新方式无法继续使用,决定接入XLua进行热更,此篇文章做个简单的爬坑记录。
目录
一、更换热更方式的原由
二、选择热更框架
三、XLua导入工程
四、XLua接入
        1、luaconfig
        2、pdf重写
        3、Bridge桥接类
五、坑点梳理(持续更新)
        1、事件使用
        2、pdf增加xlua交互
        3、报错:xlua.access, no field __Hitfix0_xxx
        4、打包Bundle报错:'light' does not contain a definition for 'SetLightDirty' and no accessible extension method 'SetLightDirty'
        5、Lua中的与或非:and   or   not

一、更换热更方式的原由

        由于需要使用IL2CPP进行出包,但IL2CPP中已经不包含Mono虚拟机,而是在脚本被编译成IL之后,直接转变为C++,再由平台从C++转成自己的汇编语言进行使用,所以导致原先可以直接替换的 .dll 的方式无法支持,所以,在这种情况下,进行了热更方式更换。
IL2CPP详解:https://zhuanlan.zhihu.com/p/19972689
二、选择热更框架

        这步在最终选用了XLua进行嵌入,无他,只是因为公司的其他项目组有使用经验,并且也是目前为止使用最多的方式。期间对比了ILRuntime,但调研结果显示,使用ILRuntime需要的时间周期和爬坑量都比较大,在时间有限的情况下,XLua成了最优解。
XLua和ILRuntime的区别:最新 XLua 与 ILRuntime 性能对比_Passion 的博客-CSDN博客_xlua和ilruntime
三、XLua导入工程

        XLua的接入相对简单,只需要去GitHub下载XLua框架,按照教程导入工程中,基本都可以直接进行基础测试。
XLua GitHub:https://github.com/Tencent/xLua
        这次也是初次真正使用XLua,所以在接入初期学习了下Lua的基础语法,其中表(Table)和元表(metaTable)是比较重要的概念
Lua语法教程:Lua 教程 | 菜鸟教程
        在以上的基础上,根据XLua官方给出的教程,逐步进行测试,测试完成之后,真正进入了将XLua嵌入项目的操作。
四、XLua接入

        1、luaconfig

        配置LuaConfig,需要根据项目自身需要,将相应的 .dll 库(包括所需的第三方库)配入,这里强烈推荐使用动态列表,这样可以避免在所有需要的脚本上加标签。
  1. [Hotfix]public static List<Type> DynamicDllList
  2.     {
  3.         get
  4.         {
  5.             List<Type> all = new List<Type>();
  6.          
  7.             List<Type> slefAssembly = GetTypesForAssembly("Assembly-CSharp");
  8.            
  9.             all.AddRange(slefAssembly);
  10.            
  11.             return all;
  12.         }
  13.     }
复制代码
       白名单按照需要根据教程写基本没问题。
        黑名单可以直接使用Demo中的配置,额外添加项目中特殊的即可。
        在这里有一个比较坑的地方,有更好的解决方案的大佬可以在评论区留言。由于自定义的delegate和action无法使用动态配置导入,所以需要手动的一个个的添加。这里主要一定要加标签【CSharpCallLua】
  1. [CSharpCallLua]
  2.     public static List<Type> CSharpCallLuaList
  3.     {
  4.         get
  5.         {
  6.             return new List<Type>
  7.             {
  8.                 typeof(EventDemo.TestDelegate),
  9.                 typeof(Func<double>),
  10.                 typeof(Func<string>),
  11.                 typeof(Func<bool>),
  12.                 typeof(Func<int>),
  13.                 typeof(Func<uint>),
  14.                 typeof(Func<uint, string>),
  15.                 typeof(Func<double, double>),
  16.                 typeof(Func<double, double, double>),
  17.                 typeof(Func<int, string, List<byte>>),
  18.                 typeof(Action),
  19.                 typeof(Action<object>),
  20.                 typeof(Action<double>),
  21.                 typeof(Action<string>),
  22.                 typeof(Action<int>),
  23.                 typeof(Action<int,int>),
  24.                 typeof(Action<uint>),
  25.                 typeof(Action<uint,uint>),
  26.                 typeof(Action<double, double>),
  27.                 typeof(Action<double, double, double>),
  28.                 typeof(Action<int, string>),
  29.                 typeof(Action<float>),
  30.                 typeof(UnityAction),
  31.                 typeof(IEnumerator),
  32.                 typeof(UIEventListener.VoidDelegate),
  33.                 typeof(UIEventListener.BoolDelegate),
  34.                 typeof(UIEventListener.FloatDelegate),
  35.                 typeof(UIEventListener.VectorDelegate),
  36.                 typeof(UIEventListener.ObjectDelegate),
  37.                 typeof(UIEventListener.KeyCodeDelegate),
  38.                 typeof(Delegate),
  39.                 typeof(Func<ushort, bool>),
  40.             };
  41.         }
  42.     }
  43.    
复制代码
       完成config之后,基本上在Lua中调用C#也就完成了。C#调用Lua按照官方给到的Demo,参考写就可以。
        2、pdf重写

        C#中使用的Protobuf也可以在Lua中进行使用,但是需要做一些中间类,并且性能也不怎么好,所以直接在原有的基础上扩展出Protobuf直接对Lua的逻辑。
        这里也是比较坑的地方,遇到了Bytep[] 进行二次序列化时,多出2个字节,导致消息的长度一直对不上,这个属于项目特殊问题,有遇到过的大佬欢迎评论区留言。
        3、Bridge桥接类

        为了更好的实现XLua与原先C#的框架融合,做了几个桥接类进行转换,这里无法贴出完整脚本,大概书写一下思路(以UI为例):
        首先书写出适配器类,用于对后续所有需要用到桥接的地方做基类,其中包含适配器对象以及可标记类身份的参数。
  1. public interface IXLuaFuncBase
  2.     {
  3.         /// <summary>
  4.         /// 适配器
  5.         /// </summary>
  6.         ICSharpDataAdapter Adapter { get; set; }
  7.         
  8.         /// <summary>
  9.         /// 资源ID
  10.         /// </summary>
  11.         int SourceDataID { get; set; }
  12.         
  13.         /// <summary>
  14.         /// Lua脚本名称
  15.         /// </summary>
  16.         string LuaScriptName { get; set; }
  17.         /// <summary>
  18.         /// 创建Lua虚拟机专属Table
  19.         /// </summary>
  20.         void CreatLuaEnvTable();
  21.     }
复制代码
       桥接类继承原先的UI框架,同时继承适配器类,实现原UI框架中的流程函数,并在流程函数中添加Lua脚本所对应的Action,用于C#,Lua脚本同步。桥接类需要将C#中的Action注入到XLua中,同时也要把需要从C#传递到XLua中的参数传递完成。
  1. public void CreatLuaEnvTable()
  2.         {
  3.             LuaScriptName = "AdapterMonoLuaText";
  4.             if (string.IsNullOrEmpty(LuaScriptName) || Adapter == null)
  5.             {
  6.                 return;
  7.             }
  8.             Adapter.SetLuaField(LuaCommon.Bridge,this);
  9.             Adapter.SetLuaField("gameObject",this.gameObject);
  10.             Adapter.SetLuaField("enable",enabled);
  11.             Adapter.LoadLuaScript(LuaScriptName);
  12.             
  13.             //对应Lua中的字段/函数名称
  14.             Adapter.GetLuaField("awake",out Act_Awake);
  15.             Adapter.GetLuaField("start",out Act_Start);
  16.             Adapter.GetLuaField("onEnable",out Act_OnEnable);
  17.             Adapter.GetLuaField("update",out Act_Update);
  18.             Adapter.GetLuaField("fixedUpdate",out Act_FixedUpdate);
  19.             Adapter.GetLuaField("lateUpdate",out Act_LateUpdate);
  20.             Adapter.GetLuaField("onGUI",out Act_OnGUI);
  21.             Adapter.GetLuaField("onDisable",out Act_OnDisable);
  22.             Adapter.GetLuaField("onDestroy",out Act_OnDestroy);
  23.         }
  24.         #region Mono生命周期
  25.         private Action Act_Awake;
  26.         private Action Act_Start;
  27.         private Action Act_OnEnable;
  28.         private Action Act_Update;
  29.         private Action Act_FixedUpdate;
  30.         private Action Act_LateUpdate;
  31.         private Action Act_OnGUI;
  32.         private Action Act_OnDisable;
  33.         private Action Act_OnDestroy;
  34.         private void Awake()
  35.         {
  36.             CreatLuaEnvTable();
  37.             Act_Awake?.Invoke();
  38.         }
  39.         private void Start()
  40.         {
  41.             Act_Start?.Invoke();
  42.         }
  43.         private void OnEnable()
  44.         {
  45.             Act_OnEnable?.Invoke();
  46.         }
  47.         private void Update()
  48.         {
  49.             Act_Update?.Invoke();
  50.         }
  51.         private void FixedUpdate()
  52.         {
  53.             Act_FixedUpdate?.Invoke();
  54.         }
  55.         private void LateUpdate()
  56.         {
  57.             Act_LateUpdate?.Invoke();
  58.         }
  59.         private void OnGUI()
  60.         {
  61.             Act_OnGUI?.Invoke();
  62.         }
  63.         private void OnDisable()
  64.         {
  65.            Act_OnDisable?.Invoke();
  66.         }
  67.         private void OnDestroy()
  68.         {
  69.             Act_Awake = null;
  70.             Act_Start= null;
  71.             Act_OnEnable= null;
  72.             Act_Update= null;
  73.             Act_FixedUpdate= null;
  74.             Act_LateUpdate= null;
  75.             Act_OnGUI= null;
  76.             Act_OnDisable= null;
  77.             Act_OnDestroy?.Invoke();
  78.             Act_OnDestroy= null;
  79.         }
  80.         #endregion
复制代码
这样,桥接类就可以通过身份参数进行不同的实现,所有需要使用到这个UI脚本的地方,之后只挂载这个桥接类即可。
至于UI参数的传递,可以使用GameObject.Find()也可以自行编写所需逻辑,实际使用中,FInd的性能消耗其实并不大, 是在合理范围内的。我在项目中使用的是注入的方式,自己实现的逻辑,大体为:梳理一个编辑器逻辑,将需要的参数数据分为名称,类型,对象。再将数据注入到XLua中。
其实就是做了一个方便UI进行使用的编辑器拓展,技术实现上还是依循着XLua注入的原理。
五、坑点梳理(持续更新)

        1、事件使用

        【Action,Function】如果action是空,则需要先给action进行赋值
  1. eventClass.Act = eventClass.Act + testFunc2
  2. eventClass.Act()
复制代码
       【Delegate】如果event是空,需要先给event赋值
  1. eventClass:Events('+',testEvent1)  添加使用‘+’,删减使用‘-’
  2. eventClass['&Events']() 格式为['&事件名称']()
复制代码
        2、pdf增加xlua交互

        github资料:https://github.com/starwing/lua-protobuf
        但是在最新的Demo中,没有可以直接使用的 .dll 库,需要手动去打一个出来
        protobuf Xlua dll:
        链接:https://pan.baidu.com/s/1goaqeBN-0S985OtFLJPQEg
         提取码:h0sj
        3、报错:xlua.access, no field __Hitfix0_xxx

        这种错误目前遇到2种情况,第一是注入之后出现覆盖注入,导致原先的注入信息与当前使用的不匹配,解决方案参考:[Solution] LuaException: xlua.access, no field __Hotfix0_Update · Issue #850 · Tencent/xLua · GitHub
        第二种是在重写非继承MonoBehavior中发现的,触发脚本的HotFix需要在对应的脚本实例化之后,将实例化对象传入进行使用,简单以CS.类型是无法正常注入实例对象的。
  1. xlua.private_accessible(CS.类名)
  2. print(tostring(类对象实例))
  3. print(类对象实例== nil)
  4. xlua.hotfix(CS.CS.类名,'Open',function()
  5.     print("=====================啥也不干=======================")
  6. end)
复制代码
        4、打包Bundle报错:'light' does not contain a definition for 'SetLightDirty' and no accessible extension method 'SetLightDirty'

        该问题是Light中有一部分API需要配置到黑名单中,依据官方的黑名单配置方式进行配置即可:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/configure.md
  1. new List<string>(){"UnityEngine.Light", "areaSize"},
  2. new List<string>(){"UnityEngine.Light", "lightmapBakeType"},
  3. new List<string>(){"UnityEngine.Light", "shadowRadius"},
  4. new List<string>(){"UnityEngine.Light", "shadowAngle"},
  5. new List<string>(){"UnityEngine.Light", "SetLightDirty"},
复制代码
        5、Lua中的与或非:and   or   not

        6、XLua报错:unexpected symbol near ‘<\239>‘

        此类问题,是由于XLua文件不是UTF-8编码格式引起的,编码格式修改为UTF-8即可
        7、使用了HotFix打包Android后,运行,XLua报错:xlua.access, no field __Hitfix0_xxx

        实际验证发现打包出来的Assembly-CSharp.dll是没有XLua的内容,原因是打包时可能本地环境中就有问题,导致后续出包使用的dll库本就不包含XLua的内容,打版前建议手动注入一次,出包后再Android工程中的Assembly-CSharp.dll应该就是包含了XLua的,但是本地工程查看dll库中已经是没有注入成功的,初步猜测是在打包之后dll库进行了自动刷新,但是刷新时没有调用XLua的注入,所以本地工程的dll中不含有XLua内容,可以借用dll反编译工具查看,定位还是比较快的。
反编译工具:
链接:https://pan.baidu.com/s/1bwX9xvovBO5t7yPAQA-lRw
提取码:8uks
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-6-2 00:57 , Processed in 0.170211 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表