热更新解决方案5——toLua
概述
Tolua框架导入和AB包相关准备
结合视频进行项目资源导入。
C#调用Lua
1.toLua解析器
2.toLua解析器自定义解析方式
自定义路径
自定义解析方式
using System.Collections;
using System.Collections.Generic;
using LuaInterface;
using UnityEngine;
public class LuaCustomLoader : LuaFileUtils
{
public override byte[] ReadFile(string fileName)
{
//Debug.Log("自定义解析方式" + fileName);
//如果想要重新定义 解析lua的方式 那么只需要在该函数中去写逻辑即可
//如果没有lua后缀 加上lua后缀 不管是从AB包中加载还是从res下加载 都不支持用.lua后缀
//所以tolua加上了bytes后缀
//我们自己可以加上.txt后缀
if(!fileName.EndsWith(".lua")){
fileName += ".lua";
}
byte[] buffer = null;
//因为 进行热更新的lua代码 肯定是我们自己写的上层lua逻辑
//第二种 从AB包中加载lua文件
//CSharpCallLua/Lesson2_Loader这样的名字 但是在AB包中我们只需要文件名 所以需要拆分一下
string[] strs = fileName.Split('/');
//加载AB包中的lua文件
TextAsset luaCode = ABMgr.GetInstance().LoadRes<TextAsset>("lua", strs[strs.Length - 1]);
if(luaCode != null){
buffer = luaCode.bytes;
Resources.UnloadAsset(luaCode);
}
//toLua的自带逻辑和自带lua类 我们不太需要去热更新 直接从resources下 去加载即可
if(buffer == null){
//第一种 从resources中加载lua文件
//先要通过 窗口的Lua ——> Copy Lua files to Resources 将lua文件加载到res文件下
//注意:它只能把lua文件夹下的自定义文件拷贝到res下
string path = "Lua/" + fileName;
TextAsset text = Resources.Load<TextAsset>(path);
if(text != null){
buffer = text.bytes;
//卸载使用后的 文本资源
Resources.UnloadAsset(text);
}
}
return buffer;
}
}
3.toLua解析器管理器
using System.Collections;
using System.Collections.Generic;
using LuaInterface;
using UnityEngine;
/// <summary>
/// 学习的主要目标 通过LuaMgr来管理唯一的一个toLua解析器
/// 要把该管理器 做成一个 继承了mono的 单例模式对象 因为之后的知识点 需要用到它的特性
/// </summary>
public class LuaMgr : SingletonAutoMono<LuaMgr>
{
private LuaState luaState;
public void Init(){
//最后打包的时候 再用 再取消注释
//new LuaCustomLoader();
//就是初始化 唯一的 luaState
luaState = new LuaState();
luaState.Start();
//后面有些东西没写 当我们学习到对应知识点时 再来写
//委托初始化相关
//协程相关
//Lua使用Unity中的类相关
}
/// <summary>
/// 该属性可以让外部获取到 解析器
/// </summary>
/// <value></value>
public LuaState LuaState{
get{
return luaState;
}
}
/// <summary>
/// 提供一个外部执行 lua语法字符串的 方法
/// </summary>
/// <param name="str">lua语法字符串</param>
/// <param name="chunkName">执行出处</param>
public void DoString(string str, string chunkName = "LuaMgr.cs"){
luaState.DoString(str, chunkName);
}
/// <summary>
/// 执行指定名字的lua脚本
/// </summary>
/// <param name="fileName"></param>
public void Require(string fileName){
luaState.Require(fileName);
}
/// <summary>
/// 销毁lua解析器
/// </summary>
public void Dispose(){
if(luaState == null)
return;
luaState.CheckTop();
luaState.Dispose();
luaState = null;
}
}
4.全局变量获取
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson4_CallVariable : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().Require("Main");
//获取全局变量
//toLua中访问全局变量 一个套路 得到luaState解析器 然后中括号 变量名 即可得到
Debug.Log("testNumber:" + LuaMgr.GetInstance().LuaState["testNumber"]);
Debug.Log("testBool:" + LuaMgr.GetInstance().LuaState["testBool"]);
Debug.Log("testFloat:" + LuaMgr.GetInstance().LuaState["testFloat"]);
Debug.Log("testString:" + LuaMgr.GetInstance().LuaState["testString"]);
//值拷贝
int value = Convert.ToInt32(LuaMgr.GetInstance().LuaState["testNumber"]);
value = 99;
Debug.Log("testNumber:" + LuaMgr.GetInstance().LuaState["testNumber"]);
//如果要改值 直接这些写即可
LuaMgr.GetInstance().LuaState["testNumber"] = 99;
Debug.Log("testNumber:" + LuaMgr.GetInstance().LuaState["testNumber"]);
//toLua中 也没有办法通过C#得到local申明的局部临时变量
Debug.Log("testLocal:" + LuaMgr.GetInstance().LuaState["testLocal"]);
//可以在C#处为lua添加全局变量 相当于在Lua中的_G中加了一个变量
Debug.Log("addValue:" + LuaMgr.GetInstance().LuaState["addValue"]);
LuaMgr.GetInstance().LuaState["addValue"] = 999;
Debug.Log("addValue:" + LuaMgr.GetInstance().LuaState["addValue"]);
}
// Update is called once per frame
void Update()
{
}
}
5.全局函数获取
无参无返回
有参有返回
多返回
到这里时会报错,处理好重要点就不会了!!!
重要点:
变长参数
6.访问Lua中table表现List和Dictionary
using System.Collections;
using System.Collections.Generic;
using LuaInterface;
using UnityEngine;
public class Lesson6_CallListDic : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//主要学习目的 学会tolua 如何在C#中调用Lua中table表现的List和Dictionary
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().Require("Main");
//List相关
//toLua中 C#得到Lua中的表 都只是一个套路 通过LuaTable来获取
LuaTable table = LuaMgr.GetInstance().LuaState.GetTable("testList");
Debug.Log(table[1]);
Debug.Log(table[2]);
Debug.Log(table[3]);
Debug.Log(table[4]);
Debug.Log(table[5]);
//如果要遍历LuaTable对应的table 需要先转数组
object[] objs = table.ToArray();
for(int i = 0; i < objs.Length; i++){
Debug.Log("遍历打印:" + objs[i]);
}
//如果是修改 是否是引用拷贝? 测试结果为:是引用拷贝
table[1] = 999;
LuaTable tableTmp = LuaMgr.GetInstance().LuaState.GetTable("testList");
Debug.Log("测试引用拷贝:" + tableTmp[1]); //输出999
LuaTable table2 = LuaMgr.GetInstance().LuaState.GetTable("testList2");
Debug.Log(table2[1]);
Debug.Log(table2[2]);
Debug.Log(table2[3]);
Debug.Log(table2[4]);
Debug.Log(table2[5]);
objs = table2.ToArray();
for(int i = 0; i < objs.Length; i++){
Debug.Log("遍历打印2:" + objs[i]);
}
//Dictionary相关
LuaTable dic = LuaMgr.GetInstance().LuaState.GetTable("testDic");
Debug.Log(dic["1"]);
Debug.Log(dic["2"]);
Debug.Log(dic["3"]);
Debug.Log(dic["4"]);
LuaDictTable<string, int> luaDic = dic.ToDictTable<string, int>();
Debug.Log("luaDic:" + luaDic["1"]);
Debug.Log("luaDic:" + luaDic["2"]);
Debug.Log("luaDic:" + luaDic["3"]);
Debug.Log("luaDic:" + luaDic["4"]);
dic["1"] = 9999;
LuaTable dicTmp = LuaMgr.GetInstance().LuaState.GetTable("testDic");
Debug.Log("Dic引用拷贝测试:" + dicTmp["1"]);
//通过中括号得到值 只支持 int和string 其它类型的 没有办法直接来获取
LuaTable dic2 = LuaMgr.GetInstance().LuaState.GetTable("testDic2");
//Debug.Log(dic2[true]);
LuaDictTable<object, object> luaDic2 = dic2.ToDictTable<object, object>();
Debug.Log(luaDic2[true]);
Debug.Log(luaDic2["123"]);
Debug.Log(luaDic2[false]);
//dic建议 使用 迭代器遍历
IEnumerator<LuaDictEntry<object, object>> ie = luaDic2.GetEnumerator();
while(ie.MoveNext()){
Debug.Log(ie.Current.Key + "_" + ie.Current.Value);
}
}
// Update is called once per frame
void Update()
{
}
}
7.访问Lua中的table
using System.Collections;
using System.Collections.Generic;
using LuaInterface;
using UnityEngine;
public class Lesson7_CallTable : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//主要学习目的 学会toLua中如何在C#中调用lua中的自定义table
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().Require("Main");
//通过luaState中的 GetTable方法 来获取
LuaTable table = LuaMgr.GetInstance().LuaState.GetTable("testClass");
//table = LuaMgr.GetInstance().LuaState["testClass"] as LuaTable;
//访问其中的变量
//中括号 变量名 就可以获取
Debug.Log(table["testInt"]);
Debug.Log(table["testBool"]);
Debug.Log(table["testFloat"]);
Debug.Log(table["testString"]);
//引用拷贝测试
table["testInt"] = 10;
LuaTable table2 = LuaMgr.GetInstance().LuaState.GetTable("testClass");
Debug.Log("测试引用拷贝:" + table2["testInt"]);
//获取其中的函数
LuaFunction function = table.GetLuaFunction("testFun");
function.Call();
//如果表中还有一张表,还是通过GetTable来获得
//table.GetTable<LuaTable>();
}
// Update is called once per frame
void Update()
{
}
}
8.使用toLua提供的协程
Lua调用C#
1.类
知识点
print("**************toLua访问C#类*************")
--toLua和xLua访问C#类非常类似
--固定套路
--命名空间.类名
--Unity的类 比如 GameObject Transform等等 ——> UnityEngin.类名
--UnityEngin.GameObject
--通过C#中的类实例化一个对象 lua中没有new 所以我们直接使用 类名括号就是实例化对象
--默认 调用的 相当于是无参构造
--和xLua的区别是 不需要加CS.命名空间.类名 省略了CS.
local obj1 = UnityEngine.GameObject()
local obj2 = UnityEngine.GameObject("Sunset")
--为了方便使用 并且节约性能 定义全局变量来存储我们C#中的类
--相当于取一个别名
GameObject = UnityEngine.GameObject
local obj3 = GameObject("Sunset2")
--类中的静态对象 可以使用. 来调用
local obj4 = GameObject.Find("Sunset")
--得到对象中的成员变量 也是 直接对象 . 属性 即可
print(obj4.transform.position.x)
--注意!!! Debug默认报错 是因为我们没有把它加到CustomSetting文件中
--如果发现Lua使用C#中的类报错了 不认识 我们需要把它加入到CustomSetting文件中的customTypeList中去
--然后再通过菜单栏 Lua中进行生成代码
Debug = UnityEngine.Debug
Debug.Log(obj4.transform.position.x)
--成员方法的使用
Vector3 = UnityEngine.Vector3
--如果要使用对象的成员方法!!! 一定要加 :
obj4.transform:Translate(Vector3.right)
Debug.Log(obj4.transform.position.x)
--使用自定义继承了Mono的类
--继承了Mono的类 是不能直接new的
local obj5 = GameObject("加脚本测试")
--通过GameObject的 AddComponent方法 添加脚本
--typeof 是toLua提供的一个 得到Type的方法
--如果发现Lua使用C#中的类报错了 不认识 我们需要把它加入到CustomSetting文件中的customTypeList中去
--然后再通过菜单栏 Lua中进行生成代码
obj5:AddComponent(typeof(LuaCallCSharp))
--没有继承Mono的类
--同样 需要在CustomSetting中去添加
local t1 = Test()
t1:Speak("t1说话")
local t2 = Sunset.Test2()
t2:Speak("t2说话")
调用Lua代码
申明类实例(先申明好)
要调用C#中的类方法要先绑定解析器
自定义类或toLua中没有加入的类都需要自己添加上
测试结果:
2.枚举
知识点
调用
实例
添加自定义类相关
测试
3.数组、List和Dictionary
数组
知识点
调用
实例
测试:
List和Dic
事例
知识点
print("***********Lua访问C#中的List、Dictionary**************")
--虽然自定义类Lesson3已经在 就是在CustomSetting中添加了并且生成了文件
--但是我们改变了这个类 里面新加了 两个变量
--我们必须要在菜单栏中重新生成一次
local obj = Lesson3()
print("****List****")
obj.list:Add(10)
obj.list:Add(12)
obj.list:Add(15)
--得到其中一个元素
print(obj.list[0])
--数组长度
print("长度:" .. obj.list.Count)
--遍历
for i = 0, obj.list.Count - 1 do
print("遍历:" .. obj.list[i])
end
--在toLua中创建一个list
print("创建List")
--toLua它对泛型支持比较糟糕 想要用什么泛型类型的对象
--都需在CustomSetting中去添加对应的类型 再生成文件
--才能够在ToLua中去使用
--如 List<string>
local list2 = System.Collections.Generic.List_string()
list2:Add("123")
print(list2[0])
print("*****************Dictionary**************")
obj.dic:Add(1, "123")
obj.dic:Add(2, "234")
obj.dic:Add(3, "345")
print(obj.dic[1])
--xlua是通过pairs去遍历的 字典
--toLua中不支持这种遍历方式
--toLua中要使用迭代器来进行遍历
local iter = obj.dic:GetEnumerator()
while iter:MoveNext() do
local v = iter.Current
print(v.Key .. "_" .. v.Value)
end
--遍历键
local keyIter = obj.dic.Keys:GetEnumerator()
while keyIter:MoveNext() do
print("键:" .. keyIter.Current)
end
--遍历值
local valueIter = obj.dic.Values:GetEnumerator()
while valueIter:MoveNext() do
print("值:" .. valueIter.Current)
end
--如果要在Lua中创建字典对象
--和List一样 需要在CustomSetting中去添加对应的类型
print("创建Dic")
local dic2 = System.Collections.Generic.Dictionary_int_string()
dic2:Add(10,"123")
print(dic2[10])
local dic3 = System.Collections.Generic.Dictionary_string_int()
dic3:Add("123", 888)
--toLua使用Dic 不能够直接通过字符串作为键来访问值
--print(dic3["123"])
local b,v = dic3:TryGetValue("123", nil)
print(v)
调用
添加
测试
4.函数(拓展方法)
事例
知识点
调用
添加
测试
5.函数(ref和out)
事例
知识点
调用
添加
测试
6.函数(重载)
事例
知识点
调用
添加
测试
7.委托和事件
事例
知识点
print("************toLua访问C#的委托和事件**************")
local obj = Lesson7()
--委托是用来装载函数的
--要使用C#中的委托 就是用来装lua函数的
local fun = function()
print("Lua函数Fun")
end
print("*********委托中加函数**********")
--Lua中没有复合运算符 不能用+=
--如果是第一次往委托中加函数 因为是nil 不能直接+ 所以第一次 要先等=
obj.del = fun
obj.del = obj.del + fun
obj.del = obj.del + function()
print("临时申明的函数")
end
--xLua执行委托直接: obj.del()
--toLua中没有办法直接这样执行
--并且toLua中没有办法直接执行委托函数 所以只能在C#包裹一个执行方法过来用
obj:DoDel()
print("***********委托中减函数************")
obj.del = obj.del - fun
obj.del = obj.del - fun
obj:DoDel()
print("***********委托中清空函数**************")
obj.del = nil
obj:DoDel()
obj.del = fun
obj:DoDel()
local fun2 = function()
print("事件加的函数")
end
print("***********事件加的函数**********")
--xLua中事件加函数 对象:事件名("+/-", 函数)
--toLua中事件加减函数 和 委托方式一样
--obj.eventAction = fun2 这行代码报错 因为要遵循C#中的规则 不能直接 = 只能是+= -=
obj.eventAction = obj.eventAction + fun2
obj.eventAction = obj.eventAction + function()
print("事件加的匿名函数")
end
obj:DoEvent()
print("***********事件减的函数**********")
obj.eventAction = obj.eventAction - fun2
obj:DoEvent()
print("***********事件中清空函数**********")
obj:ClearEvent()
obj:DoEvent()
调用
添加
测试
8.协程
知识点
调用
添加
这个不是在CusTomSetting里添加自定义类了,这是在LuaMgr里添加注册
测试
注意点
注意点一:
在LuaMgr中需要注册委托、协程、类相关的东西,里面封装了一些DoString、Require、Dispose方法,主要是为了好管理唯一toLua解析器
using System.Collections;
using System.Collections.Generic;
using LuaInterface;
using UnityEngine;
/// <summary>
/// 学习的主要目标 通过LuaMgr来管理唯一的一个toLua解析器
/// 要把该管理器 做成一个 继承了mono的 单例模式对象 因为之后的知识点 需要用到它的特性
/// </summary>
public class LuaMgr : SingletonAutoMono<LuaMgr>
{
private LuaState luaState;
public void Init(){
//最后打包的时候 再用 再取消注释
//new LuaCustomLoader();
//就是初始化 唯一的 luaState
luaState = new LuaState();
luaState.Start();
//后面有些东西没写 当我们学习到对应知识点时 再来写
//委托初始化相关
//想要C#和Lua相互访问使用委托 必须要初始化时 把委托工厂初始化了 不然没法使用委托
DelegateFactory.Init();
//协程相关
//如果要让toLua的协程跑起来 必须添加一个脚本
LuaLooper loop = this.gameObject.AddComponent<LuaLooper>();
//将我们自己申明的解析器和 lualooper绑定起来 就可以让协程跑起来
loop.luaState = luaState;
//Lua协程注册
LuaCoroutine.Register(luaState, this);
//Lua使用Unity中的类相关
LuaBinder.Bind(luaState);
}
/// <summary>
/// 该属性可以让外部获取到 解析器
/// </summary>
/// <value></value>
public LuaState LuaState{
get{
return luaState;
}
}
/// <summary>
/// 提供一个外部执行 lua语法字符串的 方法
/// </summary>
/// <param name="str">lua语法字符串</param>
/// <param name="chunkName">执行出处</param>
public void DoString(string str, string chunkName = "LuaMgr.cs"){
luaState.DoString(str, chunkName);
}
/// <summary>
/// 执行指定名字的lua脚本
/// </summary>
/// <param name="fileName"></param>
public void Require(string fileName){
luaState.Require(fileName);
}
/// <summary>
/// 销毁lua解析器
/// </summary>
public void Dispose(){
if(luaState == null)
return;
luaState.CheckTop();
luaState.Dispose();
luaState = null;
}
}
注意点二:
CustomSetting中添加自定义类或未收录的类
总结
toLua对泛型不是很友好,toLua的暖更新类似于xLua的热补丁,但是xLua的整体功能更完善,所以建议先使用xLua。