一.热更新方案简介

  在Unity游戏工程中,C#代码(编译型语言)资源和Resources文件夹下的资源打包后都不可以更改,因此这部分内容不能进行热更新,而lua代码(解释型语言)逻辑不需要进行预编译再运行,可以在游戏运行过程中进行修改,AB包资源也可以在游戏运行过程中下载解压缩并使用其中的资源。因此客户端可以在启动时检验服务器端的AB包资源是否有更新,如果有更新先下载更新,将lua代码资源和其他更新资源打包为AB包放在服务器端,客户端下载后直接在运行过程中解压缩并使用更新资源,实现了客户端不中断运行即完成更新的目的,也就是热更新。

二.xlua热更新方案简介

  xlua框架提供了C#和lua相互调用的功能及Hotfix热补丁的功能,主要目的是方便我们将纯C#工程在不重做的情况下改造成具备热更新功能的工程。

三.准备工作--说明:使用的Unity版本为2019.4.18f1c1,导入的xlua为2021年4月4日从GitHub上直接clone的工程文件,没有下载release版本。

  1.xlua框架导入

    在GitHub上搜索xlua,找到腾讯的xlua项目,下载项目的压缩包。

    下载完成后解压,发现下载的是一个Unity工程文件:

    在工程文件中,核心代码是Assets目录下的Plugins和XLua这两个文件夹中的内容,将其复制到自己的工程文件中即可。

    将这两个文件夹复制到自己的工程中后稍等一会儿,就会发现在菜单栏中Windows菜单选项左侧出现了XLua菜单选项,没有报错的话说明成功导入。

  2.AB包工具导入

    在Unity中通过PackageManager导入AB包工具,导入方法详见:热更新基础--AssetBundle学习笔记

  3.AB包管理器

    为了方便加载AB包,我们可以制作一个AB包的管理器脚本,脚本详见:热更新基础--AssetBundle学习笔记

四.C#调用lua

  1.lua解析器

    void Start()
{
//Lua解析器,能够在Unity中执行Lua
LuaEnv env = new LuaEnv(); //执行单行Lua语言,使用DoString成员方法
env.DoString("print('hello world!')"); //执行lua脚本,一般都是直接调用lua语言的require关键字执行lua脚本
//默认寻找脚本的路径是在Resources下
//lua后缀Unity不能识别,需要将lua文件添加上.txt以便Unity识别
env.DoString("require('Main')"); //清除lua中没有手动释放的对象,相当于垃圾回收,一般在帧更新中定时执行或者切换场景时执行
env.Tick(); //销毁lua解析器,但是一般不会销毁,因为最好保持解析器的唯一性以节约性能
env.Dispose();
}

  2.lua文件加载重定向,即更改使用require关键字加载lua文件时寻找lua文件的位置(默认lua脚本在Resources下才能识别,这和热更新的目的冲突)

    void Start()
{
LuaEnv env = new LuaEnv(); //使用AddLoader方法添加重定向,即自定义文件加载的规则
//参数为一个委托,这个委托有一个ref参数,自动执行传入require执行的脚本文件名,在委托中拼接好完整的路径;委托的返回值为lua文件转化出的字节数组
//添加委托后,委托在执行require语句时自动执行
env.AddLoader(MyCustomLoader); //使用require语句执行lua文件,会自动先调用添加的重定向方法寻找lua文件,如果找不到再到默认路径Resources下寻找
env.DoString("require('Main')");
} /// <summary>
/// 重定向方法
/// </summary>
/// <param name="filePath">文件名</param>
/// <returns></returns>
private byte[] MyCustomLoader(ref string filePath)
{
//拼接完整的lua文件所在路径
string path = Application.dataPath + "/Lua/" + filePath + ".lua";
Debug.Log(path);
//判断文件是否存在,存在返回读取的文件字节数组,不存在打印提醒信息,返回null
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("MyCustomLoader重定向失败,文件名为" + filePath);
}
return null;
}

  3.lua解析器管理器:对lua解析器的进一步封装以方便使用

