热更新解决方案5——toLua

发布于:2025-03-19 ⋅ 阅读:(12) ⋅ 点赞:(0)

概述

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。