xLua学习笔记——初见(壹)

目录

目录

一、XLua 概述

1、xlua 简介

xLua 是由腾讯维护的一个开源项目,xLua 为 Unity、 .Net、 Mono 等 C# 环境增加 Lua 脚本编程的能力,借助 xLua,这些 Lua 代码可以方便的和 C# 相互调用。简而言之就是一个方便我们 Lua 和 C# 相互调用的框架

2、xlua 安装

在 github 下载 xLua 后,我们打开文件夹,可以看见下面这些文件,将 Assets 文件夹下两个文件夹 Plugins 和 Xlua,拖入 Unity 的 Assets 文件夹下就好了。Tools 后面热补丁时需要用到,现在可以先不拖进去

image-20260129123023882

导入成功后,unity 顶部菜单栏就会出现这两个选项,用于生成和清空 xlua 相关代码,导入后可以点一下 Generate Code 生成下 Xlua 所需代码

image-20260129123120169

3、一些准备工作

之后导入 AB 包,准备一个 AB 包管理器,用于加载 AB 包资源,具体代码在AssetBundle的笔记那里,这里只展示大概的方法:

public class ABMgr : SingletonAutoMono<ABMgr>
{
    // 同步加载AB包资源
    public Object LoadRes(string abName,string resName);
 
    public Object LoadRes(string abName, string resName,System.Type type);
 
    public T LoadRes<T>(string abName,string resName);
 
    // 异步加载AB包资源
    public void LoadResAsync(string abName,string resName,UnityAction<Object> callback);
 
    public void LoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callback);
 
    public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callback);
 
    // 卸载指定AB包
    public void UnLoad(string abName);
 
    // 清理AB包资源
    public void ClearAB();
}

可以看到,每种加载方式我都提供了 3 种方式:普通、Type、泛型

其中不推荐的就是第一种加载方式,因为遇到同名不同类型的资源无法区分;而第三种泛型虽然 c# 中常用,但 lua 是不支持泛型的,所以我们会使用第二种通过 Type 的方式进行加载,并且为了方便,采用同步加载资源的方式,即我们只会用到 ABMgr 中的这个方法:

public Object LoadRes(string abName, string resName,System.Type type);

二、Lua 解析器

这算是 C# 调用 Lua 部分的内容,但是感觉篇幅也比较多,因此单独作为一节

(一)Lua 解析器 LuaEnv

LuaEnv 的功能:能够让我们在 Unity 中执行 Lua,下面列举一些常用的方法:

1、DoString

  • 作用:执行一个 Lua 代码块

  • 参数:正常来说我们用第一个参数即可,需要详细了解其他参数请看官方文档。chunk:Lua 代码的字符串

object[] DoString(string chunk, string chunkName = "chuck", LuaTable env = null)

2、Tick

  • 作用:清除 Lua 的未手动释放的 LuaBase 对象(比如LuaTableLuaFunction),以及其它一些事情。需要定期调用,比如在 MonoBehaviour 的 Update 中调用。就是 Lua 的垃圾回收
void Tick()

3、Dispose

  • 作用:销毁该解析器
void Dispose()

4、LuaEnv 实例 1:Hello World

创建一个 C# 脚本,我们在 Start 周期函数里写吧,罗列了上述的几个函数用法,运行后会打印:LUA: hello world,前面的 LUA 字符串是 xlua 自带的,说明是 lua 的信息

void Start()
{
    // Lua解析器,能够在Unity中执行Lua
    LuaEnv env = new LuaEnv();
 
    // 执行Lua
    env.DoString("print('hello world')"); // LUA: hello world
 
    // 清除Lua中没有手动释放的对象(垃圾回收),一般在Update中调用
    env.Tick();

    // 销毁Lua解析器
    env.Dispose();
}

5、LuaEnv 实例 2:Lua 的 Require 调用

正常来说,我们不会直接在 DoString 写 Lua 代码,而是通过 require 调用一个类似主入口的 lua 脚本,这样其余代码都可以 lua 内部实现。

我们先创建一个 lua 脚本,取名 Main.lua.txt,放在 Resources 文件夹下,只需要一句话提醒下就行啦:

print("Main Lua脚本启动")

为什么后面要加 txt 后缀呢?为什么放在 Resources 下呢?

因为在没有配置解析器的情况下,默认加载路径都是 Resources 文件夹,但是 Resources 加载的话识别不了.lua 类型文件,只能加载文本文件,因此只能将后缀改为.txt 才可以加载

好啦,接下来就在实例 1 的基础上,把 Dostring 里的内容改成这样:

env.DoString("require('Main')");

点击运行,就可以看见打印了 LUA: Main Lua脚本启动

不过,如果每次在开发过程中,都要将 lua 文件放在 Resources 下并且需要加后缀 txt 的话,就不太方便,因此,接下来我们学习解析器中的 AddLoader 这个方法

(二)自定义 Loader