/// <summary>
/// lua管理器,对lua解析器的进一步封装,保证lua解析器的唯一性
/// </summary>
public class LuaManager
{
//单例模块
private static LuaManager instance;
public static LuaManager Instance
{
get
{
if (instance == null)
instance = new LuaManager();
return instance;
}
}
private LuaManager()
{
//在构造方法中就为唯一的lua解析器赋值
luaEnv = new LuaEnv();
//加载lua脚本重定向
//重定向到lua文件夹下
luaEnv.AddLoader((ref string filePath) =>
{
//拼接完整的lua文件所在路径
string path = Application.dataPath + "/Lua/" + filePath + ".lua";
//判断文件是否存在,存在返回读取的文件字节数组,不存在打印提醒信息,返回null
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("MyCustomLoader重定向失败,文件名为" + filePath);
}
return null;
});
//重定向加载AB包中的lua脚本
luaEnv.AddLoader((ref string filePath) =>
{
/*//加载AB包
string path = Application.streamingAssetsPath + "/lua";
AssetBundle bundle = AssetBundle.LoadFromFile(path); //加载lua文件,返回
TextAsset texts = bundle.LoadAsset<TextAsset>(filePath + ".lua");
//返回加载到的lua文件的byte数组
return texts.bytes;*/ /*//使用AB包管理器加载,异步加载
byte[] luaBytes = null;
AssetBundleManager.Instance.LoadResAsync<TextAsset>("lua", filePath + ".lua", (lua) =>
{
if (lua != null)
luaBytes = lua.bytes;
else
Debug.Log("重定向失败,从AB包加载lua文件失败");
});
return luaBytes;*/ //使用AB包管理器加载,同步加载
return AssetBundleManager.Instance.LoadRes<TextAsset>("lua", filePath + ".lua").bytes;
});
} //持有一个唯一的lua解析器
private LuaEnv luaEnv; //luaEnv中的大G表,提供给外部调用
public LuaTable Global
{
get
{
//校验一下instance是否是null,避免dispose后无法获取的情况出现
if (instance == null)
instance = new LuaManager();
return luaEnv.Global;
}
} /// <summary>
/// 执行单句lua代码
/// </summary>
/// <param name="luaCodeString"></param>
public void DoString(string luaCodeString)
{
luaEnv.DoString(luaCodeString);
}
/// <summary>
/// 执行lua文件的代码,直接提供文件名即可执行文件,不需要再书写lua的require语句,在方法内部拼接lua语句
/// </summary>
/// <param name="fileName">lua文件名</param>
public void DoLuaFile(string fileName)
{
luaEnv.DoString("require('" + fileName + "')");
}
/// <summary>
/// 释放解析器
/// </summary>
public void Tick()
{
luaEnv.Tick();
}
/// <summary>
/// 销毁解析器
/// </summary>
public void Dispose()
{
luaEnv.Dispose();
//销毁解析器后将lua解析器对象和单例变量都置空,下次调用时会自动调用构造函数创建lua解析器,以免报空
luaEnv = null;
instance = null;
}
}

  4.访问变量

void Start()
{
LuaManager.Instance.DoLuaFile("Main"); //使用_G表获取luaenv中的global变量值
Debug.Log(LuaManager.Instance.Global.Get<int>("testNumber"));
Debug.Log(LuaManager.Instance.Global.Get<bool>("testBool"));
Debug.Log(LuaManager.Instance.Global.Get<float>("testFloat")); //使用_G表修改luaenv中的global变量值
LuaManager.Instance.Global.Set("testBool",false);
Debug.Log(LuaManager.Instance.Global.Get<bool>("testBool")); //不能直接获取和设置本地变量
}

  5.访问函数,使用委托接收

