当前位置: 首页 > 工具软件 > xLua > 使用案例 >

XLua接入笔记

张晨朗
2023-12-01

近期项目升级到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 库(包括所需的第三方库)配入,这里强烈推荐使用动态列表,这样可以避免在所有需要的脚本上加标签。

 [Hotfix]public static List<Type> DynamicDllList
    {
        get
        {
            List<Type> all = new List<Type>();
          
            List<Type> slefAssembly = GetTypesForAssembly("Assembly-CSharp");
           
            all.AddRange(slefAssembly);
           
            return all;
        }
    }

        白名单按照需要根据教程写基本没问题。

        黑名单可以直接使用Demo中的配置,额外添加项目中特殊的即可。

        在这里有一个比较坑的地方,有更好的解决方案的大佬可以在评论区留言。由于自定义的delegate和action无法使用动态配置导入,所以需要手动的一个个的添加。这里主要一定要加标签【CSharpCallLua】

[CSharpCallLua]
    public static List<Type> CSharpCallLuaList
    {
        get
        {
            return new List<Type>
            {
                typeof(EventDemo.TestDelegate),
                typeof(Func<double>),
                typeof(Func<string>),
                typeof(Func<bool>),
                typeof(Func<int>),
                typeof(Func<uint>),
                typeof(Func<uint, string>),
                typeof(Func<double, double>),
                typeof(Func<double, double, double>),
                typeof(Func<int, string, List<byte>>),
                typeof(Action),
                typeof(Action<object>),
                typeof(Action<double>),
                typeof(Action<string>),
                typeof(Action<int>),
                typeof(Action<int,int>),
                typeof(Action<uint>),
                typeof(Action<uint,uint>),
                typeof(Action<double, double>),
                typeof(Action<double, double, double>),
                typeof(Action<int, string>),
                typeof(Action<float>),
                typeof(UnityAction),
                typeof(IEnumerator),
                typeof(UIEventListener.VoidDelegate),
                typeof(UIEventListener.BoolDelegate),
                typeof(UIEventListener.FloatDelegate),
                typeof(UIEventListener.VectorDelegate),
                typeof(UIEventListener.ObjectDelegate),
                typeof(UIEventListener.KeyCodeDelegate),
                typeof(Delegate),

                typeof(Func<ushort, bool>),
            };
        }
    }
    

        完成config之后,基本上在Lua中调用C#也就完成了。C#调用Lua按照官方给到的Demo,参考写就可以。

        2、pdf重写

        C#中使用的Protobuf也可以在Lua中进行使用,但是需要做一些中间类,并且性能也不怎么好,所以直接在原有的基础上扩展出Protobuf直接对Lua的逻辑。

        这里也是比较坑的地方,遇到了Bytep[] 进行二次序列化时,多出2个字节,导致消息的长度一直对不上,这个属于项目特殊问题,有遇到过的大佬欢迎评论区留言。

        3、Bridge桥接类

        为了更好的实现XLua与原先C#的框架融合,做了几个桥接类进行转换,这里无法贴出完整脚本,大概书写一下思路(以UI为例):

        首先书写出适配器类,用于对后续所有需要用到桥接的地方做基类,其中包含适配器对象以及可标记类身份的参数。

public interface IXLuaFuncBase
    {
        /// <summary>
        /// 适配器
        /// </summary>
        ICSharpDataAdapter Adapter { get; set; }
        
        /// <summary>
        /// 资源ID
        /// </summary>
        int SourceDataID { get; set; }
        
        /// <summary>
        /// Lua脚本名称
        /// </summary>
        string LuaScriptName { get; set; }

        /// <summary>
        /// 创建Lua虚拟机专属Table
        /// </summary>
        void CreatLuaEnvTable();
    }

        桥接类继承原先的UI框架,同时继承适配器类,实现原UI框架中的流程函数,并在流程函数中添加Lua脚本所对应的Action,用于C#,Lua脚本同步。桥接类需要将C#中的Action注入到XLua中,同时也要把需要从C#传递到XLua中的参数传递完成。

