近期项目升级到Unity2018之后,需要使用IL2CPP进行打包,导致之前使用的直接替换 .dll 的更新方式无法继续使用,决定接入XLua进行热更,此篇文章做个简单的爬坑记录。
目录
3、报错:xlua.access, no field __Hitfix0_xxx
由于需要使用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的接入相对简单,只需要去GitHub下载XLua框架,按照教程导入工程中,基本都可以直接进行基础测试。
XLua GitHub:https://github.com/Tencent/xLua
这次也是初次真正使用XLua,所以在接入初期学习了下Lua的基础语法,其中表(Table)和元表(metaTable)是比较重要的概念
Lua语法教程:Lua 教程 | 菜鸟教程
在以上的基础上,根据XLua官方给出的教程,逐步进行测试,测试完成之后,真正进入了将XLua嵌入项目的操作。
配置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,参考写就可以。
C#中使用的Protobuf也可以在Lua中进行使用,但是需要做一些中间类,并且性能也不怎么好,所以直接在原有的基础上扩展出Protobuf直接对Lua的逻辑。
这里也是比较坑的地方,遇到了Bytep[] 进行二次序列化时,多出2个字节,导致消息的长度一直对不上,这个属于项目特殊问题,有遇到过的大佬欢迎评论区留言。
为了更好的实现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注入的原理。
【Action,Function】如果action是空,则需要先给action进行赋值
eventClass.Act = eventClass.Act + testFunc2
eventClass.Act()
【Delegate】如果event是空,需要先给event赋值
eventClass:Events('+',testEvent1) 添加使用‘+’,删减使用‘-’
eventClass['&Events']() 格式为['&事件名称']()
github资料:https://github.com/starwing/lua-protobuf
但是在最新的Demo中,没有可以直接使用的 .dll 库,需要手动去打一个出来
protobuf Xlua dll:
链接:https://pan.baidu.com/s/1goaqeBN-0S985OtFLJPQEg
提取码:h0sj
这种错误目前遇到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)
该问题是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"},
此类问题,是由于XLua文件不是UTF-8编码格式引起的,编码格式修改为UTF-8即可
实际验证发现打包出来的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