//自定义委托,对于有参数和返回值的委托,必须加上特性[CSharpCallLua],否则无法处理,无参无返回值的委托不需要
//特性起作用还需要在Unity中生成脚本
[CSharpCallLua]
public delegate void CustomCall(int a);
//自定义含有out或者ref参数的委托用于接收多返回值函数,out和ref根据需要选择,都可以用于接收多返回值
[CSharpCallLua]
public delegate int CustomCall2(out int a, out int b);
[CSharpCallLua]
public delegate void CustomCall3(params int[] args);
    void Start()
{
LuaManager.Instance.DoLuaFile("Main"); //获取函数,使用委托存储
UnityAction npnr = LuaManager.Instance.Global.Get<UnityAction>("NoParamNoReturn");
npnr(); //xlua提供了获取函数的方法,但是不推荐使用,推荐使用Unity或者C#提供的委托或者自定义委托存储
LuaFunction luaFun = LuaManager.Instance.Global.Get<LuaFunction>("NoParamNoReturn");
luaFun.Call(); //有参无返回值
//使用自定义的委托需要声明特性且在Unity中生成代码
CustomCall hpnr = LuaManager.Instance.Global.Get<CustomCall>("HaveParamNoReturn");
hpnr(2); //使用C#提供的委托存储,不用声明特性
Action<int> hpnr2 = LuaManager.Instance.Global.Get<Action<int>>("HaveParamNoReturn");
hpnr2(2); //多返回值
//不能使用系统自带的委托,多返回值需要自定义委托
CustomCall2 mr = LuaManager.Instance.Global.Get<CustomCall2>("ManyReturns");
int m;
int n;
int p = mr(out m, out n);
Debug.Log(m + "-" + n + "-" + p); //变长参数
CustomCall3 vp = LuaManager.Instance.Global.Get<CustomCall3>("VariableParams");
vp(1, 2, 3, 4, 5);
}

  6.表映射为List或者Dictionary

    void Start()
{
LuaManager.Instance.DoLuaFile("Main"); //得到List
List<int> list = LuaManager.Instance.Global.Get<List<int>>("ListTable");
foreach (int i in list)
{
Debug.Log(i);
} //得到Dictionary
Dictionary<string, int> dic = LuaManager.Instance.Global.Get<Dictionary<string, int>>("DictionaryTable");
foreach (KeyValuePair<string,int> pair in dic)
{
Debug.Log(pair.Key + ":" + pair.Value);
}
}

  7.表映射到类对象

/// <summary>
/// 声明一个类来和lua中的类进行映射,变量名称必须和lua中的对应类一致,但是不必一一对应,映射时会自动丢弃找不到的内容
/// </summary>
public class CallLuaClass
{
public string name;
public int age;
public int sex;
public UnityAction eat;
}
    void Start()
{
LuaManager.Instance.DoLuaFile("Main"); //获得类对象
CallLuaClass clc = LuaManager.Instance.Global.Get<CallLuaClass>("testClass");
Debug.Log(clc.name + "-" + clc.age + "-" + clc.sex);
clc.eat();
}

  8.表映射到接口

/// <summary>
/// 使用一个接口接收表的映射,但是接口中的变量不允许被赋值,这里使用属性
/// 必须加上特性[CSharpCallLua]
/// </summary>
[CSharpCallLua]
public interface ICSharpCallLua
{
string name { get; set; }
int age { get; set; }
int sex { get; set; }
Action eat { get; set; }
}
    void Start()
{
LuaManager.Instance.DoLuaFile("Main"); //得到接口对象
ICSharpCallLua icscl = LuaManager.Instance.Global.Get<ICSharpCallLua>("testClass");
Debug.Log(icscl.name + "-" + icscl.age + "-" + icscl.sex);
icscl.eat();
}

  注意:之前实现的所有拷贝都是引用拷贝,也就是c#中的拷贝值发生改变,lua代码不受影响,但是接口的拷贝是引用拷贝,也就是改变C#中的拷贝的值,lua中的值也发生了改变。

  9.映射到luaTable类

    void Start()
{
LuaManager.Instance.DoLuaFile("Main"); //得到LuaTable,对应lua中的table。
//本质上Global也是LuaTable类型的变量,使用方法和之前通过Global获取各种变量函数等方法相同
//不推荐使用LuaTable和LuaFunction,效率低
//LuaTable的拷贝是引用拷贝
LuaTable table = LuaManager.Instance.Global.Get<LuaTable>("testClass");
Debug.Log(table.Get<int>("age"));
Debug.Log(table.Get<string>("name"));
Debug.Log(table.Get<int>("sex"));
table.Get<LuaFunction>("eat").Call();
}

  LuaTable类对应Lua中的表,本质上Global变量也是LuaTable类型,所以LuaTable的使用方法和之前讲的通过Global获取各种变量的方法相同。LuaTable和LuaFunction使用后记得调用dispose方法释放垃圾,否则容易造成内存泄漏。

