|
《Lua热更新》
##《Lua热更新》发布说明:
++++“Lua热更新”开始了,立钻哥哥终于开始此部分的探索了。
++++作为游戏发布迭代的重要技术:Lua热更新在网络游戏迭代更新中非常重要,特别是对于AppStore这样的平台,我们只需要定期更新主App,原则上可以随时灵活更新我们的以Lua热更新框架为基础的代码。
++++当然,作为一项新引入的技术,我们通常是以【快速入门】=>【基础夯实】=>【中级进阶】=>【高级实战】=>【立钻哥哥带您学Lua热更新】等几个阶段进行探索。
##《Lua热更新》目录:
#第一篇:Lua快速入门篇
#第二篇:Lua基础夯实篇
#第三篇:Lua中级进阶篇
#第四篇:Lua高级实战篇
#第五篇:立钻哥哥带您学Lua热更新
#第一篇:Lua快速入门篇
#第一篇:Lua快速入门篇
++++第一章:Lua基础概述
++++第二章:xLua教程
++++第三章:Lua基础拓展
++++第四章:立钻哥哥带您学Lua热更新
##第一章:Lua基础概述
++第一章:Lua基础概述
++++1.1、Lua热更新框架
++++1.2、toLua#热更新框架
++++1.3、xLua热更新框架
++++1.4、立钻哥哥带您学Lua基础
###1.1、Lua热更新框架
++1.1、Lua热更新框架
++++toLua:https://github.com/topameng/tolua
++++uLua: http://ulua.org/index.html
++++xLua: https://github.com/Tencent/xLua
++++C#在开发效率和运行效率平衡得很好,语言特性也比较全,是很优秀的一门语言。
++++lua被称为游戏脚本之王,在游戏领域应用比较广泛,它设计之初就考虑到嵌入式领域,比如相对它提供的特性来说,它体积非常小,启动占资源也不多,性能是脚本里的佼佼者。
++++lua相对于C#而言,首先是它支持解析执行,进而支持热更新。(免编译对开发效率提升也很大,特别是较大的项目。)(从运行效率上说C#比最快的lua方案也要快50倍左右。)
++++IOS不能热更新,不是因为不能用反射,是因为【System.Reflection.Assembly.Load】、【System.Reflection.Emit】、【System.CodeDom.Compiler】等无法使用,Unity原生的代码逻辑,无论是以前的MonoAOT或者后来的il2cpp,都是编译成native code,iOS下是跑不了的,立钻哥哥:IOS下不能动态载入dll或者cs文件,已经编译进App的没有问题。
++++立钻哥哥:以lua热更技术为基础的框架有:toLua、uLua、xLua、SLua、C#light等。(ulua+ngui)(tolua+gui)(xlua+ngui)(ulua作者已不再维护,转至tolua)(tolua的性能表现好)
++1.1.1、toLua
++++toLua(gitHub):https://github.com/topameng/tolua
++++toLua是一个工具,将Unity的C#代码包装之后导出给Lua,同时提供了一些访问Lua的接口,使得Unity和Lua可以相互调用。
++++toLua#是Unity静态绑定Lua的一个解决方案,它通过C#提供的反射信息分析代码生成包装的类。(它是一个用来简化在C#中集成Lua的插件,可以自动生成用于在Lua中访问Unity的绑定代码,并把C#中的常量、变量、函数、属性、类以及枚举暴露给lua。)
++++toLua#底层库是使用toLua(C语言编写),那么就需要通过C#来调用原生代码。
++++toLua#集成主要分两部分:
--第一部分:运行时需要的代码,包括一些手写的和自动生成的绑定代码;
--第二部分:编辑器相关代码,主要提供代码生成、编译lua文件等操作,具体就是Unity编辑器中提供的功能。
++++【ToLua/Assembly-CSharp】:References/、Source/、ToLua/
--【References/】:
--【Source/】:Generate/、LuaConst.cs
----Generate/:这个文件里面是生成的绑定代码;
----LuaConst.cs:这个文件是一些lua路径等配置文件;
--【ToLua/】:BaseType/、Core/、Examples/、Misc、Reflection、ReadMe.txt
----BaseType/:一些基础类型的绑定代码;
----Core/:提供的一些核心功能,包括封装的LuaFunction、LuaTable、LuaThread、LuaState、LuaEvent、调用tolua原生代码等;
----Examples/:示例代码;
----Misc/:杂项,包括LuaClient、LuaCoroutine(协程)、LuaLooper(用于tick)、LuaResLoader(用于加载Lua文件);
----Reflection/:反射相关;
++++【ToLua/Assembly-CSharp-Editor】:References/、Editor/、ToLua/
--【Editor/Custom/CustomSettings.cs】:自定义配置文件,用于定义哪些类作为静态类型、哪些类需要导出、哪些附加委托需要导出等。
--【ToLua/Editor】:Extend/、ToLuaExport.cs、ToLuaMenu.cs、ToLuaTree.cs
----Extend/:扩展一些类的方法;
----ToLuaExport.cs:真正生成Lua绑定的代码;
----ToLuaMenu.cs:Lua菜单上功能对应的代码;
----ToLuaTree.cs:辅助树结构;
++++Generate All流程:生成绑定代码主要放在ToLuaExport.cs里面:
--【GenLuaDelegates()函数】:生成委托绑定的代码,它会从CustomSettings.customDelegateList里面取出所有自定义导出的委托列表,然后把CustomSettings.customTypeList里面的所有类型中的委托根据一定规则加入到list中,最后调用ToLuaExport.GenDelegates()方法来生成委托绑定的代码,生成的代码在DelegateFactory.cs文件中。(立钻哥哥:该函数的详细实现可查看ToLuaExport.cs中的函数实现。)
--【GenerateClassWraps()函数】:遍历所有需要导出的类,然后调用ToLuaExport.Generate()方法来生成类的绑定代码。
++1.1.2、uLua
++++uLua.org: http://ulua.org/index.html
++++uLua:基于tolua#的Lua热更新UGUI/NGUI框架。(uLua已停止维护,由toLua#代替。)
++++uLua的原理:给GameObject添加上一个C#脚本组件作为中间层,在中间层上绑定上一个Lua脚本,将Unity的所有回调接口通过中间层传递到Lua。(Lua脚本也可以通过中间层操作GameObject。)
++++uLua要使用最新版本,早期的uLua是使用反射机制,脚本的运行效率比较糟糕,新的uLua集成了cstolua,预先生成一批代码把Unity的类和函数导出给lua,然后lua再调用,这样无论是效率还是GC的角度说都是比较完美的。
++1.1.3、xLua
++++xLua官方: https://github.com/Tencent/xLua
++++xLua是Unity3D下Lua编程解决方案,腾讯已将xLua开源到GitHub。
++++xLua是2015年3月完成第一个版本;2016年12月末,xLua实现新的突破:全平台支持用Lua修复C#代码bug。
++++xLua为Unity、.Net、Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便和C#相互调用。(xLua在功能、性能、易用性都有不少突破。)
++++xLua的突破:1、可以运行时把C#实现(方法,操作符,属性,事件等等)替换成lua实现;2、出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc;3、编辑器下无需生成代码,开发更轻量。
++++xLua热补丁技术支持在运行时把一个C#实现(函数,操作符,属性,事件,或者整个类)替换成Lua实现,意味着我们可以:1、平时用C#开发;2、运行也是C#,性能秒杀Lua;3、有bug的地方下发个Lua脚本fix了,下次整体更新时可以把Lua的实现换回正确的C#实现,更新时甚至可以做到不重启游戏;
++++xLua热修复框架工程结构参考:
++++【Resource/xLua/Main.lua】:xlua热修复入口。
++++【Resource/xLua/Common/】:提供给lua代码使用的一些工具方法,提供lua逻辑代码到C#调用的一层封装。
++++【Scripts/xLua/XLuaManager.cs】:xLua热修复环境,包括luaState管理,自定义loader。
++++【Scripts/xLua/Util/】:为xLua的lua脚本提供的C#侧代码支持,被Resources/xLua/Common/所使用。
++++【Scripts/HotfixTest】:需要热修复的C#脚本。
++++【Resource/xLua/HotFix】:热修复脚本。
###1.2、toLua#热更新框架
++1.2、toLua#热更新框架
++++toLua:https://github.com/topameng/tolua
++++toLua#是Unity静态绑定lua的一个解决方案,它通过C#提供的反射信息分析代码并生成包装的类。它是一个用来简化在C#中集成lua的插件,可以自动生成用于在lua中访问Unity的绑定代码,并把C#中的常量、变量、函数、属性、类以及枚举暴露给lua。
++++【Unity C#】<==>【Tolua#】<==>【Tolua(c)】
++++toLua特性:
--自动生成绑定代码文件,非反射调用;
--大量内建基础类型支持,如枚举,委托,事件,Type,数组,迭代器等;
--支持多种协同形式;
--支持所有unity内部类导出,支持委托类型导出;
--支持导出自定义,跳过某个空的基类,修改导出名称等;
--支持扩展函数自定义导出,比如DoTween;
--支持值类型Nullable导出,包括Nullable<Vector3>等;
--支持Lua中function转委托,可以区分需要不同委托的参数的重载函数;
--支持C# LuaFunction对象转委托,简化调用方式。支持无GC的多参数调用形式;
--支持重载函数自动排序,如:参数个数相同,object参数执行级最低,不会出现错误匹配情况;
--支持导出函数重命名,可以分离导出某个重载函数(可以导出被折叠掉的函数);
--支持使用编辑器类改写导出规则;
--支持this数组访问,索引为int可以通过[]访问,其他可使用.get_Item或者.this.get()访问数组成员;
--支持委托(事件)+-lua function。支持通过函数接口的Add和Remove委托操作;
--支持静态反射操作,形式同C#;
--支持peer表,可在lua端扩展导出的userdata;
--支持自定义struct压入和读取,做到无GC,并且结构成员无类型限制;
--支持preloading,可以通过require后绑定wrap文件;
--支持int64,unit64;
--大量的lua数学类型,如Quaternion, Vector3, Mathf等;
--包含第三方lua扩展,包括luasocket, struct, lpeg, utf8, pb等库;
--当lua出现异常,能够同时捕获C#端和lua端堆栈,便于调试;
--print信息,在编辑器点击日志,能自动打开对应lua文件;
--支持unity所有版本;
--支持Lua hook C#代码实现,一定程度上支持利用Lua代码修改C#端代码的bug;
++++tolua#集成主要两部分:1、运行时需要的代码包括一些手写和自动生成的绑定代码,2、编辑器相关代码,主要提供代码生成、编译lua文件等操作,具体就是Unity编辑器中提供的功能。
++1.2.1、【MenuItem(“Lua/Generate All”)】流程
++++ToLuaMenu.cs/GenLuaAll():GenLuaDelegates()、GenerateClassWraps()、GenLuaBinder():
--【GenLuaDelegate()】:生成委托绑定代码,它会从CustomSettings.customDelegateList里面取出所有自定义导出的委托列表,然后把CustomSettings.customTypeList里面的所有类型中的委托根据一定规则加入到list中,最后调用ToLuaExport.GenDelegates()方法来生成委托绑定的代码,生成的代码在DelegateFactory.cs文件中。
--【GenerateClassWraps()】:遍历所有需要导出的类,然后调用ToLuaExport.Generate()方法来生成类的绑定代码。
--【GenLuaBinder()】:生成向lua注册C#类的绑定代码,这个代码存放在LuaBinder.cs文件中,这部分代码中不包含BaseType(Array、Enum、Object、String等等)的注册。
//立钻哥哥:GenLuaAll(tolua-master-\Assets\ToLua\Editor\ToLuaMenu.cs)
using UnityEngine;
using UnityEditor;
using System;
[InitializeOnLoad]
public static class ToLuaMenu{
[MenuItem(“Lua/Generate All”, false, 5)]
static void GenLuaAll(){
if(EditorApplication.isCompiling){
EditorUtility.DisplayDialog(“立钻哥哥警告”, “请等待编辑器完成编译再执行此功能”, “确定”);
return;
}
beAutoGen = true;
GenLuaDelegates();
AssetDatabase.Refresh();
GenerateClassWraps();
GenLuaBinder();
beAutoGen = false;
}
[MenuItem(“Lua/Gen Lua Delegates”, false, 2)]
static void GenLuaDelegates(){
if(!beAutoGen &&EditorApplication.isCompiling){
EditorUtility.DisplayDialog(“立钻哥哥警告”, ”请等待编辑器完成编译再执行此功能”, “确定”);
return;
}
ToLuaExport.Clear();
List<DelegateType> list = new List<DelegateType>();
list.AddRange(CustomSettings.customDelegateList);
HashSet<Type> set = GetCustomTypeDelegates();
foreach(Type tin set){
if(null == list.Find((p)=>{ return p.type == t; })){
list.Add(new DelegateType(t));
}
}
ToLuaExport.GenDelegates(list.ToArray());
set.Clear();
ToLuaExport.Clear();
AssetDatabase.Refresh();
Debug.Log(“立钻哥哥:Create lua delegate over!”);
}
[MenuItem(“Lua/Gen Lua Wrap Files”, false, 1)]
public static void GenerateClassWraps(){
if(!bAutoGen &&EditorApplication.isCompiling){
EditorUtility.DisplayDialog(“立钻哥哥警告”, “请等待编辑器完成编译再执行此功能”, “确定”);
return;
}
if(!File.Exists(CustomSettings.saveDir)){
Directory.CreateDirectory(CustomSettings.saveDir);
}
allTypes.Clear();
BindType[] typeList = CustomSettings.customTypeList;
BindType[] list = GenBindTypes(typeList);
ToLuaExport.allTypes.AddRange(baseType);
for(int i = 0; i < list.Length; i++){
ToLuaExport.allTypes.Add(list[i].type);
}
for(int i = 0; i < list.Length; i++){
ToLuaExport.Clear();
ToLuaExport.className = list[i].name;
ToLuaExport.type = list[i].type;
ToLuaExport.isStaticClass = list[i].IsStatic;
ToLuaExport.baseType = list[i].baseType;
ToLuaExport.wrapClassName = list[i].wrapName;
ToLuaExport.libClassName = list[i].libName;
ToLuaExport.extendList = list[i].extendList;
ToLuaExport.Generate(CustomSettings.saveDir);
}
Debug.Log(“立钻哥哥:Generic lua binding files over!”);
ToLuaExport.allTypes.Clear();
allTypes.Clear();
AssetDatabase.Refresh();
}
[MenuItem(“Lua/Gen LuaBinder File”, false, 4)]
static void GenLuaBinder(){
if(!beAutoGen && EditorApplication.isCompiling){
EditorUtility.DisplayDialog(“立钻哥哥警告”, “请等待编辑器完成编译再执行此功能”, “确定”);
return;
}
allTypes.Clear();
ToLuaTree<string> tree = InitTree();
StringBuilder sb = new StringBuilder();
List<DelegateType> dtList = new List<DelegateType>();
List<DelegateType> list = new List<DelegateType>();
list.AddRange(CustomSetting.customDelegateType);
HashSet<Type> set = GetCustomTypeDelegates();
List<BindType> backupList = new List<BindType>();
backupList.AddRange(allTypes);
ToLuaNode<string> root = tree.GetRoot();
string libname = null;
foreach(Type tin set){
if(null == list.Find(p) => { return p.type == t; }){
DelegateType dt = new DelegateType(t);
AddSpaceNameToTree(tree, root, ToLuaExport.GetNameSpace(t, out libname));
list.Add(dt);
}
}
sb.AppendLineEx(“//立钻哥哥:this source code was auto-generated by tolua#, do not modify it”);
sb.AppendLineEx(“using System”);
sb.AppendLineEx(“using UnityEngine”);
sb.AppendLineEx(“using LuaInterface”);
sb.AppendLineEx();
sb.AppendLineEx(“public static class LuaBinder”);
sb.AppendLineEx(“{”);
sb.AppendLineEx(“\tpublic static void Bind(LuaState L)”);
sb.AppendLineEx(“\t{”);
sb.AppendLineEx(“\t\tfloat t = Time.realtimeSinceStartup;”);
sb.AppendLineEx(“\t\tL.BeginModule(null);”);
GenRegisterInfo(null, sb, list, dtList);
Action<ToLuaNode<string>> begin = (node)=>{
if(node.value == null){
return;
}
sb.AppendFormat(“\t\tL.BeginModule(\”{0}\”);\r\n”, node.value);
string space = GetSpaceNameFromTree(node);
GenRegisterInfo(space, sb, list, dtList);
};
Action<ToLuaNode<string>> end = (node) =>{
if(node.value != null){
sb.AppendLineEx(“\t\tL.EndModule();”);
}
};
tree.DepthFirstTraversal(begin, end, tree.GetRoot());
sb.AppendLineEx(“\t\tL.EndModule()”);
if(CustomSettings.dynamicList.Count > 0){
sb.AppendLineEx(“\t\tL.BeginPreLoad();”);
for(int i = 0; i < CustomSettings.dynamicList.Count; i++){
Type t1 = CustomSettings.dynamicList[i];
BindType bt = backupList.Find((p)=>{ return p.type == t1; });
if(bt != null){
sb.AppendFormat(“\t\tL.AppPreLoad(\”{0}\”, LuaOpen_{1}, typeof({0}));\r\n”, bt.name, bt.wrapName);
}
}
sb.AppendLineEx(“\t\tL.EndPreLoad();”);
}
sb.AppendLineEx(“\t\tDebugger.Log(\”立钻哥哥:Register lua type cost time: {0}\”, Time.realtimeSinceStartup - t);”);
sb.AppendLineEx(“\t}”);
for(int i = 0; i < dtList.Count; i++){
ToLuaExport.GenEventFunction(dtList.type, sb);
}
if(CustomSettings.dynamicList.Count > 0){
for(int i = 0; i < CustomSettings.dynamicList.Count; i++){
Type t = CustomSettings.dynamicList[i];
BindType bt = backupList.Find((p)=>{ return p.type == t; });
if(bt != null){
GenPreLoadFunction(bt, sb);
}
}
}
sb.AppendLineEx(“}\r\n”);
allTypes.Clear();
string file = CustomSettings.saveDir + “LuaBinder.cs”;
using(StreamWriter textWriter = new StreamWriter(file, false, Encoding.UTF8)){
textWriter.Write(sb.ToString());
textWriter.Flush();
textWriter.Close();
}
AssetDatabase.Refresh();
Debugger.Log(“立钻哥哥:Generate LuaBinder over!”);
}
} //立钻哥哥:public static class ToLuaMenu{}
++1.2.2、【ToLuaExport.cs/Generate()】流程
++++ToLuaExport.Generate()基本流程:如果这个类是枚举类型,那么会调用枚举导出的接口,如果这个类型是一个普通的类,那么就会调用相应的流程将代码导出。
//立钻哥哥:Generate()流程(tolua-master-\Assets\ToLua\Editor\ToLuaExport.cs)
using UnityEngine;
using System;
public static class ToLuaExport{
public static void Generate(string dir){
#if !EXPORT_INTERFACE
Type iterType = typeof(System.Collections.IEnumerator);
if(type.IsInterface &&type !=iterType){
return;
}
#endif
Debugger.Log(“立钻哥哥:Begin Generate lua Wrap for class {0}”, className);
sb = new StringBuilder();
usingList.Add(“System”);
if(wrapClassName == “”){
wrapClassName = className;
}
if(type.IsEnum){
BeginCodeGen();
GenEnum();
EndCodeGen(dir);
return;
}
InitMethods();
InitPropertyList();
InitCtorList();
BeginCodeGen();
GenRegisterFunction();
GenConstructFunction();
GenItemPropertyFunction();
GenFunctions();
//GenToStringFunction();
GenIndexFunc();
GenNewIndexFunc();
GenOutFunction();
GenEventFunctions();
EndCodeGen(dir);
}
static void BeginCodeGen(){
sb.AppendFormat(“public class {0}Wrap\r\n”, wrapClassName);
sb.AppendLineEx(“{”);
}
static void GenEnum(){
fields = type.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static);
List<FieldInfo> list = new List<FieldInfo>(fields);
for(int i = list.Count - 1; i > 0; i--){
if(IsObsolete(list[i])){
list.RemoveAt(i);
}
}
fields = list.ToArray();
.... //立钻哥哥:此处省略一万字
}
static void EndCodeGen(string dir){
sb.AppendLineEx(“}\r\n”);
SaveFile(dir + wrapClassName + “Wrap.cs”);
}
static void InitMethods(){
bool flag = false;
if(baseType != null || isStaticClass){
binding |= BindingFlags.DeclaredOnly;
flag = true;
}
List<_MethodBase> list = new List<_MethodBase>();
MethodInfo[] infos = type.GetMethods(BindingFlags.Instance | binding);
for(int i = 0; i < infos.Length; i++){
list.Add(new _MethodBase(infos[i]));
}
.... //立钻哥哥:此处省略一万字
}
static void InitPropertyList(){
props = type.GetProperties(BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Instance | binding);
propList.AddRange(type.GetProperties(BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase));
fields = type.GetFields(BindingFlags.GetField | BindingFlags.SetField | BindingFlags.Instance | binding);
events = type.GetEvents(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static);
eventList.AddRange(type.GetEvents(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public));
List<FieldInfo> fieldList = new List<FieldInfo>();
fieldList.AddRange(fields);
.... //立钻哥哥:此处省略一万字
}
static void InitCtorList(){
if(isStaticClass || type.IsAbstract || typeof(MonoBehaviour).IsAssignableFrom(type)){
return;
}
ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Instance | binding);
.... //立钻哥哥:此处省略一万字
}
static void BeginCodeGen(){
sb.AppendFormat(“public class {0}Wrap\r\n”, wrapClassName);
sb.AppendLineEx(“{”);
}
static void GenRegisterFunction(){
sb.AppendLineEx(“\tpublic static void Register(LuaState L)”);
sb.AppendLineEx(“\t{”);
....
GenRegisterFuncItems();
GenRegisterOpItems();
GenRegisterVariables();
GenRegisterEventTypes(); //立钻哥哥:注册事件类型
....
}
static void GenConstructFunction(){
....
}
//立钻哥哥:this[] 非静态函数
static void GenItemPropertyFunction(){
....
}
static void GenFunctions(){
HashSet<string> set = new HashSet<string>();
....
}
static void GenIndexFunc(){
....
}
static void GenNewIndexFunc(){
....
}
static void GenOutFunction(){
if(isStaticClass || CustomSettings.outList.IndexOf(type) < 0){
return;
}
sb.AppendLineEx(“\r\n\r[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]”);
sb.AppendLineEx(“\tstatic int get_out(IntPtr L)”);
sb.AppendLineEx(“\t{”);
sb.AppendFormat(“\t\tToLua.PushOut<{0}>(L, new LuaOut<{0}>());\r\n”, className);
sb.AppendLineEx(“\t\treturn 1;”);
sb.AppendLineEx(“\t}”);
}
static void GenEventFunctions(){
foreach(Type t in eventSet){
GetEventFunction(t, sb);
}
}
static void EndCodeGen(string dir){
sb.AppendLineEx(“}\r\n”);
SaveFile(dir + wrapClassName + “Wrap.cs”);
}
} //立钻哥哥:public static class ToLuaExport{}
++1.2.3、toLua#核心运行时
++++toLua#的运行代码包含【Source/Generate/】下的绑定代码,以及【ToLua/BaseType/】代码,以及【ToLua/Core/】下的核心代码。
++++【LuaAttribute.cs】:在toLua#生成绑定代码时做一些标示使用。
++++【LuaBaseRef.cs】:Lua中对象对应C#中对象的一个基类,主要作用是用一个reference指向lua里面的对象,引用计数判断两个对象是否相等。(比如:LuaFunction里面的reference是指向lua里面的一个闭包的,而LuaTable的reference是指向lua中的一个table的。)
++++【LuaDll.cs】:这个类的主要作用就是实现了C#调用原生代码的功能。
++++【LuaState.cs】:对真正的lus_State的封装,包括初始化lua路径,加载相应的lua文件,注册生成的绑定代码以及各种辅助函数。
++++【ObjectTranslator.cs】:给lua中对C#对象的交互提供了基础,简单来说就是C#中的对象在传给lua时并不是直接把对象暴露给lua,而是在这个ObjectTranslator里面注册并返回一个索引(句柄),并把这个索引包装成一个userdata传递给lua,并且设置元表。(在lua需要通过传到lua里面的对象调用C#的方法时,它会调用ToLua.CheckObject或者ToLua.ToObject从ObjectTranslator获取真正的C#对象。)
++1.2.4、关于反射
++++toLua#不支持动态反射。(动态反射对于重载函数有参数匹配问题,函数排序问题,ref, out参数问题等等。)
++++toLua#提供的替换方法是:
--1、preloading,把未来可能需要的类型添加到导出列表customTypeList,同时也添加到dynamicList列表中,这样导出后该类型不会随binder注册到lua中,可以通过require “namespace.classname”动态注册到lua中,对于非枚举类型toLua#系统也可以在第一次push该类型时动态载入,当然也可在过场动画、资源下载、登录、场景加载或者某个的函数中require这个类型。
--2、静态反射。通过静态反射支持精确的函数参数匹配和类型检查。不会存在重载函数参数混乱匹配错误问题。(注意iOS必须配置好link.xml)
++++link.xml(tolua-master-\Assets\link.xml)
//立钻哥哥:link.xml(iOS必须配置好link.xml)
<?xml version=”1.0”encoding=”utf-8”?>
<linker>
<assembly fullname=”mscorlib”>
<namespace fullname=”System.Collections.Generic”perserve=”all”/>
</assembly>
</linker>
++1.2.5、toLua简单示例:HelloWorld(最小的toLua#环境)
//立钻哥哥:HelloWorld示例(\Assets\ToLua\Examples\01_HelloWorld\HelloWorld.cs)
using UnityEngine;
using LuaInterface;
using System;
public class HelloWorld : MonoBehaviour{
void Awake(){
LuaState myLua = new LuaState();
myLua.Start();
string myHello = @”print(‘立钻哥哥:hello tolua#’)”;
myLua.DoString(myHello, “HelloWorld.cs”);
myLua.CheckTop();
myLua.Dispose();
myLua = null;
}
} //立钻哥哥:public class HelloWorld : MonoBehaviour{}
++++立钻哥哥:该简单示例展示了最小的toLua#环境。
++++【LuaState】封装了对lua主要数据结构lua_State指针的各种堆栈操作。
++++一般对于客户端,推荐只创建一个LuaState对象。(如果要使用多State需要在Unity中设置全局宏MULTI_STATE)
++++【LuaState.Start()】:需要在toLua代码加载到内存后调用。(如果使用assetbundle加载lua文件,调用Start()之前assetbundle必须加载好)
++++【LuaState.DoString()】:执行一段lua代码。(比较少用这种方式加载代码,无法避免代码重复加载覆盖等情况,需调用者自己保证。第二个参数用于调试信息,或者error消息(用于提示出错代码所在文件名称。))
++++【LuaState.CheckTop()】:检查堆栈是否平衡,一般放在update中,C#中任何使用lua堆栈操作,都需要调用者自己平衡堆栈,当CheckTop出现警告时其实早已经离开了堆栈操作范围,这是需自行review代码。
++++【LuaState.Dispose()】:释放LuaState以及其资源。
++1.2.6、toLua简单示例:ScriptsFromFile(DoFile和Require的区别)
//立钻哥哥:ScriptsFromFile示例(\ToLua\Examples\02_ScriptsFromFile\ScriptsFromFile.cs)
using UnityEngine;
using System.Collections;
using LuaInterface;
using System;
using System.IO;
//立钻哥哥:展示searchpath使用,require与dofile区别
public class ScriptsFromFile : MonoBehaviour{
LuaState myLua = null;
private string strLog = “”;
void Start(){
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived += Log;
#else
Application.RegisterLogCallback(Log);
#endif
myLua = new LuaState();
myLua.Start();
//立钻哥哥:如果移动了ToLua目录,手动修复配置
string fullPath = Application.dataPath + “\\ToLua/Examples/Yanlz_ScriptsFromFile”;
myLua.AddSearchPath(fullPath);
}
void Log(string msg, string stackTrace, LogType type){
strLog += msg;
strLog += “\r\n”;
}
void OnGUI(){
GUI.Label(new Rect(100, Screen.height/2 - 100, 600, 400), strLog);
if(GUI.Button(new Rect(50, 50, 120, 45), “DoFile”)){
strLog = “”;
myLua.DoFile(“ScriptsFromFile.lua”);
}else if(GUI.Button(new Rect(50, 150, 120, 45), “Require”)){
strLog = “”;
myLua.Require(“ScriptsFromFile”);
}
myLua.Collect();
myLua.CheckTop();
}
void OnApplicationQuit(){
myLua.Dispose();
myLua = null;
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived -= Log;
#else
Application.RegisterLogCallback(null);
#endif
}
} //立钻哥哥:public class ScriptsFromFile : MonoBehaviour{}
++++立钻哥哥:该示例展示了dofile和require区别。
++++toLua#中DoFile函数,和Lua保持一致行为,能多次执行一个文件。
++++toLua#中加入了新的Require函数,无论C#和lua谁先require一个lua文件,都能保证加载唯一性。
++++【LuaState.AddSearchPath()】:增加搜索目录,这样DoFile和Require函数可以只用文件名,无需写全路径。
++++【LuaState.DoFile()】:加载一个lua文件,注意dofile需要扩展名,可反复执行,后面的变量会覆盖之前的DoFile加载的变量。
++++【LuaState.Require()】:同lua require(modname)操作,加载指定模块并且把结果写入到package.loaded中,如果modname存在,则直接返回package.loaded[modname]。
++++【LuaState.Collect()】:垃圾回收,对于被自动gc的LuaFunction,LuaTable,以及委托减掉的LuaFunction,延迟删除的Object类等需要延迟处理的回收,都在这里自动执行。
++1.2.7、toLua#示例:CallLuaFunction(调用Lua函数)
//立钻哥哥:CallLuaFunction示例(\ToLua\Examples\03_CallLuaFunction\CallLuaFunction.cs)
using UnityEngine;
using System.Collections;
using LuaInterface;
using System;
public class CallLuaFunction : MonoBehaviour{
private string script =
@”
function luaFunc(num)
return num + 1;
end
Test = {};
Test.luaFunc = luaFunc;
”;
LuaFunction luaFunc = null;
LuaState myLua = null;
string myTips = null;
void Start(){
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived += ShowTips;
#else
Application.RegisterLogCallback(ShowTips);
#endif
new LuaResLoader();
myLua = new LuaState();
myLua.Start();
DelegateFactory.Init();
myLua.DoString(script);
//立钻哥哥:Get the fucntion object
luaFunc = myLua.GetFunction(“test.luaFunc”);
if(luaFunc != null){
int myNum = luaFunc.Invoke<int, int>(123456);
Debugger.Log(“立钻哥哥:generic call return: {0}”, myNum);
myNum = CallFunc();
Debugger.Log(“立钻哥哥:expansion call return: {0}”, myNum);
Func<int, int> myFunc = luaFunc.ToDelegate<Func<int, int>>();
myNum = myFunc(123456);
Debugger.Log(“立钻哥哥:Delegate call return: {0}”, myNum);
myNum = myLua.Invoke<int, int>(“test.luaFunc”, 123456, true);
Debugger.Log(“立钻哥哥:luastate call return: {0}”, myNum);
}
myLua.CheckTop();
}
void ShowTips(string msg, string stackTrace, LogType type){
myTips += msg;
myTips += “\r\n”;
}
#if ! TEST_GC
void OnGUI(){
GUI.Label(new Rect(Screen.width/2 - 200, Screen.height/2 - 150, 400, 300), myTips);
}
#endif
void OnDestroy(){
if(luaFunc != null){
luaFunc.Dispose();
luaFunc = null;
}
myLua.Dispose();
myLua = null;
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived -= ShowTips;
#else
Application.RegisterLogCallback(null);
#endif
}
int CallFunc(){
luaFunc.BeginPCall();
luaFunc.Push(123456);
luaFunc.PCall();
int myNum = (int)luaFunc.CheckNumber();
luaFunc.EndPCall();
return myNum;
}
} //立钻哥哥:public class CallLuaFunction : MonoBehaviour{}
++++立钻哥哥:toLua#简化了lua函数的操作,通过LuaFunction封装(并缓存)一个lua函数,并提供各种操作,建议频繁调用函数使用无GC方式。
++++【LuaState.GetLuaFunction()】:获取并缓存一个lua函数,此函数支持串式操作,如“test.luaFunc”代表test表中的luaFunc函数。
++++【LuaState.Invoke()】:临时调用一个lua function并返回一个值,这个操作并不缓存lua function,适合频率非常低的函数调用。
++++【LuaFunction.Call()】:不需要返回值的函数调用操作。
++++【LuaFunction.Invoke()】:有一个返回值的函数调用操作。
++++【LuaFunction.BeginPCall()】:开始函数调用。
++++【LuaFunction.Push()】:压入函数调用需要的参数,通过众多的重载函数来解决参数转换gc问题。
++++【LuaFunction.PCall()】:调用lua函数。
++++【LuaFunction.CheckNumber()】:提取函数返回值,并检查返回值为lua number类型。
++++【LuaFunction.EndPCall()】:结束lua函数调用,清除函数调用造成的堆栈变化。
++++【LuaFunction.Dispose()】释放LuaFunction,递减引用计数,如果引用计数为0,则从_R表删除该函数。
++++立钻哥哥:无论Call还是PCall只相当于lua中的函数“.”调用。(self:call(...) == self.call(self, ...),C#中需要按self.call(self, ...)方式调用,即必须主动传入第一个参数self)
++1.2.8、toLua#示例:AccessingLuaVariables(访问lua变量)
//立钻哥哥:AccessingLuaVariables示例(\04_AccessingLuaVariables\AccessingLuaVariables.cs)
using UnityEngine;
using System.Collections.Generic;
using LuaInterface;
public class AccessingLuaVariables : MonoBehaviour{
private string myScript = @”
print(‘立钻哥哥:Objs2Spawn is: ’ .. Objs2Spawn)
var2read = 42
varTable = { 1, 2, 3, 4, 5 }
varTable.default = 1
varTable.map = {}
varTable.map.name = ‘map’
meta = { name = ‘meta’ }
setmetatable(varTable, meta)
function TestFunc(strs)
print(‘立钻哥哥:func by variable’);
end
”;
void Start(){
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReveived += MyShowTips;
#else
Application.RegisterLogCallback(MyShowTips);
#endif
new LuaResLoader();
LuaState myLua = new LuaState();
myLua.Start();
myLua[“Objs2Spawn”] = 5;
myLua.DoString(myScript);
//立钻哥哥:通过LuaState访问
Debugger.Log(“立钻哥哥:Read var from lua:{0}”, lua[“var2read”]);
Debugger.Log(“立钻哥哥:Read table var from lua:{0}”, lua[“varTable.default”]);
LuaFunction myFunc = myLua[“TestFunc”] as LuaFunction;
myFunc.Call();
myFunc.Dispose();
//立钻哥哥:cache成LuaTable进行访问
LuaTable myTable = myLua.GetTable(“varTable”);
Debugger.Log(“立钻哥哥:Read varTable from lua, default:{0} name:{1}”, myTable[”default”], myTable[“map.name”]);
myTable[“map.name”] = “new”; //立钻哥哥:table字符串只能是key
Debugger.Log(“立钻哥哥:Modify varTable name:{0}”, table[“map.name”]);
myTable.AddTable(“newmap”);
LuaTable myTable1 = (LuaTable)myTable[“newmap”];
myTable1[“name”] = “table1”;
Debugger.Log(“立钻哥哥:varTable.newmap name:{0}”, myTable1[“name”]);
myTable1.Dispose();
myTable1 = myTable.GetMetaTable();
if(myTable1 != null){
Debugger.Log(“立钻哥哥:varTable metatable name: {0}”, myTable1[“name”]);
}
object[] myList = myTable.ToArray();
for(int i = 0; i < myList.Length; i++){
Debugger.Log(“立钻哥哥:varTable[{0}], is {1}”, i, myList);
}
myTable.Dispose();
myLua.CheckTop();
myLua.Dispose();
}
private void OnApplicationQuit(){
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived -= MyShowTips;
#else
Application.RegisterLogCallback(null);
#endif
}
string myTips = null;
void MyShowTips(string msg, string stackTrace, LogType type){
myTips += msg;
myTips += “\r\n”;
}
void OnGUI(){
GUI.Label(new Rect(Screen.width/2 - 300, Screen.height/2 - 200, 600, 400), tips);
}
} //立钻哥哥:public class AccessingLuaVariables : MonoBehaviour{}
++++立钻哥哥:该示例展示了如何访问lua中的变量,table的操作。
++++【luaState[“Objs2Spawn”]】:LuaState通过重载this操作符,访问lua_G表中的变量Objs2Spawn。
++++【LuaState.GetTable()】:从lua中获取一个lua table,可以串式访问,如:lua.GetTable(“varTable.map.name”)等同:varTable->map->name。
++++LuaTable支持this操作符,但此this不支持串式访问。比如table[“map.name”]中,“map.name”只是一个key,不是table->map->name。
++++【LuaTable.GetMetaTable()】:可以获取当前table的metatable。
++++【LuaTable.ToArray()】:获取数组表中的所有对象存入到object[]表中。
++++【LuaTable.AddTable(name)】:在当前的table表中添加一个名字为name的表。
++++【LuaTable.GetTable(key)】:获取t[key]值到C#,类似于lua_gettable。
++++【LuaTable.SetTable(key, value)】:等价于t[k]=v的操作,类似于lua_settable。
++++【LuaTable.RawGet(key)】:获取t[key]值到C#,类似于lua_rawget。
++++【LuaTable.RawSet(key, value)】:等价于t[k]=v的操作,类似于lua_rawset。
++1.2.9、toLua#示例:TestCoroutine(使用lua协同)
//立钻哥哥:TestCoroutine示例(\Assets\ToLua\Examples\05_LuaCoroutine\TestCoroutine.cs)
using UnityEngine;
using System;
using System.Collections;
using LuaInterface;
public class TestCoroutine : MonoBehaviour{
public TextAsset luaFile = null;
private LuaState myLua = null;
private LuaLooper myLooper = null;
void Awake(){
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived += MyShowTips;
#else
Application.RegisterLogCallback(MyShowTips);
#endif
new LuaResLoader();
myLua = new LuaState();
myLua.Start();
LuaBinder.Bind(myLua);
DelegateFactory.Init();
myLooper = gameObject.AddComponent<LuaLooper>();
myLooper.luaState = myLua;
myLua.DoString(luaFile.text, “TestLuaCoroutine.lua”);
LuaFunction myF = myLua.GetFunction(“TestMyCoroutine”);
myF.Call();
myF.Dispose();
myF = null;
}
void OnApplicationQuit(){
myLooper.Destroy();
myLua.Dispose();
myLua = null;
#if UNITY_% || UNITY_2017 || UNITY_2018
Applicaiton.logMessageReceived -= MyShowTips;
#else
Application.RegisterLogCallback(null);
#endif
}
string myTips = null;
void MyShowTips(string msg, string stackTrace, LogType type){
myTips += msg;
myTips +=“\r\n”;
}
void OnGUI(){
GUI.Label(new Rect(Screen.width/2 - 300, Screen.height/2 - 200, 600, 400), myTips);
if(GUI.Button(new Rect(50, 50, 120, 45), “Start Counter”)){
myTips = null;
LuaFunction myFunc = myLua.GetFunction(“StartDelay”);
myFunc.Call();
myFunc.Dispose();
}else if(GUI.Button(new Rect(50, 150, 120, 45), “Stop Counter”)){
LuaFunction myFunc = myLua.GetFunction(“StopDelay”);
myFunc.Call();
myFunc.Dispose();
}else if(GUI.Button(new Rect(50, 250, 120, 45), “立钻哥哥:GC”)){
myLua.DoString(“collectgarbage(‘collect’)”, “TestCoroutine.cs”);
Resources.UnloadUnusedAssets();
}
}
} //立钻哥哥:public class TestCoroutine : MonoBehaviour{}
//立钻哥哥:TestLuaCoroutine.lua(使用lua协同,lua代码部分)
//(tolua-master-\Assets\ToLua\Examples\Resources\Lua\TestLuaCoroutine.lua.bytes)
//fib函数负责计算一个斐波那契n
function fib(n)
local a, b = 0, 1
while n > 0 do
a, b = b, a + b
n = n - 1
end
return a
end
function CoFunc()
print(‘立钻哥哥:Coroutine started!’);
for i = 0, 10, 1do
print(fib(i))
coroutine.wait(0.1)
end
print(“立钻哥哥:current framewCount: ” .. Time.frameCount)
coroutine.step()
print(“立钻哥哥:yield frameCount: ” .. Time.frameCount);
local myWww = UnityEngine.WWW(“http://www.baidu.com”);
coroutine.www(myWww);
local s = tolua.tolstring(myWww.bytes)
print(s:sub(1, 128));
print(‘立钻哥哥:Coroutine ended’)
end
function TestMyCoroutine()
coroutine.start(CoFunc)
end
local coDelay = nil
function Delay()
local c = 1
while true do
coroutine.wait(1)
print(“立钻哥哥:Count:” .. c)
c = c + 1
end
end
function StartDelay()
coDelay = coroutine.start(Delay)
end
function StopDelay()
coroutine.stop(coDelay)
end
++++立钻哥哥:该示例展示了如何使用lua协同。(必须启动LuaLooper驱动协同,这里将一个lua的半双工协同转换为类似unity的全双工协同。)
++++【coroutine.start()】:启动一个lua协同。
++++【coroutine.wait()】:协同中等待一段时间,单位:秒。
++++【coroutine.step()】:协同中等待一帧。
++++【coroutine.www()】:等待一个WWW完成。
++++【tolua.tolstring()】:转换byte数组为lua字符串缓冲。
++++【coroutine.stop()】:停止一个协同。
###1.3、xLua热更新框架
++1.3、xLua热更新框架
++++xLua:https://github.com/Tencent/xLua
++++xLua为Unity、.Net、Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便和C#相互调用。
++1.3.1、xLua的突破
++++xLua在功能、性能、易用性都有不少突破,最具代表性的是:
--1、可以运行时把C#实现(方法,操作符,属性,事件等等)替换成Lua实现;
--2、出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc;
--3、编辑器下无需生成代码,开发更轻量;
++++【总体特性】:
--1、Lua虚拟机支持:Lua5.3、Luajit2.1;
--2、Unity3D版本支持:各版本支持;
--3、平台支持:windows64/32、android、ios 64/32/bitcode、osx、uwp、webgl;
--4、互访技术:生成适配代码、反射;
--5、易用性:解压即可用;开发期无需生成代码;生成代码和反射间可无缝切换;更简单的无GC api;菜单简单易懂;配置可以多份,按模块划分,也可以直接在目标类型上打Attribute标签;自动生成link.xml防止代码剪裁;Plugins部分采用cmake编译,更简单;核心代码不依赖生成代码,可以随时删除生成目录;
--6、性能:lazyload技术,避免用不上的类型的开销;lua函数映射到C# delegate,lua table映射到interface,可实现接口层面无C# gc alloc开销;所有基本值类型,所有枚举,字段都是值类型的struct,在Lua和C#间传递无C# gc alloc;LuaTable,LuaFunction提供无gc访问接口;通过代码生成期的静态分析,生成最优代码;支持C#和Lua间指针传递;自动解除已经Destroy的UnityEngine.Object的引用;
--7、扩展性:不用改代码就可以加入Lua第三方扩展;生成引擎供接口做二次开发;
++++【支持C#实现打补丁】:构造函数、析构函数、成员函数、静态函数、泛化函数、操作符重载、成员属性;静态属性;事件;
++++【Lua代码加载】:加载字符串;支持加载后立即执行;支持加载后返回一个delegate或者LuaFunction,调用delegate或者LuaFunction后可传脚本参数;Resources目录的文件;直接require;自定义loader;Lua里头require时触发;require参数透传给loader,loader读取Lua代码返回;Lua原有的方式;Lua原有的方式都保留;
++++【Lua调用C#】:
--1、创建C#对象:C#静态属性,字段;C#静态方法;C#成员属性,字段;C#成员方法;C#继承;子类对象可以直接调用父类的方法,访问父类属性;子类模块可以直接调用父类的静态方法,静态属性;
--2、扩展方法(Extension methods):就像普通成员方法一样使用;
--3、参数的输入输出属性(out, ref):out对应一个lua返回值;ref对应一个lua参数以及一个lua返回值;
--4、函数重载:支持重载;由于lua数据类型远比C#要少,会出现无法判断的情况,可通过扩展方法来调用;操作符重载;
--5、支持的操作符:+,-,*,/,==,<,<=,%,[];其他操作符可以借助扩展方法调用;
--6、参数默认值:C#参数有默认值,在Lua可以不传;
--7、可变参数:在对应可变参数部分,直接输入一个参数即可,不需要把这些参数扩到一个数组里头;
--8、泛化方法调用:静态方法可以自行封装使用;成员函数可通过扩展方法封装使用;
--9、枚举类型:数字或字符串到枚举的转换;
--10、delegate:调用一个C# delegate;+操作符;-操作符;把一个lua函数作为一个c# delegate传递给C#;
--11、evnet:增加事件回调;移除事件回调;
--12、64位整数:传递无gc而且无精度损失;lua53下使用原生64位支持;可以和number运算;以java的方式支持无符号64位整数;
--13、table的自动转换到C#复杂类型;
--14、obj.complexField = { a=1, b = { c = 1}},obj是一个C#对象,complexField是两层嵌套的struct或者class;
--15、typeof:对应C#的typeof操作符,返回Type对象;
--16、lua侧直接clone;
--17、decimal:传递无gc而且无精度损失;
++++【C#调用Lua】:调用lua函数;以delegate方式调用Lua函数;以LuaFunction调用Lua函数;访问Lua的table;LuaTable的泛化Get/Set接口,调用无gc,可指明Key,Value的类型;用标注了CSharpCallLua的interface访问;值拷贝到struct,class;
++++【Lua虚拟机】:虚拟机gc参数读取及设置;
++++【工具链】:Lua Profiler;可根据函数调用总时长,平均每次调用时长,调用次数排序;显示lua函数名及其所在文件的名字及行号;如果C#函数,会显示这个C#函数;支持真机调试;
++1.3.2、xLua常见问题(立钻哥哥:初学者FAQ)
++++1、xLua发布包怎么用:xLua目前已zip包形式发布,在工程目录下解压即可。
++++2、xLua可以放别的目录吗?
--可以,但生成代码目录需要配置一下(默认放Assets/XLua/Gen目录);
--更改目录要注意的是:生成代码和xLua核心代码必须在同一程序集。如果要用热补丁特性,xLua核心代码必须在Assembly-CSharp程序集;
++++3、lua源码只能以txt后缀?
--什么后缀都可以。
--如果想以TextAsset打包到安装包(比如放到Resources目录),Unity不认lua后缀,这是Unity的规则;
--如果不打包到安装包,就没有后缀的限制:比如自行下载到某个目录(这也是热更的正确姿势),然后通过CustomLoader或者设置package.path去读这个目录;
--为啥xLua本身带的lua源码(包括示例)为什么都是txt结尾呢?因为xLua本身就一个库,不含下载功能,也不方便运行时去某个地方下载代码,通过TextAsset是较简单的方式;
++++4、编辑器(或非il2cpp的android)下运行正常,ios下运行调用某函数报“attempt to call a nil value”:
--il2cpp默认会对诸如引擎、C#系统api,第三方dll等等进行代码裁剪。(简单来说就是这些地方的函数如果C#代码没访问到的就不编译到最终发布包。)
--解决办法:增加引用(比如配置到LuaCallCSharp,或者自己C#代码增加那函数的访问),或者通过link.xml配置(当配置了ReflectionUse后,xlua会自动帮我们配置到link.xml)告诉il2cpp别剪裁某类型;
++++5、Plugins源码在哪里可以找到,怎么使用?
--Plugins源码位于xLua_Project_Root/build下。
--源码编译依赖cmake,按照cmake后执行make_xxxx_yyyy.zz即可。(xxxx代表平台,比如ios,android等)(yyyy是要集成的虚拟机,有lua53和luajit)(zz是后缀,window下是bat,其他平台是sh)
--windows编译依赖Visual Studio 2015;
--android编译在linux下执行,依赖NDK,并且需要把脚本中ANDROID_NDK指向NDK的安装目录。
--ios和osx需要在mac下编译。
++++6、报类似“xlua.access, no field _Hitfix0_Update”的错误怎么解决?
--按Hotfix操作指南一步步操作。
--https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/hotfix.md
--使用方式:
----1、添加HOTFIX_ENABLE宏打开该特性(在Unity3D的File->Build Setting->Scripting Define Symbols下添加)。(编辑器、各手机平台这个宏要分别设置!如果是自动化打包,要注意在代码里头用API设置的宏是不生效的,需要在编辑器设置)(建议平时开发业务代码不打开HOTFIX_ENABLE,只在build手机版本或者要在编辑器下开发补丁时打开HOTFIX_ENABLE)
----2、执行xLua/Generate Code菜单;
----3、注入,构建手机包这个步骤会在构建时自动进行,编辑器下开发补丁需要手动执行“XLua/Hotfix Inject In Editor”菜单。(注入成功会打印“hotfix inject finish!”或者“had injected!”)
--约束:不支持静态构造函数。(目前只支持Assets下代码的热补丁,不支持引擎,C#系统库的热补丁。)
++++7、报“please install the Tools”:没有把Tools安装到Assets平级目录,安装包,或者master下都能找到这个目录。
++++8、报“This delegate/interface must add to CSharpCallLua: XXX”异常怎么解决?
--在编辑器下xLua不生成代码都可以运行,出现这种提示,要么是该类型没加CSharpCallLua,要么是加之前生成过代码,没重新执行生成。
--解决方法,确认XXX(类型)加上CSharpCallLua后,清除代码后运行。
--如果编辑器下没有问题,发布到手机报这错,表示你发布前没有生成代码(执行“XLua/Generate Code”)。
++++9、Unity5.5以上执行“XLua/Hotfix Inject In Editor”菜单会提示“WARNING: The runtime version supported by this application is unavailable.”
--这是因为注入工具是用.net3.5编译,而Unity5.5中MonoBleedingEdge的mono环境并没3.5支持导致的,不过一般而言都向下兼容,目前为止也没发现该warning带来什么问题。
--可能有人发现定义INJECT_WITHOUT_TOOL用内嵌模式会没有该warning,但问题是这模式是调试问题用的,不建议使用,因为可能会有一些库冲突问题。
++++10、hotfix下怎么触发一个event。
--首先通过xlua.private_accessible开启私有成员访问。
--跟着通过对象的“&事件名”字段调用delegate。(例如:self[‘&MyEvent’](),其中MyEvent是事件名。)
++++11、怎么对Unity Coroutine的实现函数打补丁?
--见Hotfix操作指南。
--https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/hotfix.md
--xLua可以用lua函数替换C#的构造函数,函数,属性,事件的替换。Lua实现都是函数,比如属性对于一个getter函数和一个setter函数,事件对应一个add函数和一个remove函数。
--Unity协程:通过util.cs_generator可以用一个function模拟一个IEnumerator,在里头用coroutine.yield,就类似C#里头的yield return。
++++12、支持NGUI(后者UGUI/DOTween等)么?
--支持,xLua最主要的特性是让原来用C#写的地方可以换成用lua写,C#能用的插件,基本都能用。
++++13、如果需要调试,CustomLoader的filepath参数该如何处理?
--lua里头调用require ‘a.b’时,CustomLoader会被调用,并传入字符串“a.b”,需要理解这字符串,(从文件/内存/网络等)加载好lua文件,返回两个东西,第一个是调试器可以理解的路径,比如:a/b.lua,这个通过设置ref类型的filepath参数返回,第二个是UTF8格式的源码的字节流(byte[]),通过返回值返回。
++++14、什么是生成代码?
--xLua支持的lua和C#间交互技术之一,这种技术通过生成两者间的适配代码来实现交互,性能较好,是推荐的方式。
--另一种交互技术是反射,这种方式对安装包的影响更少,可以在性能要求不高或者对安装包大小很敏感的场景下使用。
++++15、改了接口后,之前生成的代码出现错误怎么办?
--清除掉生成代码(执行“Clear Generated Code”菜单,如果重启过,会找不到这个菜单,这是可以手动删除整个生成代码目录),等编译完成后重新生成。
++++16、应该什么时候生成代码?
--开发期不建议生成代码,可以避免很多由于不一致导致的编译失败,以及生成代码本身的编译等待。
--build手机版本前必须执行生成代码,建议做成自动化的。
--做性能调优,性能测试前必须执行生成代码,因为生成和不生成性能的区别还是很大的。
++++17、CS命名空间下有所有C# API是不是很占内存?
--由于用了lazyload,这个“有”只是个虚拟的概念,比如UnityEngine.GameObject,是访问第一次CS.UnityEngine.GameObject或者第一个实例往lua传送才加载该类型方法,属性等。
++++18、LuaCallSharp以及CSharpCallLua两种生成各在什么场景下用?
--看调用者和被调用者,比如要在lua调用C#的GameObject.Find函数,或者调用gameobject的实例方法,属性等,GameObject类要加LuaCallSharp,而想把一个lua函数挂到UI回调,这时调用者是C#,被调用的是一个lua函数,所以回调声明的delegate要加CSharpCallLua。
--有时会比较迷惑人,比如List.Find(Predicate match)的调用,List当然是加LuaCallSharp,而Predicate却要加CSharpCallLua,因为match的调用者在C#,被调用的是一个lua函数。
--更无脑一点的方式是看到“立钻哥哥:This delegate/interface must add to CSharpCallLua:XXX”,就把XXX加到CSharpCallLua即可。
++++19、值类型传递会有gc alloc么?
--如果使用的是delegate调用lua函数,或者用LuaTable、LuaFunction的无gc接口,或者数组的话,已下值类型都没有gc的:
----1、所有的基本值类型(所有整数,所有浮点数,decimal);
----2、所有的枚举类型;
----3、字段只包含值类型的struct,可嵌套其它只包含值类型struct;
----立钻哥哥:其中2、3需要把该类型加到GCOptimize。
++++20、反射在ios下可用吗?
--ios下的限制有两个:1、没有jit;2、代码剪裁(stripping);
--对于C#通过delegate或者interface调用lua,如果不生成代码是反射的emit,这依赖jit,所以这目前只在编辑器可用。
--对于lua调用C#,主要会被代码剪裁影响,这时可以配置ReflectionUse(不要配LuaCallSharp),执行“Generate Code”,这时不会对该类生成封装代码,而是生成link.xml把该类配置为不剪裁。
--简而言之,除了CSharpCallLua是必须的(这类生成代码往往不多),LuaCallSharp生成都可以改为用反射。
++++21、支持泛型方法的调用么?
--部分支持,支持的程度可以看示例9。
--其它情况也有办法调用到。如果是静态方法,可以自己写个封装来实例化泛型方法。
--如果是成员方法,xLua支持扩展方法,可以添加一个扩展方法来实例化泛型方法。(该扩展方法使用起来就和普通成员方法一样。)
//立钻哥哥:扩展方法实例化泛型方法
//C#
public static Button GetButton(this GameObject go){
return go.GetComponent<Button>();
}
//--lua
local go = CS.UnityEngine.GameObject.Find(“button”)
go.GetButton().onClick:AddListener(
function() print(‘onClick’)end
)
++++22、支持lua调用C#重载函数吗?
--支持,但没有C#端支持的那么完善,比如重载方法void Foo(int a)和void Foo(short a),由于int和short都对应lua的number,是没法根据参数判断调用的是哪个重载。这是可以借助扩展方法来为其中一个起一个别名。
++++23、编辑器下运行正常,打包的时候生成代码报“没有某方法/属性/字段定义”怎么办?
--往往是由于该方法/属性/字段是扩在条件编译里头,只在UNITY_EDITOR下有效,这是可以通过这方法/属性/字段加到黑名单来解决,加了之后要等编译完成后重新执行代码生成。
++++24、this[string field]或者this[object field]操作符重载为什么在lua无法访问?(比如Dictionary<string, xxx>,Dictionary<object, xxx>在lua中无法通过dic[‘abc’]或者dic.abc检索值)
--因为1:这个特性会导致基类定义的方法、属性、字段等无法访问(比如Animation无法访问到GetComponent方法);
--因为2:key为当前类某方法、属性、字段的名字的数据无法检索,比如Dictionary类型,dic[‘TryGetValue’]返回的是一个函数,指向Dictionary的TryGetValue方法。
--如果版本大于2.1.11,可以用get_Item来获取值,用set_Item来设置值。要注意只有this[string field]或者this[object field]才有这两个替代api,其它类型的key是没有的。
--如果版本小于或等于2.1.11,建议直接方法该操作符的等效方法,比如Dictionary的TryGetValue,如果该方法没有提供,可以在C#那通过Extension method封装一个使用。
++++25、有的Unity对象,在C#为null,在lua为啥不为nil呢?比如一个已经Destroy的GameObject。
--其实那C#对象并不为null,是UnityEngine.Object重载的==操作符,当一个对象被Destroy,未初始化等情况,obj==null返回true,但这C#对象并不为null,可以通过System.Object.ReferenceEquals(null,obj)来验证下。
--对这种情况,可以为UnityEngine.Object写一个扩展方法:
[LuaCallCSharp]
[ReflectionUse]
public static class UnityEngineObjectExtention{
public static bool IsNull(this UnityEngine.Object o){
return o == null;
}
}
--然后在lua那对所有UnityEngine.Object实例都使用IsNull判断:
print(go.GetComponent(‘Animator’):IsNull())
++++26、泛型实例怎么构造?
--涉及的类型都在mscorlib,Assembly-CSharp程序集的话,泛型实例的构造和普通类型时一样的,都是CS.namespace.typename(),可能比较特殊的是typename的表达,泛型实例的typename的表达包含了标识符非法符号,最后一部分要换成[“typename”],以List为例:
----local lst = CS.System.Collections.Generic[‘List`1[System.String]’]();
--如果某个泛型实例的typename不确定,可以在C#测打印下typeof(不确定的类型).ToString()。
--如果涉及mscorlib,Assembly-CSharp程序集之外的类型的话,可以用C#的反射来做:
----local dic = CS.System.Activator.CreateInstance(CS.System.Type.GetType(‘System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[UnityEngine.Vector3, UnityEngine]], mscorlib’))
----dic:Add(‘a’, CS.UnityEngine.Vector3(1, 2, 3))
----print(dic:TryGetValue(‘a’))
++++27、调用LuaEnv.Dispose时,报“try to dispose a LuaEnv with C# callback!”错是什么原因?
--这是由于C#还存在指向lua虚拟机里头某个函数delegate,为了防止业务在虚拟机释放后调用这些无效(因为其引用的lua函数所在虚拟机都释放了)delegate导致的异常甚至崩溃,做了这个检查。
--怎么解决?释放这些delegate即可,所谓释放,在C#中,就是没有引用:
----是在C#通过LuaTable.Get获取并保存到对象成员,赋值该成员为null;
----是在lua那把lua函数注册到一些事件事件回调,反注册这些回调;
----是通过xlua.hotfix(class, method, func)注入到C#,则通过xlua.hotfix(class, method, nil)删除;
--立钻哥哥:注意以上操作在Dispose之前完成。
++++28、调用LuaEnv.Dispose崩溃?
--很可能是这个Dispose操作是由lua那驱动执行,相当于在lua执行的过程中把lua虚拟机给释放了,改为只由C#执行即可。
++++29、C#参数(或字段)类型是object时,传递整数默认是以long类型传递,如何指明其它类型?比如int。
//立钻哥哥:示例参考
using UnityEngine;
using XLua;
namespace YanlzXLuaTest{
public class RawObjectTest : MonoBehaviour{
public static void PrintType(object o){
Debug.Log(“立钻哥哥:type:” + o.GetType() + “ , value: ” + o);
}
//Use this for initialization
void Start(){
LuaEnv luaenv = new LuaEnv();
//直接传1234到一个object参数,xLua将选择能保留最大精度的long来传递
luaenv.DoString(“CS.YanlzXLuaTest.RawObjectTest.PrintType(1234)”);
//立钻哥哥:通过一个继承RawObject的类,能实现指明以一个int来传递
luaenv.DoString(“CS.YanlzXLuaTest.RawObjectTest.PrintType(CS.XLua.Cast.Int32(1234))”);
luaenv.Dispose();
}
} //立钻哥哥:public class RawObjectTest : MonoBehaviour{}
} //立钻哥哥:namespace YanlzXLuaTest
++++30、如何做到先执行原来的C#逻辑,然后再执行补丁?
--用util.hotfix_ex,可以调用原先的C#逻辑。
local util = require ‘xlua.util’
util.hotfix_ex(CS.HotfixTest, ‘Add’, function(self, a, b)
local org_sum = self:Add(a, b)
print(‘org_sum’, org_sum)
return a + b
end)
++++31、怎么把C#的函数赋值给一个委托字段?
--2.1.8及之前版本,把C#函数当成一个lua函数即可,性能会略低,因为委托调用时先通过Bridge适配代码调用lua,然后lua再调用回C#。
--2.1.9 xlua.util新增createdelegate函数。
++++32、为什么有时Lua错误直接中断了而没错误信息?
--情况1:错误代码用协程跑,而标准的lua,协程出错是通过resume返回值来表示。如果希望协程出错直接抛异常,可以在resume调用那加个assert。(【coroutine.resume(co, ...)】改为:【assert(coroutine.resume(co, ...))】)
--情况2:上层catch后,不打印。(比如某些sdk,在回调业务时,try-catch后把异常吃了。)
++++33、重载含糊如何处理?
--比如由于忽略out参数导致的Physics.Raycast其中的一个重载调用不了,比如short,int无法区分的问题。
--首先out参数导致重载含糊比较少见,比如Physics.Raycast,建议通过自行封装来解决(short, int这种情况也适用):静态函数的直接封装个另外名字的,如果是成员方法则通过Extension method来封装。
++++34、支持interface扩展方法么?
--考虑到生成代码量,不支持通过obj:ExtensionMethod()的方式去调用,支持通过静态方法的方式去调用CS.ExtensionClass.ExtensionMethod(obj)
++立钻哥哥推荐的拓展学习链接(Link_Url):
++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/
++++Lua快速入门篇(基础概述):https://blog.csdn.net/VRunSoftYanlz/article/details/81041359
++++框架知识点:https://blog.csdn.net/VRunSoftYanlz/article/details/80862879
++++游戏框架(UI框架夯实篇):https://blog.csdn.net/vrunsoftyanlz/article/details/80781140
++++游戏框架(初探篇):https://blog.csdn.net/VRunSoftYanlz/article/details/80630325
++++设计模式简单整理:https://blog.csdn.net/vrunsoftyanlz/article/details/79839641
++++U3D小项目参考:https://blog.csdn.net/vrunsoftyanlz/article/details/80141811
++++UML类图:https://blog.csdn.net/vrunsoftyanlz/article/details/80289461
++++Unity知识点0001:https://blog.csdn.net/vrunsoftyanlz/article/details/80302012
++++U3D_Shader编程(第一篇:快速入门篇):https://blog.csdn.net/vrunsoftyanlz/article/details/80372071
++++U3D_Shader编程(第二篇:基础夯实篇):https://blog.csdn.net/vrunsoftyanlz/article/details/80372628
++++Unity引擎基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78881685
++++Unity面向组件开发:https://blog.csdn.net/vrunsoftyanlz/article/details/78881752
++++Unity物理系统:https://blog.csdn.net/vrunsoftyanlz/article/details/78881879
++++Unity2D平台开发:https://blog.csdn.net/vrunsoftyanlz/article/details/78882034
++++UGUI基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78884693
++++UGUI进阶:https://blog.csdn.net/vrunsoftyanlz/article/details/78884882
++++UGUI综合:https://blog.csdn.net/vrunsoftyanlz/article/details/78885013
++++Unity动画系统基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78886068
++++Unity动画系统进阶:https://blog.csdn.net/vrunsoftyanlz/article/details/78886198
++++Navigation导航系统:https://blog.csdn.net/vrunsoftyanlz/article/details/78886281
++++Unity特效渲染:https://blog.csdn.net/vrunsoftyanlz/article/details/78886403
++++Unity数据存储:https://blog.csdn.net/vrunsoftyanlz/article/details/79251273
++++Unity中Sqlite数据库:https://blog.csdn.net/vrunsoftyanlz/article/details/79254162
++++WWW类和协程:https://blog.csdn.net/vrunsoftyanlz/article/details/79254559
++++Unity网络:https://blog.csdn.net/vrunsoftyanlz/article/details/79254902
++++C#事件:https://blog.csdn.net/vrunsoftyanlz/article/details/78631267
++++C#委托:https://blog.csdn.net/vrunsoftyanlz/article/details/78631183
++++C#集合:https://blog.csdn.net/vrunsoftyanlz/article/details/78631175
++++C#泛型:https://blog.csdn.net/vrunsoftyanlz/article/details/78631141
++++C#接口:https://blog.csdn.net/vrunsoftyanlz/article/details/78631122
++++C#静态类:https://blog.csdn.net/vrunsoftyanlz/article/details/78630979
++++C#中System.String类:https://blog.csdn.net/vrunsoftyanlz/article/details/78630945
++++C#数据类型:https://blog.csdn.net/vrunsoftyanlz/article/details/78630913
++++Unity3D默认的快捷键:https://blog.csdn.net/vrunsoftyanlz/article/details/78630838
++++游戏相关缩写:https://blog.csdn.net/vrunsoftyanlz/article/details/78630687
++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/
--_--VRunSoft : lovezuanzuan--_-- |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|