当前位置: 首页 > article >正文

【Lua热更新】下篇 -- 更新中

上篇链接:【Lua热更新】上篇

文章目录

    • 三、`xLua`热更新
      • 📖1.概述
      • 📚︎2.导入`xLua`框架
      • 🔖3. C#调用`Lua`
        • 3.1`Lua`解析器
        • 3.2`Lua`文件夹的重定向
        • 3.3`Lua`解析器管理器
        • 3.4全局变量获取
        • 3.5全局函数获取
        • 3.6映射到List和Dictionary
        • 3.7映射到类
        • 3.8映射到接口
        • 3.9映射到`LuaTable`
      • 🔖4.`Lua`调用C#
      • 🔖5.`xLua`热补丁

三、xLua热更新

📖1.概述

C#代码和Resoureces文件夹不能实现热更新,所以需要使用Lua通过AB包来进行更新脚本

在这里插入图片描述

📚︎2.导入xLua框架

🔹1.获得xLua包,GitHub地址:Tencent/xLua: xLua is a lua programming solution for C#

🔹2.导入文件夹

在这里插入图片描述

增加了xLua编辑栏导入成功

🔹3.点击Generate Code生成相关代码

在这里插入图片描述

🔹4.导入AssetBundles-Browser

GitHub地址:Unity-Technologies/ AssetBundles -Browser

如果有报错删除文件中的报错的Test文件即可

🔖3. C#调用Lua

3.1Lua解析器

Lua解析器让我们在Unity中执行Lua

🎒简述一下原理,就是Lua是一种解释型语言,代码在运行时才被解释器一行行动态翻译和执行,先由Lua编译器编译为字节码,然后交给Lua虚拟机执行,将Lua虚拟机源代码整合给Unity使用实现之间的交互

// 创建一个解析器
LuaEnv luaEnv = new LuaEnv();
// 使用Lua解析器执行Lua语言
luaEnv.DoString("print('First Lua')");
// Lua垃圾回收,手动释放对象
luaEnv.Tick();
// 销毁Lua解析器
luaEnv.Dispose();

🔹Lua默认调用Resources文件夹下的Lua脚本

执行以下代码会发现脚本报错没有找到脚本,原因是因为.lua不是Unity中的文件格式,所以我们需要给.lua在添加一个.txt后缀才能查找到,这个查找函数的源码估计是通过Resourece.Load加载,所以查找不到Lua文件

luaEnv.DoString("require('FistTest')");

在这里插入图片描述

❓存在的问题,从Resources文件夹下读取无法进行热更新,其次每个文件都要手动添加.txt后缀十分麻烦,所以后续需要进行文件夹的重定向和自动

3.2Lua文件夹的重定向

通过AddLoader方法进行重定向,进行自定义加载Lua文件规则

void Start()
{
	luaEnv.AddLoader(MyCustomLoader);
	luaEnv.DoString("require('FistTest')");
}

private byte[] MyCustomLoader(ref string path)
{
    string newPath = Application.dataPath + "/Lua/" + path + ".lua";
    if (File.Exists(newPath))
        return File.ReadAllBytes(newPath);
    else
        Debug.Log("重定向失败" + path);
    return null;
}

🎒我们看一下AddLoader源码

传入一个委托添加到 List 列表中,在我们使用require(文件名)时如果在列表中按顺序查找该文件,那么最后将在Resoureces文件夹中查找

在这里插入图片描述

❓存在问题,最终我们需要在AB包中进行加载

3.3Lua解析器管理器

🔹通过实现Lua解析器管理器保证解析器的唯一性

👓️LuaMgr管理器源码

public class LuaMgr : BaseManager<LuaMgr>
{
    private const string LUA_PATH = "Lua";
    private LuaEnv luaEnv;
	public LuaTable Global {get => luaEnv.Global;} // 获得_G表
    
    // 初始化解析器
    public void Init()
    {
        if (luaEnv != null) return;
        luaEnv = new LuaEnv();
        luaEnv.AddLoader(MyCustomLoader); // 后缀.lua时日常开发重定向到的位置
        luaEnv.AddLoader(MyCustomABLoader); // 后缀.txt使用AB包时重定向到的位置
    }
    
    // 重定向加载AB包中的Lua脚本
    private byte[] MyCustomABLoader(ref string path)
    {
        TextAsset txtLua = ABMgr.GetInstance().LoadRes<TextAsset>(LUA_PATH, path + ".lua");
        if (txtLua == null)
        {
            Debug.LogError("在AB包中重定向失败");
            return null;
        }
        return txtLua.bytes;
    }
    