五.lua调用C#

  1.在Unity中无法直接运行lua,因此需要使用C#脚本作为lua脚本的主入口启动lua脚本的运行,接下来都不再赘述这一步骤,所有的lua代码也都在这个特定的lua脚本中编写。

public class Main : MonoBehaviour
{
private void Start()
{
//在这个脚本中启动特定的lua脚本,接下来的lua代码都在这个脚本中编写
LuaManager.Instance.DoLuaFile("Main");
}
}

  Main.lua这个脚本作为lua脚本的入口,接下来再在这个Main.lua脚本中调用其他脚本。

require("CallClass")

  2.创建类对象

--lua中调用C#脚本

--创建类对象
--Unity中的类如GameObject、Transform等类都存储在CS表中
--使用CS.命名空间.类名的方式调用Unity中的类
local obj1 = CS.UnityEngine.GameObject("使用lua创建的第一个空物体")

--lua中调用C#脚本

--创建类对象
--Unity中的类如GameObject、Transform等类都存储在CS表中
--使用CS.命名空间.类名的方式调用Unity中的类
--每次都写命名空间太麻烦,可以定义全局变量先把类存储起来,也能节约性能
GameObject = CS.UnityEngine.GameObject
local obj = GameObject("movin") --使用点来调用静态方法
local obj2 = GameObject.Find("movin") --使用.来调用对象中的成员变量
Log = CS.UnityEngine.Debug.Log
Log(obj.transform.position) Vector3 = CS.UnityEngine.Vector3
--使用对象中的成员方法必须使用:调用
obj.transform:Translate(Vector3.right)
Log(obj.transform.position) --自定义类的调用
--直接使用CS点的方式调用
local customClass = CS.CustomClass()
customClass.name = "movin"
customClass:Eat() --有命名空间的类再点一层
local customClassInNamespace = CS.CustomNamespace.CustomClassInNamespace()
customClassInNamespace.name = "movin2"
customClassInNamespace:Eat() --继承了mono的类不能new出来,只能获取组件
--xlua提供了typeof的方法得到类的Type
--自定义的脚本组件直接用CS点出来即可
obj:AddComponent(typeof(CS.Main))
--系统自带的脚本一般在UnityEngine命名空间下
obj:AddComponent(typeof(CS.UnityEngine.Rigidbody))
/// <summary>
/// 自定义类
/// </summary>
public class CustomClass
{
public string name;
public void Eat()
{
Debug.Log(name + "在吃饭");
}
} /// <summary>
/// 自定义类,包裹在命名空间中
/// </summary>
namespace CustomNamespace
{
public class CustomClassInNamespace
{
public string name;
public void Eat()
{
Debug.Log(name + "在吃饭");
}
}
}

  3.使用枚举

--调用枚举

--调用Unity提供的枚举
--Unity提供的枚举一般在UnityEngine中 PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject local obj = GameObject.CreatePrimitive(PrimitiveType.Cube) --调用自定义的枚举
E_CustomEnum = CS.E_CustomEnum Log = CS.UnityEngine.Debug.Log
Log(E_CustomEnum.Idle) --使用_CastFrom方法进行枚举类型转换,可以从数字转换成枚举或者字符串转换成枚举
Log(E_CustomEnum.__CastFrom(1))
Log(E_CustomEnum.__CastFrom("Atk"))

  4.使用List和Dictionary