前面我们提到,调用 require 时,默认会在 Resources 找 lua 文件,但是我们也可以调用解析器的 AddLoader 方法来传入委托,即自定义的 Loader。如果我们 Add 了多次自定义 Loader,那么 require 的查找文件顺序就会像这样,类似文件路径重定向:

自定义 Loader 查找 1 -> 自定义 Loader 查找 2 -> … -> 默认路径查找

1、AddLoader概述

增加一个自定义 loader 的方法:

void AddLoader(CustomLoader loader)

其中 CustomLoader 是一个委托,源代码如下:

filepath 是 require 所使用的参数,即执行的 lua 脚本名,我们需要返回的是 lua 文件的 byte 数组

public delegate byte[] CustomLoader(ref string filepath);

2、LuaEnv 实例 3:自定义 Loader

先确定想要自定义的加载位置,比如我想在 Assets 文件夹下创建一个 Lua 文件夹,以后的 lua 代码脚本都放在这里,并且我不想每次都为 lua 脚本加 txt 后缀,也就是说,以后我的 lua 文件路径都类似这样:Assets/Lua/XX.lua

我们就将实例 2 的 Main.lua.txt 改成 Main.lua,然后放在 Lua 文件夹下吧

之后,在C#类中自定义一个 Loader:

private byte[] MyCustomLoader(ref string filePath)
{
    // 拼接路径
    string path = Application.dataPath + "/Lua/" + filePath + ".lua";
 
    // 加载文件
    if (File.Exists(path))
        return File.ReadAllBytes(path); // 返回加载的脚本文件的字节数组
 
    return null;
}

在解析器中添加这个 loader:

void Start()
{
    // Lua解析器
    LuaEnv env = new LuaEnv();
 
    // 添加自定义Loader
    env.AddLoader(MyCustomLoader);
    // 执行Lua脚本
    env.DoString("require('Main')");
 
    // 销毁Lua解析器
    env.Dispose();
}

运行,可以发现这次即使 Lua 脚本改成了 Main.lua,并且没放在 Resources 下,依然可以被执行,这样以后我们就可以使用.lua 后缀来编写 Lua 脚本了,这就是自定义 Loader 的好处

(三)LuaEnv 管理器

LuaEnv 的使用建议:全局就一个 luaEnv 实例,并在 Update 中调用 GC 方法,完全不需要时调用 Dispose

上面是官方 API 的使用建议,因此,我们需要制作一个 LuaEnv 管理器,用于包装 LuaEnv 并且提供一些方法供外部调用:

public class LuaMgr : BaseManager<LuaMgr>
{
    private LuaEnv luaEnv;
 
    //Lua总表,_G表
    //用于之后 lua访问C#时 使用 通过总表获取lua中各种内容
    public LuaTable Global
    {
        get
        {
            return luaEnv.Global;
        }
    }
 
    // 初始化
    public void Init()
    {
        //唯一的解析器
        luaEnv = new LuaEnv();
        //添加重定向委托函数
        luaEnv.AddLoader(CustomLoader);
        luaEnv.AddLoader(CustomLoaderFormAB);
    }
 
    //执行Lua脚本
    public void DoString(string luaScript, string fromWhere = null)
    {
        luaEnv.DoString(luaScript, fromWhere);
    }
 
    //释放垃圾
    public void Tick()
    {
        luaEnv.Tick();
    }
 
    //销毁
    public void Dispose()
    {
        luaEnv.Tick();
        luaEnv.Dispose();
    }
 
    // 执行Lua文件
    public void DoLuaFile(string fileName)
    {
        string str = string.Format("require('{0}')", fileName);
        luaEnv.DoString(str);
    }
 
    #region 自定义Loader
    private byte[] CustomLoader(ref string filepath)
    {
        //决定Lua文件所在路径
        string path = Application.dataPath + "/Lua/" + filepath + ".lua";
        //C#自带的文件读取类
        if (File.Exists(path))
            return File.ReadAllBytes(path);
 
        Debug.Log("CustomLoader重定向失败");
        return null;
    }
 
    //再写一个Load 用于从AB包加载Lua文件
    private byte[] CustomLoaderFormAB(ref string filepath)
    {
        //改为我们的AB包管理器加载
        TextAsset file = ABMgr.GetInstance().LoadRes<TextAsset>("lua", filepath + ".lua");
        if (file != null)
            return file.bytes;
 
        Debug.Log("CustomLoaderFormAB重定向失败");
        return null;
    }
    #endregion
}

上面的管理器中,除了基本的解析器功能外,还有两个新内容:

  • 属性 Lua 总表,也就是_G表,后面会用到,
  • 两个自定义 Loader,一个是日常开发用的(即之前的实例 3),另一个是 AB 包加载用的,因为使用 AB 包加载时,无法识别.lua文件,还是得加上后缀 txt(后面会有工具一键修改后缀)。一般开发时用第一个自定义 Loader,最后打包时用第二个 AB 包的自定义 Loader