    private byte[] MyCustomLoader(ref string path)
    {
        string newPath = Application.dataPath + "/"+ LUA_PATH + "/" + path + ".lua";
        if (File.Exists(newPath))
        {
            return File.ReadAllBytes(newPath);
        }
        else
        {
            Debug.LogError("重定向失败" + path);
        }
        return null;
    }

    // 执行lua语言
    public void DoString(string filename)
    {
        if (luaEnv == null)
        {
            Debug.LogError("解析器未初始化");
            return;
        }
        luaEnv.DoString(string.Format("require('{0}')", filename));
    }

    // 释放垃圾
    public void Tick()
    {
        if (luaEnv == null)
        {
            Debug.LogError("解析器未初始化");
            return;
        }
        luaEnv.Tick();
    }

    // 销毁解析器
    public void Dispose()
    {
        luaEnv.Dispose();
        luaEnv = null;
    }
}

👓️BaseManager 源码

//1.C#中 泛型的知识
//2.设计模式中 单例模式的知识
public class BaseManager<T> where T:new()
{
    private static T instance;

    public static T GetInstance()
    {
        if (instance == null)
            instance = new T();
        return instance;
    }
}

❓现在只实现了重定向文件夹,并没有实现重定向AB包加载,接下来继续完成将Lua脚本放在AB包中,通过AB包加载脚本。需要注意的是AB包加载文本后缀不能使用.Lua,所以在打包时需要将后缀改为.txt,不过这一步后续将改成自动的

🔹我们将后缀暂时手动的修改后缀为.txt,进行进行打包,如果打包过程中存在报错,XLua → Clear Generated Code即可

在这里插入图片描述

打包成功后刷新界面

在这里插入图片描述

重定向加载AB包

🔹这里建议实际开发中仍然使用.Lua后缀,那么将重定向读取普通文件夹,进行热更新时在修改为.txt后缀重定向到AB包文件夹

// 重定向加载AB包中的Lua脚本
private byte[] MyCustomABLoader(ref string path)
{
    string newPath = Application.streamingAssetsPath + "/"+ LUA_PATH;
    AssetBundle ab = AssetBundle.LoadFromFile(newPath); // 加载AB包
    TextAsset tx = ab.LoadAsset<TextAsset>(path + ".lua"); // 加载Lua文件
    return tx.bytes;
} 	
3.4全局变量获取

👓️Main.Lua源码

print("=== Main ===")
require('FirstTest') -- 在Unity中已经重定向

👓️FirstTest.Lua 源码

print("=== First Test ===")
myNumber = 1
myFloat = 1.2
myBool = true
myString = "OvO"

👓️Unity中代码

🔹无法直接获得local本地变量,因为不在_G表中

LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoString("Main");

// Get
int myNumber = LuaMgr.GetInstance().Global.Get<int>("myNumber");
float myFloat = LuaMgr.GetInstance().Global.Get<float>("myFloat");
bool myBool = LuaMgr.GetInstance().Global.Get<bool>("myBool");
string myString = LuaMgr.GetInstance().Global.Get<string>("myString");

// Set
LuaMgr.GetInstance().Global.Set("myNumber", 99);
myNumber = LuaMgr.GetInstance().Global.Get<int>("myNumber");
3.5全局函数获取

👓️FirstTest.Lua 源码

myFun1 = function ()
	print("这是一个无参无返回的函数")
end

myFun2 = function (x)
	print("这是一个有参有返回的函数")
	return x + x
end

myFun3 = function ()
	print("这是一个多返回值的函数")
	return 99, "apple", 1.2f
end

myFun4 = function (x, ...)
	print("这是一个变长参数的函数")
	arg = {...}
	for k, v in pairs(arg) do
		print(k, v)
	end
end

🔹无参数无返回函数获取

// Unity自带委托
UnityAction ua1 = LuaMgr.GetInstance().Global.Get<UnityAction>("myFun1");
ua1.Invoke();
// C#委托
Action ac = LuaMgr.GetInstance().Global.Get<Action>("myFun1");
// xLua提供:不建议使用
LuaFunction lf = LuaMgr.GetInstance().Global.Get<LuaFunction>("myFun1");
lf.Call();

🔹有参数有返回函数获取

定义一个委托,需要使用特性否则Lua无法识别,XLua → Generate Code在编译器重新生成一下代码否则也会报错

[CSharpCallLua]
public delegate int MyFunc2(int x);

调用函数

MyFunc2 ua2 = LuaMgr.GetInstance().Global.Get<myFunc2>("myFun2");
int x = ua2.Invoke(2);
print("返回值是" + x);

🔸或者使用C#封装的委托,如果报错就XLua → Generate Code

Func<int, int> ua2 = LuaMgr.GetInstance().Global.Get<Func<int, int>>("myFun2");
int x = ua2.Invoke(2);
print("返回值是" + x);

🔹多返回值函数获取

// 第一个返回值即委托返回值
// 之后的返回值使用out或者ref
[CSharpCallLua]
public delegate int MyFun3(int x, out string fruit, out float value);

调用

MyFun3 fc3 = LuaMgr.GetInstance().Global.Get<MyFun3>("myFun3");
string fruit;
float value;
int a = fc3.Invoke(12, out fruit, out value);

或者使用xLua自带的获取方法

LuaFunction lf3 = LuaMgr.GetInstance().Global.Get<LuaFunction>("myFun3");
object[] ojb = lf3.Call(100);

🔹多返回值函数获取

// 如果变长参数每个类型都不确定传入Object
[CSharpCallLua]
public delegate void MyFun4(string a, params int[] args);
3.6映射到List和Dictionary

👓️FirstTest.Lua 源码

testList = {1, 2, 3, 4}
testDic = {
	["a"] = 1,
	["b"] = 2,
	["c"] = 3
}

获取

LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");

List<int> testList = LuaMgr.GetInstance().Global.Get<List<int>>("testList");
Dictionary<string, int> testDic = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic");
3.7映射到类

👓️FirstTest.Lua 源码

testClass = {
	name = "咪咪",
	age = 1,
	eat = function ()
		print("吃猫粮")
	end
}

🔹C#中声明该类,成员名称必须和lua中一致,类名可以不需要一致,成员必须是公共的

// 少于lua中的变量则获取不到,多余lua中的变量则不会赋值,并不会报错
public class TestClass
{
    public string name;
    public int age;
    public UnityAction eat;
}
3.8映射到接口

🔹声明接口,接口中不允许声明成员变量,所以使用成员属性来接收

🔹接口拷贝是引用拷贝,在C#中修改值Lua中也会被修改

[CSharpCallLua] // 需要特性
public interface MyInterface
{
    public string name { get; set; }
    public int age { get; set; }
    public UnityAction eat{ get; set; }
}

获取

MyInterface myInterface = LuaMgr.GetInstance().Global.Get<MyInterface>("TestClass");
3.9映射到LuaTable

官方不建议使用LuaTableLuaFunction会有垃圾占用,LuaTable需要手动销毁否则会一致占用内存

LuaTable table = LuaMgr.GetInstance().Global.Get<LuaTable>("TestClass");
// 获得
string name = table.Get<string>("name");
int age = table.Get<int>("age");
UnityAction ac = table.Get<UnityAction>("eat");

// 修改
table.Set("age", 55);
// 释放table垃圾
table.Dispose();

🔖4.Lua调用C#

🔖5.xLua热补丁


http://www.kler.cn/a/443331.html

相关文章:

  • 鸿蒙UI(ArkUI-方舟UI框架)
  • 蓝桥杯训练
  • Qt QDockWidget详解以及例程
  • python对redis的增删查改
  • Django:构建高效Web应用的强大框架
  • 【Linux】Linux指令apt、systemctl、软链接、日期时区
  • Webpack常见的Plugin有哪些?
  • Java 初学者的第一个 SpringBoot3.4.0 登录系统
  • 【安当产品应用案例100集】032-重塑企业SaaS平台的PostgreSQL凭据管理体系
  • Running CMake (运行 CMake)
  • C语言学习day24:DLL给程序打上窗口破解补丁
  • 借助腾讯云质检平台的新范式,做工业制造企业质检的“AI慧眼”
  • ofd转pdf ofd转图片 python脚本(非ai生成,实测可转换)
  • arduino继电器与电机水泵的使用
  • IDEA能够从mapper跳转到xml的插件
  • 微信小程序TTS解决方案
  • 告别数据查询瓶颈!PostgreSQL 多表连接与复杂条件解析
  • 大连理工大学经济管理学院冠名讲席教授捐赠与聘任仪式圆满举行,乐凡信息成为“冠名讲席教授项目”首捐企业!
  • simpleperf生成火焰图的步骤
  • Python如何正确解决reCaptcha验证码(9)
  • 揭开 Choerodon UI 拖拽功能的神秘面纱
  • 【容器】k8s学习笔记基础部分(三万字超详细)
  • pset4filter less: helpers.c
  • DHTMLX Scheduler 7.2全新发布:增强了重复事件的编辑、修改了实时更新等
  • 【Linux】HTTPS
  • day38-SSH安全登录