local CustomClass = CS.CustomClass
local Log = CS.UnityEngine.Debug.Log --调用数组,使用C#的数组相关API,不要使用lua的方法
obj = CustomClass();
Log(obj.array.Length) --遍历数组,注意从0到length-1,按照C#的下标遍历不是lua的
for i=0,obj.array.Length-1 do
Log(obj.array[i])
end Log("******************")
--创建数组,利用数组类Array的CreateInstance静态方法创建数组
--参数意思:创建数组中存储元素的类型、创建的数组的长度
local arr = CS.System.Array.CreateInstance(typeof(CS.System.Int32),5)
Log(arr.Length)
Log(arr[1]) Log("******************")
--调用List,调用成员方法用:
obj.list:Add('M')
for i = 0,obj.list.Count-1 do
Log(obj.list[i])
end Log("******************")
--创建List
--老版,方法很麻烦
local list2 = CS.System.Collections.Generic["List`1[System.String]"]()
list2:Add("abcde")
Log(list2[0])
--新版 版本>v2.1.12 先创建一个类,再实例化出来list
local List_String = CS.System.Collections.Generic.List(CS.System.String)
local list3 = List_String()
list3:Add("aaaaaaaaaa")
Log(list3[0]) Log("******************")
--调用dic
obj.dic:Add(1,"abc")
obj.dic:Add(2,"def")
--遍历
for k,v in pairs(obj.dic) do
Log(k.."--"..v)
end Log("******************")
--创建dic
local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dic2 = Dic_String_Vector3()
dic2:Add("abc",CS.UnityEngine.Vector3.right)
dic2:Add("def",CS.UnityEngine.Vector3.up) Log(dic2["abc"]) --在lua中创建的字典使用这种方式得不到值,这句代码打印出的结果是空值
Log(dic2:get_Item("abc")) --在lua中自己创建的字典使用get_Item方法得到值
dic2:set_Item("abc",CS.UnityEngine.Vector3.left) --同样地,通过set_Item方法设置字典地值
Log(dic2:get_Item("abc"))
print(dic2:TryGetValue("abc")) --也可以通过TryGetValue方法获取值 for k,v in pairs(dic2) do
print(k,v)
end
/// <summary>
/// 自定义类
/// </summary>
public class CustomClass
{
public string[] array = { "a","b","c","d","e","f","g","h" };
public List<char> list = new List<char>{ 'A','B','C','D','E','F','G','H','I','J' };
public Dictionary<int, string> dic = new Dictionary<int, string>();
}

  5.使用C#拓展方法

CustomClass = CS.CustomClass

--使用成员方法
local customClass = CustomClass()
customClass.name = "movin"
customClass:Eat() --使用拓展方法,拓展方法一定是静态方法,但是调用时和成员方法一样的调用方式
--在定义拓展方法的工具类前一定加上特性[LuaCallCSharp],并且生成代码
customClass:Move()
/// <summary>
/// 自定义类
/// </summary>
public class CustomClass
{
public string name;
public void Eat()
{
Debug.Log(name + "在吃饭");
}
}
/// <summary>
/// 工具类,定义拓展方法
/// </summary>
[LuaCallCSharp]
public static class Tools
{
public static void Move(this CustomClass cc)
{
Debug.Log(cc.name + "在移动");
}
}

  

  建议:要在lua中使用的C#类都可以加上[LuaCallCSharp]特性,这样预先将代码生成,可以提高Lua访问C#类的性能。

  6.使用含有ref和out参数的函数