public void CreatLuaEnvTable()
        {
            LuaScriptName = "AdapterMonoLuaText";
            if (string.IsNullOrEmpty(LuaScriptName) || Adapter == null)
            {
                return;
            }
            Adapter.SetLuaField(LuaCommon.Bridge,this);
            Adapter.SetLuaField("gameObject",this.gameObject);
            Adapter.SetLuaField("enable",enabled);
            Adapter.LoadLuaScript(LuaScriptName);
            
            //对应Lua中的字段/函数名称
            Adapter.GetLuaField("awake",out Act_Awake);
            Adapter.GetLuaField("start",out Act_Start);
            Adapter.GetLuaField("onEnable",out Act_OnEnable);
            Adapter.GetLuaField("update",out Act_Update);
            Adapter.GetLuaField("fixedUpdate",out Act_FixedUpdate);
            Adapter.GetLuaField("lateUpdate",out Act_LateUpdate);
            Adapter.GetLuaField("onGUI",out Act_OnGUI);
            Adapter.GetLuaField("onDisable",out Act_OnDisable);
            Adapter.GetLuaField("onDestroy",out Act_OnDestroy);
        }

        #region Mono生命周期

        private Action Act_Awake;
        private Action Act_Start;
        private Action Act_OnEnable;
        private Action Act_Update;
        private Action Act_FixedUpdate;
        private Action Act_LateUpdate;
        private Action Act_OnGUI;
        private Action Act_OnDisable;
        private Action Act_OnDestroy;


        private void Awake()
        {
            CreatLuaEnvTable();
            Act_Awake?.Invoke();
        }

        private void Start()
        {
            Act_Start?.Invoke();
        }

        private void OnEnable()
        {
            Act_OnEnable?.Invoke();
        }

        private void Update()
        {
            Act_Update?.Invoke();
        }

        private void FixedUpdate()
        {
            Act_FixedUpdate?.Invoke();
        }

        private void LateUpdate()
        {
            Act_LateUpdate?.Invoke();
        }

        private void OnGUI()
        {
            Act_OnGUI?.Invoke();
        }

        private void OnDisable()
        {
           Act_OnDisable?.Invoke();
        }

        private void OnDestroy()
        {
            Act_Awake = null;
            Act_Start= null;
            Act_OnEnable= null;
            Act_Update= null;
            Act_FixedUpdate= null;
            Act_LateUpdate= null;
            Act_OnGUI= null;
            Act_OnDisable= null;
            Act_OnDestroy?.Invoke();
            Act_OnDestroy= null;
        }

        #endregion

这样,桥接类就可以通过身份参数进行不同的实现,所有需要使用到这个UI脚本的地方,之后只挂载这个桥接类即可。

至于UI参数的传递,可以使用GameObject.Find()也可以自行编写所需逻辑,实际使用中,FInd的性能消耗其实并不大, 是在合理范围内的。我在项目中使用的是注入的方式,自己实现的逻辑,大体为:梳理一个编辑器逻辑,将需要的参数数据分为名称,类型,对象。再将数据注入到XLua中。

其实就是做了一个方便UI进行使用的编辑器拓展,技术实现上还是依循着XLua注入的原理。

五、坑点梳理(持续更新)

        1、事件使用

        【Action,Function】如果action是空,则需要先给action进行赋值 

eventClass.Act = eventClass.Act + testFunc2
eventClass.Act()

        【Delegate】如果event是空,需要先给event赋值

eventClass:Events('+',testEvent1)  添加使用‘+’,删减使用‘-’

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.类型是无法正常注入实例对象的。

xlua.private_accessible(CS.类名)
print(tostring(类对象实例))
print(类对象实例== nil)
xlua.hotfix(CS.CS.类名,'Open',function()
    print("=====================啥也不干=======================")
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

new List<string>(){"UnityEngine.Light", "areaSize"},
new List<string>(){"UnityEngine.Light", "lightmapBakeType"},
new List<string>(){"UnityEngine.Light", "shadowRadius"},
new List<string>(){"UnityEngine.Light", "shadowAngle"},
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

        

 类似资料: