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 后面热补丁时需要用到,现在可以先不拖进去

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

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对象(比如LuaTable,LuaFunction),以及其它一些事情。需要定期调用,比如在 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