CustomClass = CS.CustomClass
local obj = CustomClass() --ref参数,使用多返回值形式接收
--如果函数有返回值,这个返回值是多返回值的第一个
--参数数量不够,会默认使用默认值补位
local a,b,c = obj:RefFun(1,0,0,1)
print(a,b,c) --out参数,还是以多返回值的形式接收
--out参数不需要传递值
local a,b,c = obj:OutFun(23,24)
print(a,b,c) --综合来看
--从返回值上看,ref和out都会以多返回值的形式返回,原来如果有返回值的话原来的返回值是多返回值中的第一个
--从参数看,ref参数需要传递,out参数不需要传递
local a,b,c = obj:RefOutFun(12,23)
print(a,b,c)
/// <summary>
/// 自定义类
/// </summary>
public class CustomClass
{
public int RefFun(int a ,ref int b,ref int c,int d)
{
b = a + d;
c = a - d;
return 100;
}
public int OutFun(int a,out int b,out int c,int d)
{
b = a;
c = d;
return 200;
}
public int RefOutFun(int a,out int b,ref int c)
{
b = a * 10;
c = a * 20;
return 200;
}
}

  7.使用重载函数

CustomClass = CS.CustomClass
local customClass = CustomClass() --使用重载函数
--lua支持调用C#的重载函数
--lua中的数值类型只有number,所以对C#中多精度的重载函数支持不好,使用时可能出现问题
--如第四个重载函数调用结果为0(应该是11.4),所以应避免这种情况
print(customClass:Calc())
print(customClass:Calc(1))
print(customClass:Calc(2,3))
print(customClass:Calc(1.4)) --解决重载函数含糊的问题(效率低,仅作了解)
--运用反射
local m1 = typeof(CustomClass):GetMethod("Calc",{typeof(CS.System.Int32)})
local m2 = typeof(CustomClass):GetMethod("Calc",{typeof(CS.System.Single)})
--通过xlua提供的tofunction方法将反射得到的方法信息转化为函数
local f1 = xlua.tofunction(m1)
local f2 = xlua.tofunction(m2)
--再次调用函数,非静态方法需要传入对象
print(f1(customClass,10))
print(f2(customClass,1.4))
/// <summary>
/// 自定义类
/// </summary>
public class CustomClass
{
public int Calc()
{
return 100;
}
public int Calc(int a,int b)
{
return a + b;
}
public int Calc(int a)
{
return a;
}
public float Calc(float a)
{
return a + 10;
}
}

  8.委托和事件

local customClass = CS.CustomClass()

--委托中存储的是函数,声明函数存储到委托中
local fun = function()
print("函数fun")
end --委托中第一次添加函数使用=添加
customClass.action = fun
--委托中第二次添加函数使用+=,lua中不支持+=运算符,需要分开写
customClass.action = customClass.action + fun
--委托中也可以添加匿名函数
customClass.action = customClass.action + function()
print("临时函数")
end --使用点调用委托还是冒号调用委托都可以调用,最好使用冒号
customClass:action() print("********************") --事件和委托的使用方法不一致(事件不能在外部调用)
--使用冒号添加和删除函数,第一个参数传入加号或者减号字符串,表示添加还是修改函数
--事件也可以添加匿名函数
customClass:eventAction("+",fun)
--事件不能直接调用,必须在C#中提供调用事件的方法,这里已经提供了DoEvent方法执行事件
customClass:DoEvent()
--同样地,事件不能直接清空,需要在C#中提供对应地方法
/// <summary>
/// 自定义类
/// </summary>
public class CustomClass
{
public UnityAction action;
public event UnityAction eventAction;
public void DoEvent()
{
if (eventAction != null)
eventAction();
}
}

  9.特殊问题

local customClass = CS.CustomClass()

--特殊问题一:得到二维数组指定位置元素的值
--获取二维数组的长度
print("行:"..customClass.array:GetLength(0))
print("行:"..customClass.array:GetLength(1)) --不能通过C#的索引访问元素(array[0,0]或array[0][0])
--使用数组提供的成员方法GetValue访问元素
print(customClass.array:GetValue(0,0)) --遍历
for i=0,customClass.array:GetLength(0)-1 do
for j=0,customClass.array:GetLength(1)-1 do
print(customClass.array:GetValue(i,j))
end
end print("***********************") --特殊问题二:lua中空值nil和C#中空值null的比较 --往场景对象上添加一个脚本,存在就不加,不存在再加
GameObject = CS.UnityEngine.GameObject
Rigidbody = CS.UnityEngine.Rigidbody local obj = GameObject("测试nil和null")
local rigidbody = obj:GetComponent(typeof(Rigidbody))
print(rigidbody)
--校验空值,看是否需要添加脚本
--nil和null并不相同,在lua中不能使用==进行判空,一定要使用Equals方法进行判断
--这里如果rigidbody为空,可能报错,所以可以自己提供一个判空函数进行判空
--这里为了笔记方便将函数定义在这里,这个全局函数最好定义在lua脚本启动的主函数Main中
function IsNull(obj)
if obj == nil or obj:Equals(nil) then
return true
end
return false
end
--使用自定义的判空函数进行判断
if IsNull(rigidbody) then
rigidbody = obj:AddComponent(typeof(Rigidbody))
end
print(rigidbody) print("***********************") --特殊问题三:让lua和系统类型能够相互访问 --对于自定义的类型,可以添加CSharpCallLua和LuaCallCSharp这两个特性使Lua和自定义类型能相互访问,但是对于系统类或第三方代码库,这种方式并不适用
--为系统类或者第三方代码库加上这两个特性的写法比较固定,详情见C#代码
/// <summary>
/// 自定义类
/// </summary>
public class CustomClass
{
public int[,] array = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } }; //实现为系统类添加[CSharpCallLua]和[LuaCallCSharp]特性
[CSharpCallLua]
public static List<Type> csharpCallLuaList = new List<Type>()
{
//将需要添加特性的类放入list中
typeof(UnityAction<float>),
};
[LuaCallCSharp]
public static List<Type> luaCallCsharpList = new List<Type>()
{
typeof(GameObject),
};
}

  10.使用协程

--xlua提供了一个工具表,要使用协程必须先调用这个工具表
util = require("xlua.util") GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.LuaCallCSharp)) --被开启的协程函数
fun = function()
local a = 1
while true do
--lua中不能直接使用C#中的yield return返回
--使用lua中的协程返回方法
coroutine.yield(WaitForSeconds(1))
print(a)
a = a + 1
if a>10 then
--协程的关闭,必须要将开启的协程存储起来
mono:StopCoroutine(startedCoroutine)
end
end
end --启动协程
--写法固定,必须使用固定表的cs_generate方法把xlua方法处理成mono能够使用的协程方法
startedCoroutine = mono:StartCoroutine(util.cs_generator(fun))

  11.使用泛型函数

    lua中没有泛型语法,对于C#中的泛型方法,可以直接传递参数(因为lua中不需要声明类型),但是这种写法并不是所有的泛型方法都支持,xlua只支持有约束且泛型作为参数的泛型函数,其他泛型函数不支持。如果要在lua中调用泛型函数,可以使用特定的语法。

local tank = CS.UnityEngine.GameObject.Find("Tank")

--xlua提供了得到泛型函数的方法get_generic_method,参数第一个为类名,第二个为方法名
local addComponentFunc = xlua.get_generic_method(CS.UnityEngine.GameObject,"AddComponent")
--接着调用这个泛型方法,参数为泛型的类,得到一个新方法
local addComponentFunc2 = addComponentFunc(CS.MonoForLua)
--调用,第一个参数是调用的对象,如果有其他参数在后面传递
addComponentFunc2(tank)

    使用限制:打包时如果使用mono打包,这种方式支持使用;如果使用il2cpp打包,泛型参数需要是引用类型或者是在C#中已经调用过的值类型。

热更新解决方案--xlua学习笔记的更多相关文章

  1. 热更新解决方案--tolua学习笔记

    一.tolua使用准备工作:从GitHub上下载tolua(说明:这篇笔记使用的Unity版本是2019.4.18f1c1,使用的tolua是2021年4月9日从GitHub上Clone的tolua工 ...

  2. 热更新基础--AssetBundle学习笔记

    一.简介 AssetBundle简称AB包,特定平台的资产压缩包(包括模型.贴图.预设体.音效.材质球等资产). 作用:Resources下的资源只读且打包后不可修改,而AB包存储位置自定,后期可以动 ...

  3. 热更新语言--lua学习笔记

    一.lua安装和编程环境搭建 lua语言可以在官网:http://luadist.org/下载安装包安装,编程IDE之前学习使用的是SciTE(https://www.cnblogs.com/movi ...

  4. 【腾讯Bugly干货分享】手游热更新方案xLua开源:Unity3D下Lua编程解决方案

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/2bY7A6ihK9IMcA0bOFyB-Q 导语 xL ...

  5. Bugly 多渠道热更新解决方案

    作者:巫文杰 Gradle使用productFlavors打渠道包的痛 有很多同学可能会采用配置productFlavors来打渠道包,主要是它是原生支持,方便开发者输出不同定制版本的apk,举个例子 ...

  6. unity3d热更新插件uLua学习整理

    前言 IOS不能热更新,不是因为不能用反射,是因为System.Reflection.Assembly.Load 无法使用System.Reflection.Emit 无法使用System.CodeD ...

  7. 手游热更新方案xLua开源:Unity3D下Lua编程解决方案

    C#下Lua编程支持 xLua为Unity. .Net. Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用. xLua的突破 xLua在功能.性能.易用 ...

  8. 《精通CSS:高级Web标准解决方案》学习笔记(上)

    鉴于国产CSS书籍基本都是辣鸡的现状,我在半年前动用某工作室的购书资金采购了一些技术书籍,这本广受好评的<精通CSS>也在其中.但是在阅读过后我深深的感觉到,如果说CSS本来已经是一种很琐 ...

  9. 【持续更新】 | OpenCV 学习笔记

    本文地址:http://www.cnblogs.com/QingHuan/p/7365732.html,转载请注明出处 ######################################## ...

随机推荐

  1. 微信小程序 TypeScript bug

    微信小程序 TypeScript bug 执行自定义预览前预处理命令失败! internal/modules/cjs/loader.js:584 throw err; ^ Error: Cannot ...

  2. how to get window width in javascript

    how to get window width in javascript how to get window width in js How to Detect Screen Resolution ...

  3. nasm astrcmp函数 x86

    xxx.asm: %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export as ...

  4. c++ readIntger writeIntger

    类似CE的read/writeIntger函数(外部) #include <iostream> #include <Windows.h> #include <TlHelp ...

  5. HTML页面顶部出现空白部分(#65279字符?)解决办法

    1.在火狐下面用Firebug,选择body,点编辑html的时候,看到是多出了一个这个代表的意思,还真不知道,搜索后了解到是一种中文的编码规则,   UTF-8不需要BOM来表明字节顺序.   制作 ...

  6. 死磕Spring之IoC篇 - BeanDefinition 的加载阶段(XML 文件)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  7. Ctf_show Web[1-8]

    CTFshow_web1: F12查看JS即在代码里 CTFshow_web2: 进入网址后页面如下: ①:尝试使用admin登陆,发现页面无回显 ②:尝试单引号闭合,并且添加' or 1=1#,此时 ...

  8. 渗透测试--Nmap主机识别

    通过本篇博客可以学到:Nmap的安装和使用,列举远程机器服务端口,识别目标机器上的服务,指纹,发现局域网中存活主机,端口探测技巧,NSE脚本使用,使用特定网卡进行检测,对比扫描结果ndiff,可视化N ...

  9. Django练习遇到的错误记录

    _reverse_with_prefix() argument after ** must be a mapping, not set 错误代码: def get_absolute_url(self) ...

  10. 面试系列二:精选大数据面试真题JVM专项-附答案详细解析

    公众号(五分钟学大数据)已推出大数据面试系列文章-五分钟小面试,此系列文章将会深入研究各大厂笔面试真题,并根据笔面试题扩展相关的知识点,助力大家都能够成功入职大厂! 大数据笔面试系列文章分为两种类型: ...